【问题标题】:Is it bad practice to have unit tests run in a loop?让单元测试循环运行是不好的做法吗?
【发布时间】:2017-03-06 21:42:29
【问题描述】:

我正在使用 nunit 在 C# 中编写一些单元测试。考虑以下代码:

    [Test ()]
    public void TestCarCost () {
        for (int i = 0; i < Examples.exampleCount; i++) {
            Car car = new Car(Examples[i]);
            Assert.AreEqual (car.getCost (), Examples[i].cost, "Test " + (i + 1) + " failed");
        }
    }

假设 Examples 是一个包含一些静态数据的类,用于测试汽车类型的不同可能输入,如您所见,我正在尝试测试 car.getCost() 函数中的任何错误。现在让这个循环在某种程度上感觉是错误的,因为例如当任何断言失败时,它总是将您发送到同一行代码。此外,据我所知,只要 [Test ()] nunit 中的断言失败,就会立即终止其余的测试代码。这意味着如果我将所有内容都放在一个循环中并且断言 nr 1 失败,我将无法查看其他断言是否失败。显式编写所有测试本质上是编写复制粘贴的代码,因此感觉也不对。在这种情况下,什么被认为是好的做法?在单元测试中有大量类似的代码可以吗?是否有一些我缺少的优雅解决方案?

【问题讨论】:

  • 你可以在这里使用 NUnit 的TestCase 属性。 Example 这里是什么?

标签: c# unit-testing nunit


【解决方案1】:

NUnit 内置了对parameterized tests 的支持。如果您的示例数据很简单(即值可以出现在属性中,例如string),您的测试可以使用TestCase attributes,看起来像这样:

[TestCase("Example Value 1", ExpectedResult=123.4)]
[TestCase("Example Value 2", ExpectedResult=567.8)]
[TestCase("Example Value 3", ExpectedResult=901.2)]
public decimal TestCarCost (string exampleValue) {
    Car car = new Car(exampleValue);
    return car.GetCost();        
}

如果您的示例值(实际上是测试输入)不能出现在属性中,您可以使用TestCaseSource attribute 来指示将为您提供值的成员:

[TestCaseSource(nameof(Examples))]
public void TestCarCost (ExampleInput exampleValue) {
    Car car = new Car(exampleValue);
    return car.GetCost();        
}

public IEnumerable<ITestCaseData> Examples {
    get {
        yield return new TestCaseData(new ExampleInput(1,2)).Returns(123.4);
        yield return new TestCaseData(new ExampleInput(3,4)).Returns(567.8);
        yield return new TestCaseData(new ExampleInput(5,6)).Returns(901.2);
    }
}

【讨论】:

  • 我接受了另一篇文章,但您的回答也很有见地,谢谢。将来肯定会对我有用。
【解决方案2】:

最好使用TestCaseAttribute 代替循环。请在以下link阅读更多信息。

你的情况应该是:

[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
public void TestCarCost (int id) 
{           
    Car car = new Car(Examples[id]);
    Assert.AreEqual (car.getCost (), Examples[id].cost, "Test " + (i + 1) + " failed");

}

您可以在此处为单个测试方法创建多个测试用例。对于每个测试用例,您可以在TestCase() 属性中提供参数并作为id 传递给您的测试方法。

基于id,您可以访问您的资源,在您的情况下为“示例[id]”。

我宁愿使用这种方式,因为我可以监控每个案例,并且不需要额外的逻辑。

此外,如果您在单元测试中添加额外的逻辑,则会增加失败的机会,并且您将依赖该逻辑的准确性。

【讨论】:

    猜你喜欢
    • 2014-03-11
    • 2023-04-04
    • 1970-01-01
    • 2011-01-08
    • 2011-02-18
    • 2010-11-25
    • 1970-01-01
    • 2010-10-11
    相关资源
    最近更新 更多