【问题标题】:Initialize object to test in SetUp or during the test method?在 SetUp 中或在测试方法期间初始化要测试的对象?
【发布时间】:2015-08-27 02:33:14
【问题描述】:

我想知道要测试的对象是否应该是一个字段,并因此在 SetUp 方法期间设置(即 JUnit、nUnit、MS 测试……)。

考虑以下示例(这是带有 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);
    }
}

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

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

【问题讨论】:

    标签: unit-testing


    【解决方案1】:

    一旦您开始初始化字段并通常在测试方法本身设置测试的上下文,这是一个滑坡。这会导致大型测试方法和无法很好地解释自身的真正难以管理的夹具。

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

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

    OrderFulfillmentServiceTests.cs

    • with_an_order_from_a_new_customer

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

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

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

      • 它不应该检查信用
      • 应该免费添加加急运输

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

    【讨论】:

    • 是的,切换到这种测试风格确实有助于了解事情的发展方向。基本上,单元测试遵循与代码相同的规则:即单一职责。一景一试。
    【解决方案2】:

    第二种方法更具可读性,也更容易直观地追踪。

    但是,第一种方法意味着更少的重复。

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

    【讨论】:

      【解决方案3】:

      通过与 Kent Beck 讨论 jUnit 的设计,我知道测试类是在测试之间共享设置的一种方式,因此使用通用初始化是其目的。但是,除此之外,这意味着将需要不同设置的测试拆分为具有显着名称的单独测试类。

      【讨论】:

        【解决方案4】:

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

        1. 当所有测试都使用共同的启动逻辑并且在设置中创建的对象的单个实例旨在重复使用时,请使用设置和拆卸方法。
        2. 当在每个 TestMethod 中重复创建和销毁所创建的任何对象所需的时间足以减慢单元测试过程时,请使用 Setup 和 Teardown 方法。

        为了让您了解我在这些场景中运行的频率,在我现在正在处理的项目中,我的测试类中只有两个(大约 80 个)明确需要 Setup 和 Teardown 方法,两者由于我为每次测试执行启用了 10 秒的最大值,因此有时是为了满足我的第二个原因。

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

        【讨论】:

        • 在每个 TestMethod 之前都不会调用 SetUp 和 TearDown 吗?
        • 我的错。不知道我在哪里被误导了。谢谢你的澄清!
        【解决方案5】:

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

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

        【讨论】:

          【解决方案6】:

          在实践中,我发现设置方法很难推断失败的测试,并且必须滚动到文件顶部附近的某个位置(可能非常大)以找出协作者损坏了(不是易于模拟),并且在您的 IDE 中导航没有可点击的参考。简而言之,你失去了空间局部性。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-05-21
            • 2012-05-28
            • 2017-09-19
            • 1970-01-01
            • 2016-07-13
            相关资源
            最近更新 更多