如何对未定义行为进行单元测试?

本文关键字:单元测试 未定义 | 更新日期: 2023-09-27 18:11:50

我有一组相关的类,它们接受各种输入并产生预期的输出。这些都是单元测试的理想的低级别候选,并且对于有效的输入都能很好地工作。

困难来自无效的输入,特别是当试图从未添加的集合中删除项时,我们目前有未定义的行为:一些类会简单地产生垃圾结果(GIGO1获胜),但有些会抛出异常(可能是KeyNotFoundException)。

假设对于这些无效的输入没有有效的、一致的行为(这意味着某些东西在其他地方被错误地配置了,并且不能产生合理的结果),并且我们的API明确地声明调用者必须只删除他们添加的东西,这如何在我们的单元测试中反映出来?

它显然不能是一个"测试",因为没有定义的行为(简单地记录我们当前的行为将是脆弱的,如果我们的任何实现在未来应该改变),但我想有一种方法来排除一些热心的团队成员在没有意识到潜在问题的情况下在未来添加一个的可能性。

其中一个的单元测试方法目前看起来像这样:

[TestCase("1", "2", "1", ExpectedResult = "|2|")]
[TestCase("1", "2", "2", ExpectedResult = "|1|")]
public object InsertTwoDeleteOne(string insertedValue1,
                                 string insertedValue2,
                                 string deletedValue1)
{
    // Apply tests here
}
我可以看到处理这个问题的两种方法是在测试方法中沿着以下行添加显式代码:
    if (deletedValue1 != insertedValue1 &&
        deletedValue1 != insertedValue2)
    {
        Assert.Fail("Invalid inputs");
    }

,但这是"越界",在其他测试用例中不太容易看到,或者通过添加纯粹用于文档的TestCase说"不要运行这个",像这样:

[TestCase("1", "2", "3", Ignore = true, Reason = "Invalid inputs")]

但是这会产生一个不整洁的"跳过测试"结果。

有更好的吗?


[编辑]有问题的API是一个公共接口,我们的产品中有许多它的实现:我正在为这些实现更新测试。然而,安装可以自由地将它们自己的实现写成插件(通过创建它们自己的程序集,实现它们自己的对象,并通过配置实例化它们),因此我们的框架将在调用它们之前确保数据是有效的。

在我们当前的模型中,安装不太可能重用对象并从自己的代码中调用它们。

我们选择不去验证每个对象中的数据的原因有两个:

  1. 在我们的默认产品配置中,它将始终接收已被调用者验证的数据。
  2. 性能:我们在这里存储大量的数据,目前仅限于100000行数据(我们的一个对象中的每个字段行,所以也许在20到50之间对象总共)但是我们的客户已经问增加限制到1000000,所以我们已经将字典的数据存储在调用代码我们可以验证它我们必须存储在这些对象的复制。这是20MB和50MB之间,如果他们是简单的double s在当前的限制,或200MB - 500MB在预计的未来需求。

对于我们目前不需要做的事情来说,这是一个巨大的开销!


1警告:有些人可能不喜欢在办公室里用谷歌搜索!

如何对未定义行为进行单元测试?

这可能取决于项目的临界性和质量标准,但我的直觉是,您通常不应该让"未定义行为"潜入您的系统,特别是在产生"垃圾结果"的情况下。

您说您担心热心的团队成员向套件中添加不一致的测试。您可能会假设团队成员总是在编写产品代码之前添加测试,从而遇到您的"胸墙"测试,但是如果他们不这样做呢?首要的安全措施难道不是首先防止他们以错误的方式使用API(即正确处理边缘情况)吗?