简体   繁体   English

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

[英]How can I have unit tests for undefined behaviour?

I have a set of related classes which take a variety of inputs and produce expected outputs. 我有一组相关的类,它们接受各种输入并产生预期的输出。 These are ideal low-level candidates for unit testing, and all works well for valid inputs. 这些都是进行单元测试的理想低水平候选人,并且对于有效的输入都非常有效。

The difficulty comes with invalid inputs, particularly when attempting to remove items from collections which were not added, for which we currently have undefined behaviour: some of the classes will simply produce a rubbish result (GIGO 1 wins) but some will throw an exception (perhaps a KeyNotFoundException ). 困难来自于无效输入,尤其是在尝试从未添加的集合中删除项目时,当前我们尚不确定行为:某些类只会产生垃圾结果(GIGO 1获胜),而某些类会抛出异常(也许是KeyNotFoundException )。

Given that there is no valid, consistent behaviour for these invalid inputs (it means that something has been mis-configured elsewhere and no sensible results can be produced) and that our API explicitly states that the caller must only remove something which they added, how can this be reflected in our unit tests? 鉴于这些无效输入没有有效且一致的行为(这意味着某些东西在其他地方被错误配置,并且不会产生任何有意义的结果),并且我们的API明确声明调用者必须仅删除他们添加的内容,如何这可以反映在我们的单元测试中吗?

It clearly cannot be a "test", as there is no defined behaviour (simply recording our current behaviour will be fragile if our implementation of any of them should change in the future), but I want to have a way to preclude the possibility of some zealous team member adding one in the future without being aware of the potential problems. 显然,它不能作为“测试”,因为没有定义的行为(如果将来对其中任何一种的实现进行更改,则记录我们当前的行为将很脆弱),但是我想有一种方法来排除这种可能性一些热心的团队成员将来会增加一个,而不会意识到潜在的问题。

The unit test method for one of them currently looks something like this: 其中之一的单元测试方法当前看起来像这样:

[TestCase("1", "2", "1", ExpectedResult = "|2|")]
[TestCase("1", "2", "2", ExpectedResult = "|1|")]
public object InsertTwoDeleteOne(string insertedValue1,
                                 string insertedValue2,
                                 string deletedValue1)
{
    // Apply tests here
}

The two ways I can see to deal with this are either to add explicit code in the test method along the lines of: 我可以看到的两种处理方式是在测试方法中按照以下方式添加显式代码:

    if (deletedValue1 != insertedValue1 &&
        deletedValue1 != insertedValue2)
    {
        Assert.Fail("Invalid inputs");
    }

but that is "out of line" and less easy to see among the other test cases or else by adding a TestCase which is purely for documentation saying "don't run this", like this: 但这是“脱节的”,并且在其他测试用例中不那么容易看到,或者通过添加一个TestCase ,该TestCase纯粹用于说明“不要运行此文件”的文档,例如:

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

but that yields a "Skipped test" result which is untidy. 但这会产生“跳过的测试”结果,这是不完整的。

Is there anything better? 有更好的吗?


[Edit] The API in question is a public interface, and we have a number of implementations of it in our product: it is these implementations which I am in the process of updating the tests for. [编辑]所讨论的API是一个公共接口,我们在产品中有许多实现:我正在更新这些测试的过程中正是这些实现。 However, installations are free to write their own implementations as plugins (by creating their own assembly, implementing their own objects, and instantiating them through configuration), so our framework will ensure that the data is valid before calling them. 但是,安装可以自由地将自己的实现编写为插件(通过创建自己的程序集,实现自己的对象并通过配置实例化它们),因此我们的框架将确保在调用它们之前数据是有效的。

In our current model, it is unlikely that installations would re-use the objects and call them from their own code. 在我们当前的模型中,安装不太可能会重复使用对象并从自己的代码中调用它们。

The reason why we have chosen not to concern ourselves with validating the data in each object is twofold: 我们选择不考虑验证每个对象中数据的原因有两个:

  1. It will, in our default product configuration, always receive data which has already been validated by the caller. 在我们的默认产品配置中,它将始终接收已由调用方验证的数据。
  2. Performance: we are storing a lot of data here - currently limited to 100,000 rows of data (one of our objects per field in the row, so perhaps between 20 and 50 objects in total) but our customers are already asking about increasing that limit to 1,000,000 - so where we already store a dictionary of the data in our calling code so we can validate it there we would have to store a duplicate of it within these objects. 性能:我们在此处存储大量数据-当前限制为100,000行数据(该行中每个字段中有一个对象,因此总共可能有20到50个对象),但是我们的客户已经在要求将该限制提高到1,000,000-因此我们已经在调用代码中存储了数据的字典,因此我们可以在此处进行验证,我们将必须在这些对象中存储数据的副本。 That's between 20MB and 50MB if they are simply double s on current limits, or 200MB - 500MB on the projected future needs. 如果它们仅是当前限制的double则在20MB至50MB之间,或者在预计的未来需求上为200MB-500MB。

That's a massive overhead for something we don't currently need to do! 这是我们当前不需要做的事情的巨大开销!


1 Warning: Some people might prefer not to google for that in an office! 1警告:有些人可能不希望在办公室使用Google搜索!

It might depend on the project's criticality and quality standards, but my gut feeling is that you should normally not let "undefined behaviour" creep into your system, especially if "rubbish results" are produced. 这可能取决于项目的关键性和质量标准,但是我的直觉是通常不要让“不确定的行为”潜入您的系统中,尤其是在产生“垃圾结果”的情况下。

You say you fear that a zealous team member adds an inconsistent test to the suite. 您说您担心一个热心的团队成员会对该套件添加不一致的测试。 You might be assuming that team members will always add tests before writing production code and thus come across your "parapet" test, but what if they don't ? 您可能会假设团队成员将始终在编写生产代码之前添加测试,从而遇到“栏杆”测试,但是如果不这样做,该怎么办? Wouldn't the primary safety measure be to prevent them from using the API the wrong way in the first place (ie handling edge cases properly) ? 主要安全措施不是一开始就防止他们以错误的方式使用API (即正确处理边缘情况)吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM