在SetUp方法中还是在测试方法中初始化对象进行测试?

5

我在想测试的对象是否应该是一个字段,因此在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个回答

8
一旦您开始在测试方法内初始化字段并通常设置测试上下文,情况就会变得棘手。这会导致大型测试方法和非常难以管理的夹具,无法很好地解释自己。
相反,您应该查看BDD样式的命名和测试组织。每个上下文一个夹具,而不是每个系统一个夹具。然后,您的[setup]确实设置了上下文,您的测试可以是简单的一行断言。
当您看到这样的测试输出时,它更容易阅读:
OrderFulfillmentServiceTests.cs
- 对于来自新客户的订单 - 应该从信用服务中检查他们的信用 - 不应该给予折扣 - 具有有效信用检查 - 应该减少库存 - 应该发货 - 具有德克萨斯州或加利福尼亚州客户 - 应添加适当的销售税 - 具有金牌客户的订单 - 不应该检查信用 - 应该免费添加快速送货
我们的测试现在是我们系统的真正好的文档。每个“with_an…”都是一个测试夹具,下面的项目是测试。在其中,您设置上下文(类名描述的世界状态),然后测试执行简单的断言,验证方法名称所说的内容。

是的,转换到这种测试方式确实有助于代码的结构。基本上,单元测试遵循与代码相同的原则,即单一职责。一个测试针对一个场景。 - kͩeͣmͮpͥ ͩ

1

第二种方法更易读,也更容易进行视觉追踪。

然而,第一种方法意味着减少了重复。

我发现我倾向于使用SetUp来创建对象(特别是对于具有多个依赖项的事物),然后在测试本身中设置使用的值。从经验上看,这提供了适量的代码重用与可读性/可追溯性。


1
从与肯特·贝克讨论jUnit设计的谈话中,我知道测试类是一种在测试之间共享设置的方法,因此使用常见初始化是意图。然而,这也意味着将需要不同设置的测试拆分为具有透露名称的单独测试类。

0
实际上,我发现设置方法使得很难推断出测试失败的原因,并且必须滚动到文件的顶部附近(可能非常大)才能弄清楚哪个协作者已经出了问题(使用模拟不容易),而且没有可点击的引用来在IDE中导航。简而言之,您会失去空间局部性。
静态辅助方法更明确地显示协作者,并避免不必要地扩大变量范围的字段。

0

就我个人而言,我使用设置(Setup)和拆卸(Teardown)方法有两个不同的原因,尽管我认为其他人可能会有不同的原因。

  1. 当所有测试都使用共同的初始化逻辑,并且在设置中创建的对象的单个实例旨在被重复使用时,使用设置和拆卸方法。
  2. 当创建和销毁任何对象所需的时间足够长,以至于在每个TestMethod中重复执行会减慢单元测试过程时,使用设置和拆卸方法。

为了让您了解我遇到这些情况的频率,我现在正在工作的项目中,我的大约80个测试类中只有两个需要显式使用设置和拆卸方法,这两次都是为了满足我的第二个原因,因为我启用了每个测试执行的10秒最大值。

我还喜欢在TestMethod内部创建和销毁对象的可读性,尽管这对我来说并不是一个破坏或卖点。


SetUp和TearDown不是在每个TestMethod之前都会被调用吗? - kͩeͣmͮpͥ ͩ
我的错。不知道我在哪里得到了错误的信息。谢谢你的澄清! - Joseph Ferris

0
我所采取的方法介于两者之间 - 我使用 TearDown 和 SetUp 来创建一个测试用“沙盒”目录(并在完成后将其删除),以及初始化一些测试成员变量,并使用一些默认值来测试类。然后我设置了一些“辅助方法” - 其中一个通常被称为 InstantiateClass(),我使用它来调用默认参数(如果有的话),我可以在每个显式测试中根据需要覆盖这些参数。
[Test]
public void TestSomething()
{
    _myVar = "value";
    InstantiateClass();
    RunTheClass();
    Assert.IsTrue(this, that);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接