You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NUnit的Setup属性是否为代码异味?共享初始化代码该如何处理?

平衡SetUp/TearDown与测试可读性的实用建议

Great question—this is such a common tension between keeping tests maintainable (via DRY) and making them self-documenting, so you’re not alone in wrestling with this!

Let’s break this down:

First, acknowledge both sides of the argument

  • James Newkirk’s point about scrolling is totally valid: when reading a test, you shouldn’t have to jump back and forth to the SetUp method to understand what the starting state is. Tests should ideally be self-contained so anyone can read a single test method and grasp its context quickly.
  • But your DRY concern is also spot-on: repeating the same initialization code across every test is messy, hard to update, and invites typos. No one wants to change a Product constructor parameter in 10 different test methods!

So what’s the middle ground?

It depends on your specific test suite, but here are some practical approaches:

1. Keep SetUp if all tests truly share identical, unmodified state

If every single test in your class uses _product1, _product2, and _products in exactly their default initialized state (no test modifies these objects), then SetUp is perfectly reasonable. To mitigate the readability issue:

  • Give your fields clear, descriptive names (you’re already doing this—great!)
  • Add a brief comment above the fields to explain their default state, so readers don’t have to jump to SetUp:
    // Initialized in SetUp: two default Product instances in a list
    private Product _product1;
    private Product _product2;
    private IList<Product> _products;
    

2. Use factory methods for flexible, readable initialization

If some tests only need a subset of these objects, or if tests modify the state of the objects (e.g., setting a Product’s Price), ditching SetUp for factory methods is a better call. This keeps DRY while making each test’s context explicit:

// Reusable factory methods
private Product CreateDefaultProduct() => new Product();

private IList<Product> CreateDefaultProductList()
{
    var product1 = CreateDefaultProduct();
    var product2 = CreateDefaultProduct();
    return new List<Product> { product1, product2 };
}

// Example test
[Test]
public void AddingProduct_IncreasesListCount()
{
    var products = CreateDefaultProductList();
    var newProduct = CreateDefaultProduct();
    
    products.Add(newProduct);
    
    Assert.That(products.Count, Is.EqualTo(3));
}

Now anyone reading the test can immediately see exactly what objects are being used, without scrolling to SetUp.

3. Avoid shared state that changes between tests

Even though NUnit creates a new instance of your test class for every test (so SetUp runs fresh each time), if your tests modify the shared objects, it can still create confusion for readers. If a test needs a modified Product (e.g., with a specific ID), initialize that directly in the test or create a specialized factory method like CreateProductWithId(int id).

Final takeaway

There’s no one-size-fits-all answer, but the priority should be test readability first, DRY second. Tests act as living documentation for your code, so making them easy to understand should outweigh avoiding a little duplication. That said, factory methods are usually the sweet spot—they keep code DRY while keeping each test self-contained.

内容的提问来源于stack exchange,提问作者w0051977

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:13:16