test-namer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Test Namer

Test Namer

Write tests that describe behavior in plain English, not implementation details. Based on Vladimir Khorikov's testing principles.
编写用通俗易懂的英语描述行为而非实现细节的测试,本指南基于Vladimir Khorikov的测试原则编写。

Naming Guidelines

命名规范

Rules

规则

  1. No rigid naming policy — never use
    [MethodUnderTest]_[Scenario]_[ExpectedResult]
    or similar templates
  2. Describe behavior to a non-programmer familiar with the problem domain — a domain expert should understand the test name
  3. Separate words with underscores in function/method names (not needed in string-based test names like JS/TS)
  4. Do not include the SUT's method name in the test name — test behavior, not a method
  5. Use plain facts, not wishes — write
    is_invalid
    , not
    should_be_invalid
  6. Use basic English grammar — articles (
    a
    ,
    the
    ) improve readability
  7. Be specific
    Delivery_with_a_past_date_is_invalid
    beats
    Delivery_with_invalid_date_is_invalid
  1. 不使用僵化的命名模板 —— 永远不要使用
    [MethodUnderTest]_[Scenario]_[ExpectedResult]
    或类似的模板
  2. 向熟悉业务领域的非程序员描述行为 —— 领域专家应该能看懂测试名称
  3. 函数/方法名称中用下划线分隔单词(JS/TS等基于字符串的测试名称不需要这么做)
  4. 测试名称中不要包含SUT的方法名 —— 测试的是行为,不是某个方法
  5. 使用客观事实而非主观预期 —— 写
    is_invalid
    ,不要写
    should_be_invalid
  6. 使用基础英语语法 —— 冠词(
    a
    the
    )可以提升可读性
  7. 尽可能具体 ——
    Delivery_with_a_past_date_is_invalid
    优于
    Delivery_with_invalid_date_is_invalid

Naming Progression Example

命名优化示例

Starting from a rigid convention — progressively improve:
IsDeliveryValid_InvalidDate_ReturnsFalse     -- rigid, cryptic
Delivery_with_invalid_date_should_be_invalid  -- plain English (good start)
Delivery_with_past_date_should_be_invalid     -- more specific
Delivery_with_past_date_is_invalid            -- fact, not wish
Delivery_with_a_past_date_is_invalid          -- natural grammar (final)
从僵化的约定开始,逐步优化:
IsDeliveryValid_InvalidDate_ReturnsFalse     -- 僵化、晦涩
Delivery_with_invalid_date_should_be_invalid  -- 通俗英语(良好的开端)
Delivery_with_past_date_should_be_invalid     -- 更具体
Delivery_with_past_date_is_invalid            -- 客观事实,而非预期
Delivery_with_a_past_date_is_invalid          -- 符合自然语法(最终版本)

Exception: Utility Code

例外:工具类代码

For utility/helper code without business logic, referencing the method name is acceptable since the behavior doesn't mean anything to business people:
Sum_of_two_numbers
Trimmed_string_has_no_leading_whitespace
对于没有业务逻辑的工具/帮助类代码,可以引用方法名,因为这类行为对业务人员没有意义:
Sum_of_two_numbers
Trimmed_string_has_no_leading_whitespace

Language-Specific Adaptations

不同语言的适配方案

Go

Go

Test functions MUST start with
Test
(language requirement). Append the descriptive name:
go
func TestDelivery_with_a_past_date_is_invalid(t *testing.T) { ... }
Subtests via
t.Run
have full naming freedom:
go
func TestDelivery(t *testing.T) {
    t.Run("with a past date is invalid", func(t *testing.T) { ... })
    t.Run("with a future date is valid", func(t *testing.T) { ... })
}
Table-driven tests — use descriptive
name
fields, not method signatures:
go
tests := []struct {
    name string
    // ...
}{
    {"delivery with a past date is invalid", ...},
    {"delivery for tomorrow is valid", ...},
}
测试函数必须以
Test
开头(语言要求),后面追加描述性名称:
go
func TestDelivery_with_a_past_date_is_invalid(t *testing.T) { ... }
通过
t.Run
实现的子测试可以自由命名:
go
func TestDelivery(t *testing.T) {
    t.Run("with a past date is invalid", func(t *testing.T) { ... })
    t.Run("with a future date is valid", func(t *testing.T) { ... })
}
表驱动测试 —— 使用描述性的
name
字段,不要用方法签名:
go
tests := []struct {
    name string
    // ...
}{
    {"delivery with a past date is invalid", ...},
    {"delivery for tomorrow is valid", ...},
}

Python (pytest)

Python (pytest)

Functions must start with
test_
. Append the descriptive name in snake_case:
python
def test_delivery_with_a_past_date_is_invalid():
    ...

