初始化对象以在SetUp中或在测试方法期间进行测试?

时间:2008-11-19 15:33:59

标签: unit-testing

我想知道要测试的对象是否应该是一个字段,因此在SetUp方法(即JUnit,nUnit,MS Test,...)中进行设置。

考虑以下示例(这是带有MsTest的C♯,但对于任何其他语言和测试框架,这个想法应该类似):

public class SomeStuff
{
    public string Value { get; private set; }

    public SomeStuff(string value)
    {
        this.Value = value;
    }
}


[TestClass]
public class SomeStuffTestWithSetUp
{
    private string value;
    private SomeStuff someStuff;

    [TestInitialize]
    public void MyTestInitialize()
    {
        this.value = Guid.NewGuid().ToString();
        this.someStuff = new SomeStuff(this.value);
    }

    [TestCleanup]
    public void MyTestCleanup()
    {
        this.someStuff = null;
        this.value = string.Empty;
    }

    [TestMethod]
    public void TestGetValue()
    {
        Assert.AreEqual(this.value, this.someStuff.Value);
    }
}

[TestClass]
public class SomeStuffTestWithoutSetup
{
    [TestMethod]
    public void TestGetValue()
    {
        string value = Guid.NewGuid().ToString();
        SomeStuff someStuff = new SomeStuff(value);
        Assert.AreEqual(value, someStuff.Value);
    }
}

当然,只有一种测试方法,第一个例子太长了,但是有了更多的测试方法,这可能是一些安全的冗余代码。

每种方法的优缺点是什么?有没有“最佳实践”?

6 个答案:

答案 0 :(得分:8)

一旦你开始初始化田地,这是一个滑坡通常在测试方法本身中设置测试的上下文。这导致了大量的测试方法和真正无法管理的固定装置,这些装置无法很好地解释自己。

相反,你应该看看BDD风格的命名和放大测试组织。每个上下文制作一个夹具,而不是每个被测系统一个夹具。然后你的[setup]确实设置了上下文,你的测试可以是简单的单线断言。

当您看到执行此操作的测试输出时,它会更容易阅读:

OrderFulfillmentServiceTests.cs

  • with_an_order_from_a_new_customer

    • 应该从信用服务中检查他们的信用
    • 它应该没有折扣
  • 有效的信用检查

    • 它应该减少库存
    • 它应该运送货物
  • 与德克萨斯州或加利福尼亚州的客户

    • 应该添加适当的销售税
  • 来自黄金客户的订单

    • 不应该检查信用
    • 应免费加入免费送货

我们的测试现在是我们系统的非常好的文档。每个“with_an ...”都是一个测试夹具,下面的项目是测试。在这些内容中,您可以设置上下文(类名描述的世界状态),然后测试执行简单的断言,验证方法名称的作用。

答案 1 :(得分:1)

第二种方法更易读,更容易直观地跟踪。

然而,第一种方法意味着更少的重复。

我发现我倾向于使用SetUp来创建对象(特别是对于具有许多依赖项的东西),然后设置测试本身使用的值。根据经验,这提供了适当数量的代码重用与可读性/可追溯性。

答案 2 :(得分:1)

通过与Kent Beck谈论jUnit的设计,我知道测试类是一种在测试之间共享设置的方法,因此使用常见的初始化是意图。然而,与此同时,这意味着将需要不同设置的测试拆分为具有显示名称的单独测试类。

答案 3 :(得分:0)

就个人而言,我使用Setup和Teardown方法有两个不同的原因,尽管我认为其他人会有不同的原因。

  1. 当存在所有测试使用的公共启动逻辑时,使用Setup和Teardown方法,并且设计中创建的对象的单个实例可以重复使用。
  2. 当创建和销毁所创建的任何对象所花费的时间花费足够的时间来减慢每个TestMethod中重复的单元测试过程时,使用Setup和Teardown方法。
  3. 为了让您了解我在这些场景中运行的频率,在我正在进行的项目中,只有两个测试类(大约八十个)明确需要Setup和Teardown方法,两者都是时间是为了满足我的第二个原因,因为我已经为每次测试执行启用了10秒钟。

    我也更喜欢在TestMethod中创建和销毁对象的可读性,尽管它对我来说不是一个突破或卖点。

答案 4 :(得分:0)

我采取的方法是在中间的某个地方 - 我使用TearDown和SetUp创建一个测试“沙盒”目录(并在完成后删除它),以及使用一些默认值初始化一些测试成员变量用于测试类。然后我设置了一些“辅助方法” - 一个通常称为InstantiateClass()我用它来调用默认参数(如果有的话),我可以在每个显式测试中根据需要覆盖它。

[Test]
public void TestSomething()
{
    _myVar = "value";
    InstantiateClass();
    RunTheClass();
    Assert.IsTrue(this, that);
}

答案 5 :(得分:0)

在实践中,我发现设置方法很难推断出一个失败的测试并且必须滚动到文件顶部附近的某个地方(可能非常大)来弄清楚协作者已经破坏了什么(不是使用模拟很容易)并且没有可点击的引用来在IDE中导航。简而言之,你失去了空间位置。

静态辅助方法更明确地揭示了协作者,并且避免了不必要地扩大变量范围的字段。