单元测试 - 测试用例与多种方法

Unit Testing - Tests Cases vs. Multiple Methods

在为下面的示例编写单元测试时,最好的做法是使用 TestCases(例如在 NUnit 中),还是编写多个测试来验证功能?

假设您要测试下面的 RetrieveContact 方法 - 它只是在数组中找到相应的联系人,如果找到则 return 返回结果,否则 return 为空。

public class Contact
{
    public string Name { get; set; }
}

private static Contact[] Contacts =
{
    new Contact() { Name = "Jim" },
    new Contact() { Name = "Bob" },
    new Contact() { Name = "Tom" }
};

public static Contact RetrieveContact(string name)
{
    return Contacts.FirstOrDefault(c => c.Name == name);
}

你会用一种方法测试这个吗,使用 TestCases,如下所示?

[TestCase("Bob", "Bob")]
[TestCase(null, "ZZZ")]
public void Test_RetrievesFromContacts(string expectedName, string name)
{
    var ret = RetrieveContact(name);
    Assert.AreEqual(expectedName, ret?.Name);
}

或者您会编写两个单独的测试 - 一个用于有效联系人,一个用于无效联系人 returning null?

[Test]
public void Test_RetrieveValidContact()
{
    var ret = RetrieveContact("Bob");
    Assert.AreEqual("Bob", ret.Name);
}

[Test]
public void Test_RetrieveInvalidContact()
{
    var ret = RetrieveContact("ZZZ");
    Assert.AreEqual(null, ret);
}

谢谢

JUnit 中的约定是每个 unit 测试都应该测试一个特定的功能,一个通过被测代码的不同路径。此约定强烈暗示您不应将多个不相关的测试合并到一个测试方法中。

因此,在上面的示例中,您将为以下各项编写一个测试:

  • 有效联系人一个
  • 一个用于无效联系人

这样做的理由是:

  • 如果将上述两条测试路径组合成一个测试,那么第一条路径中的失败可能会阻止第二条路径运行(从而降低测试覆盖率)
  • 在所有其他条件相同的情况下,细粒度的测试用例可能更简洁易读,即不太可能受到 if firstPathFails then log and move on to second path 等条件性的影响
  • 如果您对 "invalid contacts" 的处理发生变化,则不必更改解决快乐路径的测试用例(在您的情况下为 "valid contacts")。这是对 SRP.
  • 的引用

备注:

  • 如果测试用例中的多个相关方法需要共享设置代码,则可以在 @Before 方法中实现,从而减少每个 @Test 方法的 size/reposibilities。
  • 上述约定并不意味着单个测试方法只能有一个断言。
  • 上述约定不一定适用于粗粒度测试,例如集成测试,其中可能会测试应用程序的整个部分。

尽管您用 junit、xunit 和 nunit 标记了 post,但代码显然是 NUnit。已经 posted 的面向 junit 的答案对于 junit 无疑是正确的,但我将重点关注您正在使用的框架和语言。

你给出的两个例子在NUnit中是完全等价的。在参数化方法中,每个测试用例作为独立于另一个的单独测试运行。每个结果单独报告。

NUnit 的 TestCaseAttribute 只不过是创建多个测试的语法快捷方式,它们仅在使用的值上有所不同。您使用哪种方法是风格和命名偏好的问题。 TestCase 方法的使用在我看来更能吸引可能决定添加新数据点的其他程序员。

如果在某些情况下您要使用的数据类型在 C# 中不允许作为属性的构造函数参数,您还应该考虑使用 TestCaseSourceAttribute

我对您的特定用例持保留意见。当 "failure" 案例只是 returns null 且没有其他副作用时,它工作正常。然而,在大多数真实场景中,失败案例会抛出异常、生成错误报告等。一般来说,对于一个或多个失败案例,通常最好使用一个完全独立的方法。