clean-tests

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clean Tests

整洁测试

T1: Insufficient Tests

T1: 测试覆盖不足

Test everything that could possibly break. Use coverage tools as a guide, not a goal.
python
undefined
测试所有可能出现问题的场景。将覆盖率工具作为参考,而非目标。
python
undefined

Bad - only tests happy path

不良示例 - 仅测试正常路径

def test_divide(): assert divide(10, 2) == 5
def test_divide(): assert divide(10, 2) == 5

Good - tests edge cases too

良好示例 - 同时测试边缘场景

def test_divide_normal(): assert divide(10, 2) == 5
def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): divide(10, 0)
def test_divide_negative(): assert divide(-10, 2) == -5
undefined
def test_divide_normal(): assert divide(10, 2) == 5
def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): divide(10, 0)
def test_divide_negative(): assert divide(-10, 2) == -5
undefined

T2: Use a Coverage Tool

T2: 使用覆盖率工具

Coverage tools report gaps in your testing strategy. Don't ignore them.
bash
undefined
覆盖率工具可报告测试策略中的漏洞,不要忽略这些结果。
bash
undefined

Run with coverage

结合覆盖率工具运行测试

pytest --cov=myproject --cov-report=term-missing
pytest --cov=myproject --cov-report=term-missing

Aim for meaningful coverage, not 100%

追求有意义的覆盖率,而非100%覆盖率

undefined
undefined

T3: Don't Skip Trivial Tests

T3: 不要跳过简单测试

Trivial tests document behavior and catch regressions. They're worth more than their cost.
python
undefined
简单测试可记录预期行为并捕获回归问题,其价值远高于编写成本。
python
undefined

Worth having - documents expected behavior

值得编写 - 记录预期行为

def test_user_default_role(): user = User(name="Alice") assert user.role == "member"
undefined
def test_user_default_role(): user = User(name="Alice") assert user.role == "member"
undefined

T4: An Ignored Test Is a Question About an Ambiguity

T4: 被忽略的测试意味着存在模糊性问题

Don't use
@pytest.mark.skip
to hide problems. Either fix the test or delete it.
python
undefined
不要使用
@pytest.mark.skip
来掩盖问题。要么修复测试,要么删除它。
python
undefined

Bad - hiding a problem

不良示例 - 掩盖问题

@pytest.mark.skip(reason="flaky, fix later") def test_async_operation(): ...
@pytest.mark.skip(reason="flaky, fix later") def test_async_operation(): ...

Good - either fix it or document why it's skipped

良好示例 - 要么修复,要么明确说明跳过原因

@pytest.mark.skip(reason="Requires Redis, see CONTRIBUTING.md for setup") def test_cache_invalidation(): ...
undefined
@pytest.mark.skip(reason="Requires Redis, see CONTRIBUTING.md for setup") def test_cache_invalidation(): ...
undefined

T5: Test Boundary Conditions

T5: 测试边界条件

Bugs congregate at boundaries. Test them explicitly.
python
def test_pagination_boundaries():
    items = list(range(100))
    
    # First page
    assert paginate(items, page=1, size=10) == items[0:10]
    
    # Last page
    assert paginate(items, page=10, size=10) == items[90:100]
    
    # Beyond last page
    assert paginate(items, page=11, size=10) == []
    
    # Page zero (invalid)
    with pytest.raises(ValueError):
        paginate(items, page=0, size=10)
    
    # Empty list
    assert paginate([], page=1, size=10) == []
漏洞往往集中在边界场景,需明确测试这些场景。
python
def test_pagination_boundaries():
    items = list(range(100))
    
    # 第一页
    assert paginate(items, page=1, size=10) == items[0:10]
    
    # 最后一页
    assert paginate(items, page=10, size=10) == items[90:100]
    
    # 超出最后一页
    assert paginate(items, page=11, size=10) == []
    
    # 第0页(无效)
    with pytest.raises(ValueError):
        paginate(items, page=0, size=10)
    
    # 空列表
    assert paginate([], page=1, size=10) == []

T6: Exhaustively Test Near Bugs

T6: 针对已发现漏洞的相似场景进行全面测试

When you find a bug, write tests for all similar cases. Bugs cluster.
python
undefined
当发现一个漏洞时,为所有相似场景编写测试。漏洞往往会集中出现。
python
undefined

Found bug: off-by-one in date calculation

发现漏洞:日期计算中的差一错误

Now test ALL date boundaries

现在测试所有日期边界场景

def test_month_boundaries(): assert last_day_of_month(2024, 1) == 31 # January assert last_day_of_month(2024, 2) == 29 # Leap year February assert last_day_of_month(2023, 2) == 28 # Non-leap February assert last_day_of_month(2024, 4) == 30 # 30-day month assert last_day_of_month(2024, 12) == 31 # December
undefined
def test_month_boundaries(): assert last_day_of_month(2024, 1) == 31 # 一月 assert last_day_of_month(2024, 2) == 29 # 闰年二月 assert last_day_of_month(2023, 2) == 28 # 非闰年二月 assert last_day_of_month(2024, 4) == 30 # 30天的月份 assert last_day_of_month(2024, 12) == 31 # 十二月
undefined

