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

C# 如何对未定义的行为进行单元测试?,c#,unit-testing,nunit,C#,Unit Testing,Nunit,我有一组相关的类,它们接受各种输入并产生预期的输出。这些都是单元测试的理想底层候选者,并且对于有效的输入都能很好地工作 困难来自无效的输入,特别是当试图从集合中删除未添加的项时,我们目前对这些项有未定义的行为:有些类只会产生垃圾结果(GIGO1获胜),但有些类会抛出异常(可能是KeyNotFoundException) 考虑到这些无效输入没有有效、一致的行为(这意味着在其他地方配置错误,无法产生合理的结果),并且我们的API明确声明调用方必须只删除他们添加的内容,这如何在我们的单元测试中反映出来

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

困难来自无效的输入,特别是当试图从集合中删除未添加的项时,我们目前对这些项有未定义的行为:有些类只会产生垃圾结果(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
}
[TestCase("1", "2", "3", Ignore = true, Reason = "Invalid inputs")]
我可以看到两种方法来处理这个问题,一种是在测试方法中添加显式代码,如下所示:

    if (deletedValue1 != insertedValue1 &&
        deletedValue1 != insertedValue2)
    {
        Assert.Fail("Invalid inputs");
    }
但这是“越轨”的,在其他测试用例中不太容易看到,或者通过添加一个
TestCase
,这纯粹是为了说明“不要运行这个”的文档,比如:

[TestCase("1", "2", "1", ExpectedResult = "|2|")]
[TestCase("1", "2", "2", ExpectedResult = "|1|")]
public object InsertTwoDeleteOne(string insertedValue1,
                                 string insertedValue2,
                                 string deletedValue1)
{
    // Apply tests here
}
[TestCase("1", "2", "3", Ignore = true, Reason = "Invalid inputs")]
但这会产生一个“跳过测试”的结果,这是不整洁的

还有更好的吗


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

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

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

  • 在我们的默认产品配置中,它将始终接收调用者已经验证过的数据
  • 性能:我们在这里存储了大量数据-目前限制为100000行数据(行中每个字段有一个对象,因此总共可能有20到50个对象)但是我们的客户已经要求将该限制提高到1000000—因此,如果我们已经在调用代码中存储了数据字典,那么我们就必须在这些对象中存储数据的副本,以便在那里进行验证。如果它们只是当前限制的两倍,或者是预计未来需求的200MB-500MB,那么这是在20MB到50MB之间
  • 对于我们目前不需要做的事情来说,这是一个巨大的开销



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

    这可能取决于项目的关键性和质量标准,但我的直觉是,您通常不应该让“未定义的行为”潜入您的系统,尤其是在产生“垃圾结果”的情况下


    你说你害怕一个热心的团队成员在套件中添加一个不一致的测试。您可能会假设团队成员总是在编写生产代码之前添加测试,从而遇到您的“女儿墙”测试,但如果他们没有呢?首要的安全措施不是首先防止他们以错误的方式使用API吗(即正确处理边缘情况)?

    为什么您的API没有为这些场景指定定义的行为?您希望从未定义的行为中获得什么好处?这种带有“无效输入”的测试可能很有用:您知道有多少API用户依赖于特定的“垃圾”输出吗?当事情得到纠正时,这样的测试会给你一个警报,你至少知道有人可能会抱怨——你可以在发行说明中写一个警告。这不是一个好主意,但我知道遗留代码的真实性。@Lilshieste这不是因为我们有未定义的行为,而是因为接口使得我们的调用代码只负责使用有效数据进行调用。请参阅我的编辑以获得更多的细节。@ BernhardHiller,你是说,我们应该考虑添加一个测试当前行为的测试用例,即使它在将来的某个时候可能会发生变化吗?谢谢更多的细节。如果这仅仅是一个内部API,那么假设输入是有效的似乎是一个合理的假设。(毕竟,修改代码要比修改客户的代码容易得多。)但由于这是一个公共API,所以非常危险。充其量,您可能会遇到依赖于获取特定垃圾值的客户。在最坏的情况下,您可能会受到安全漏洞的攻击。我对问题进行了编辑,以添加有关API的更多详细信息,以及为什么我们选择将负担强加给调用方。