def test_new_customer_starts_in_pending_state():
    ...
函数必须以
test_
开头,后面追加snake_case格式的描述性名称:
python
def test_delivery_with_a_past_date_is_invalid():
    ...

def test_new_customer_starts_in_pending_state():
    ...

Java / Kotlin (JUnit)

Java / Kotlin (JUnit)

@Test
annotation handles discovery. Method names are fully descriptive:
java
@Test
void Delivery_with_a_past_date_is_invalid() { ... }

@Test
void New_customer_starts_in_pending_state() { ... }
Kotlin supports backtick-quoted names for natural language:
kotlin
@Test
fun `delivery with a past date is invalid`() { ... }
@Test
注解负责测试发现,方法名可以完全用描述性内容:
java
@Test
void Delivery_with_a_past_date_is_invalid() { ... }

@Test
void New_customer_starts_in_pending_state() { ... }
Kotlin支持反引号包裹的名称,可以实现自然语言命名:
kotlin
@Test
fun `delivery with a past date is invalid`() { ... }

JavaScript / TypeScript (Jest, Vitest, Mocha)

JavaScript / TypeScript (Jest, Vitest, Mocha)

String-based names — use natural language directly, no underscores needed:
javascript
it("delivery with a past date is invalid", () => { ... });

test("new customer starts in pending state", () => { ... });

describe("delivery validation", () => {
    it("rejects past dates", () => { ... });
    it("accepts future dates", () => { ... });
});
基于字符串的命名 —— 直接使用自然语言,不需要下划线:
javascript
it("delivery with a past date is invalid", () => { ... });

test("new customer starts in pending state", () => { ... });

describe("delivery validation", () => {
    it("rejects past dates", () => { ... });
    it("accepts future dates", () => { ... });
});

C# / .NET

C# / .NET

[Fact]
or
[Test]
attribute. Full freedom with underscores:
csharp
[Fact]
public void Delivery_with_a_past_date_is_invalid() { ... }
使用
[Fact]
[Test]
属性,命名完全自由,使用下划线分隔即可:
csharp
[Fact]
public void Delivery_with_a_past_date_is_invalid() { ... }

Rust

Rust

#[test]
attribute. Standard snake_case identifiers:
rust
#[test]
fn delivery_with_a_past_date_is_invalid() { ... }
使用
#[test]
属性,遵循标准snake_case命名规范:
rust
#[test]
fn delivery_with_a_past_date_is_invalid() { ... }

Test Class / File Naming

测试类/文件命名

Use
[ClassName]Tests
or
[feature]_test
as an entry point, not a boundary. The unit in unit testing is a unit of behavior, not a class — it can span multiple classes.
使用
[ClassName]Tests
[feature]_test
作为入口命名,而非边界限制。单元测试中的单元指的是行为单元,不是类单元,可以横跨多个类。

What to Test — Behavior, Not Implementation

测试范围:行为,而非实现

  • Test observable behavior: outputs, state changes, side effects visible to clients
  • Never test implementation details: internal collaborations, private methods, exact SQL queries, specific method call sequences
  • Renaming an internal method should never break a test
  • If a test fails during a legit refactoring, the test is coupled to implementation
  • 测试可观测的行为:输出、状态变更、对客户端可见的副作用
  • 永远不要测试实现细节:内部协作逻辑、私有方法、具体SQL查询、特定方法调用顺序
  • 重命名内部方法不应该导致测试失败
  • 如果合理的重构导致测试失败,说明该测试与实现逻辑耦合度过高

Testing Styles (in order of preference)

测试风格(按优先度排序)

  1. Output-based — feed input, verify output. Best false-positive resistance. Use for pure functions and domain logic.
  2. State-based — perform operation, verify resulting state via public API. Good when output verification isn't possible.
  3. Communication-based (mocks) — verify interactions with dependencies. Use ONLY for uncontrolled external dependencies (SMTP, message bus, third-party APIs). Never mock domain objects or stable dependencies.
For deeper guidance on testing styles, value proposition, and pragmatic testing strategies, see testing-philosophy.md.
For common anti-patterns to avoid, see anti-patterns.md.
  1. 基于输出 —— 输入参数,验证输出。抗误报能力最强,适用于纯函数和领域逻辑
  2. 基于状态 —— 执行操作后,通过公共API验证最终状态。适合无法验证输出的场景
  3. 基于通信(mocks) —— 验证与依赖的交互。仅适用于不受控的外部依赖(SMTP、消息总线、第三方API),永远不要mock领域对象或稳定的依赖
如需更深入的测试风格指导、价值主张和务实测试策略,请查看testing-philosophy.md
如需了解需要避免的常见反模式,请查看anti-patterns.md