T7: Patterns of Failure Are Revealing

T7: 测试失败的模式具有参考价值

When tests fail, look for patterns. They often point to deeper issues.
python
undefined
当测试失败时,寻找其中的模式,它们通常指向更深层次的问题。
python
undefined

If all async tests fail intermittently,

如果所有异步测试都间歇性失败,

the problem isn't the tests—it's the async handling

问题不在测试本身——而是异步处理逻辑有问题

undefined
undefined

T8: Test Coverage Patterns Can Be Revealing

T8: 测试覆盖率模式具有参考价值

Look at which code paths are untested. Often they reveal design problems.
python
undefined
查看哪些代码路径未被测试,它们通常会暴露设计问题。
python
undefined

If you can't easily test a function, it probably does too much

如果某个函数难以测试,它可能承担了过多职责

Refactor for testability

需重构以提升可测试性

undefined
undefined

T9: Tests Should Be Fast

T9: 测试应快速执行

Slow tests don't get run. Keep unit tests under 100ms each.
python
undefined
缓慢的测试不会被频繁运行。确保每个单元测试的执行时间在100ms以内。
python
undefined

Bad - hits real database

不良示例 - 连接真实数据库

def test_user_creation(): db = connect_to_database() # Slow! user = db.create_user("Alice") assert user.name == "Alice"
def test_user_creation(): db = connect_to_database() # 速度慢! user = db.create_user("Alice") assert user.name == "Alice"

Good - uses mock or in-memory

良好示例 - 使用模拟或内存数据库

def test_user_creation(): db = InMemoryDatabase() user = db.create_user("Alice") assert user.name == "Alice"
undefined
def test_user_creation(): db = InMemoryDatabase() user = db.create_user("Alice") assert user.name == "Alice"
undefined

Test Organization

测试组织规范

F.I.R.S.T. Principles

F.I.R.S.T. 原则

  • Fast: Tests should run quickly
  • Independent: Tests shouldn't depend on each other
  • Repeatable: Same result every time, any environment
  • Self-Validating: Pass or fail, no manual inspection
  • Timely: Written before or with the code, not after
  • Fast:测试应快速运行
  • Independent:测试之间不应存在依赖关系
  • Repeatable:在任何环境下,每次运行都能得到相同结果
  • Self-Validating:通过或失败,无需人工检查
  • Timely:在代码编写之前或同时编写测试,而非之后

One Concept Per Test

每个测试仅验证一个概念

python
undefined
python
undefined

Bad - testing multiple things

不良示例 - 同时测试多个内容

def test_user(): user = User("Alice", "alice@example.com") assert user.name == "Alice" assert user.email == "alice@example.com" assert user.is_valid() user.activate() assert user.is_active
def test_user(): user = User("Alice", "alice@example.com") assert user.name == "Alice" assert user.email == "alice@example.com" assert user.is_valid() user.activate() assert user.is_active

Good - one concept each

良好示例 - 每个测试仅验证一个概念

def test_user_stores_name(): user = User("Alice", "alice@example.com") assert user.name == "Alice"
def test_user_stores_email(): user = User("Alice", "alice@example.com") assert user.email == "alice@example.com"
def test_new_user_is_valid(): user = User("Alice", "alice@example.com") assert user.is_valid()
def test_user_can_be_activated(): user = User("Alice", "alice@example.com") user.activate() assert user.is_active
undefined
def test_user_stores_name(): user = User("Alice", "alice@example.com") assert user.name == "Alice"
def test_user_stores_email(): user = User("Alice", "alice@example.com") assert user.email == "alice@example.com"
def test_new_user_is_valid(): user = User("Alice", "alice@example.com") assert user.is_valid()
def test_user_can_be_activated(): user = User("Alice", "alice@example.com") user.activate() assert user.is_active
undefined

Quick Reference

快速参考

RulePrinciple
T1Test everything that could break
T2Use coverage tools
T3Don't skip trivial tests
T4Ignored test = ambiguity question
T5Test boundary conditions
T6Exhaustively test near bugs
T7Look for patterns in failures
T8Check coverage when debugging
T9Tests must be fast (<100ms)
规则原则
T1测试所有可能出现问题的场景
T2使用覆盖率工具
T3不要跳过简单测试
T4被忽略的测试 = 存在模糊性问题
T5测试边界条件
T6针对已发现漏洞的相似场景进行全面测试
T7寻找测试失败中的模式
T8调试时检查覆盖率
T9测试必须快速(<100ms)