我正在尝试将一些按合同设计的技术融入到我的编码风格中。后置条件对我来说很重要,就像嵌入式单元测试一样,我想知道我的思路是在正确的轨道还是偏离基础。
维基百科将后置条件定义为“在执行某些代码段之后或在正式规范中执行操作之后必须始终为真的条件或谓词。后置条件有时使用代码本身中的断言进行测试”。
这与您在直接验证状态(不使用模拟)的单元测试中所做的非常相似吗?
如果是这样的话:
1)通过使用后置条件,我现在不是在我的生产代码中嵌入测试代码,而不是不赞同吗?
2)是否应该使用后置条件来改变单元测试的结构?我的第一个想法是断言逻辑从测试转移到后置条件。也就是说,测试将使用相同的输入,我仍然在测试我之前测试的所有内容,但现在不是在单元测试中进行断言,而是在做一个关于后置条件传递的简单二元断言。
3)我的第二个想法是后置条件代码可能具有控制流,因此不适用于测试代码,测试代码应该很简单并且避免控制流。但是,如果我测试后置条件,我可以在单元测试中依赖它们吗?
4)测试后置条件似乎很困难,因为如果我正确理解它们,它们基本上会通过或失败,你必须重复后置条件本身的逻辑来检查它是否做对了。那么,你如何测试后置条件?你是否在单元测试中没有使用它们来检查它们,并确保你的单元测试和后置条件一起通过或失败?
5)我的单元测试有时会验证方法是否导致协作者状态发生变化。在标准实践中,后置条件是否涵盖协作者状态或仅定义它们所定义的类的状态?
答案 0 :(得分:4)
你走在正确的轨道上。
事实上,后置条件与单元测试的目的相似。关键区别在于后置条件总是运行,而单元测试仅针对一组已知数据运行。这意味着后置条件不太可能忽略你没想到的角落情况,但在运行时更加昂贵。
以下是您具体问题的答案。
后置条件存在运行时间惩罚。但是(取决于您的环境),可能会丢弃断言以提高速度。 (在C中,您可以使用#ifdef,在Java中查找AOP,在Python中assert
中的任何内容只有在您传递--debug标志等时才会运行。)如果您的断言出现性能问题,它是可以解决的。但是,我倾向于让它们保持开启,直到你有理由不这样做。
你的一些逻辑自然会从单元测试转移到后置条件。但是,确保您的单元测试能够贯穿您的后置条件所需的所有案例,这是值得的。如果您为生产速度放弃断言,那么特别为真。
后置条件不是单元测试。用任何对他们所做的事情有意义的方式写出来。 (一般来说,它们应该有点简单。)
一般情况下,您可以通过传入一组可能违反后置条件的感兴趣的输入来测试#2中描述的后置条件,并检查它是否已被违反。如果你想测试后置条件本身的逻辑,那么你可以设置可以违反后置条件的代码,但只能在测试期间运行。例如,有一个全局变量,测试可以设置哪个(如果已设置)用您想要的任何内容替换要返回的数据。现在,您可以使后置条件接收您想要的任何输入。
我不打算给你一个严格的规则。他们是你的合同。他们应该说出功能正在做什么才有意义。也就是说,您所描述的内容可能导致这些对象之间的紧密耦合。紧耦合是你应该有充分理由做的事情。
答案 1 :(得分:2)
合同不是单元测试的一种形式。相反,它们是一种指定(以可执行格式)在调用特定函数或方法之前和之后应该保持什么条件的方式,并且还可以指定对象的不变量。
当你有合同时,你仍然需要测试,因为你已经指定了这些功能应该做什么并不意味着他们真的会这样做。但是你会发现你的合同会帮助你调试 - 因为有代码可以检查运行时发生的事情是什么意味着任何逻辑或编程错误都会导致包含错误的代码附近的失败
您可能会发现,对于合同,您很乐意减少较小的测试和更大规模的测试,因为即使测试范围很广,合同也会让您缩小错误来源。此外,单元测试不再需要发挥逻辑运算规范的作用,进一步限制了较小测试的价值。
合同就像断言一样,您可以选择或选择不在生产代码中启用它们。我的意见是合同往往比断言更昂贵,所以你会倾向于让他们在生产中被禁用。
答案 2 :(得分:0)
与任何方法或编码风格一样,没有一个正确的答案。然而,到目前为止我发现的一件事是,从来没有“一刀切”的解决方案。
因此,如果您将这些断言实现为设计中每个后置条件的逻辑,我认为这是错误的。
我自己的观点是,只有在不满足后置条件导致整个系统处于危险的不一致状态时,才应使用此类断言。因此,如果发生类似的情况,我肯定希望系统能够执行以下操作:向管理员发送电子邮件/短信,停止生产执行,运行诊断程序或应该为该特定系统执行的任何操作。请注意,这将是一个实际功能,其目的是提高安全性,它不是单元测试代码。
另一方面,如果您在每次调用方法之后编写断言,那么您只注意到您正在做的事情是将测试用例硬编码到生产代码中。除了让您的代码库变得一团糟之外,这不会产生任何实际目的。