有测试来支持和确保防御性编程
防御性编程可在运行时保护系统的完整性。
测试是(通常是静态的)诊断工具。在运行时,您的测试遥遥无期。它们就像用来搭建高砖墙或岩石圆顶的脚手架。您不会在结构中留下重要部分,因为在施工过程中有脚手架将其支撑起来。在施工过程中,您有一个脚手架将其支撑起来,以方便放入所有重要的零件。
编辑:一个比喻
类似于代码中的注释呢?
评论有其目的,但可能多余,甚至有害。例如,如果您将有关代码的内在知识放入注释中,然后更改代码,则注释最多变得无关紧要,最坏时变得有害。
因此,说您在测试中投入了很多代码基础知识,例如MethodA不能为null,而MethodB的参数必须为> 0
。然后代码更改。现在对于A来说可以为Null,而B可以取小到-10的值。现有的测试现在在功能上是错误的,但将继续通过。
是的,您应该在更新代码的同时更新测试。您还应该在更新代码的同时更新(或删除)注释。但是我们都知道这些事情并不总是会发生,并且会犯错误。
这些测试可以验证系统的行为。实际行为是系统本身固有的,而不是测试固有的。
可能出什么问题了?
有关测试的目标是考虑所有可能出问题的地方,为其编写测试以检查正确的行为,然后编写运行时代码,使其通过所有测试。
这意味着防御性编程才是重点。
如果测试很全面,TDD会推动防御性编程。
更多测试,推动更具防御性的编程
当不可避免地发现错误时,将编写更多测试以对表现该错误的条件进行建模。然后固定代码,使这些测试通过,新测试保留在测试套件中。
一组好的测试将把好的和坏的参数都传递给函数/方法,并期望结果一致。反过来,这意味着被测组件将使用前提条件检查(防御性编程)来确认传递给它的参数。
一般而言...
例如,如果特定过程的null参数无效,那么至少一个测试将通过null,并且它将期望某种“无效null参数”异常/错误。
当然,至少还有另一个测试将通过一个有效的参数(或循环通过一个大数组并传递多个有效的参数),并确认结果状态是否合适。
如果一个测试没有通过该null参数,并且被期望的异常拍打(并且该异常被抛出,因为代码防御性地检查了传递给它的状态),则该null可能最终被分配给一个类的属性或被掩埋在某种不应该的集合中。
在软件出厂后的某个遥远地理位置,这可能会在类实例传递到的系统的某些完全不同的部分中导致意外行为。那是我们实际上要避免的事情,对吧?
甚至可能更糟。具有无效状态的类实例可以被序列化和存储,仅当将其重构以供以后使用时才导致失败。Geez,我不知道,也许它是某种机械控制系统,在关机后无法重启,因为它无法反序列化其自身的持久配置状态。或者,可以将类实例序列化并传递给其他实体创建的完全不同的系统,并且该系统可能崩溃。
尤其是如果其他系统的程序员没有防御性的代码。