最近,我一直在阅读一些有关Literate Programming的文章,这让我开始思考...写得井井有条的测试(尤其是BDD风格的规范)在解释代码作用方面比散文效果更好,并且具有以下优点:验证自己的准确性。
我从未见过将测试与其代码内联地编写的测试。这是仅是因为语言在编写到相同的源文件中时不会趋向于将应用程序和测试代码分开(而没有人使之变得容易),还是人们在原则上将测试代码与应用程序代码分开呢?
最近,我一直在阅读一些有关Literate Programming的文章,这让我开始思考...写得井井有条的测试(尤其是BDD风格的规范)在解释代码作用方面比散文效果更好,并且具有以下优点:验证自己的准确性。
我从未见过将测试与其代码内联地编写的测试。这是仅是因为语言在编写到相同的源文件中时不会趋向于将应用程序和测试代码分开(而没有人使之变得容易),还是人们在原则上将测试代码与应用程序代码分开呢?
Answers:
我可以想到的用于内联测试的唯一优势是减少要写入的文件数量。对于现代IDE,这并不是什么大问题。
但是,内联测试有许多明显的缺点:
我可以想到一些:
可读性。散布“真实”代码和测试将使阅读真实代码更加困难。
代码膨胀。将“真实”代码和测试代码混合到相同的文件/类/可能会导致更大的编译文件的位置,等等。这对于后期绑定的语言尤其重要。
您可能不希望您的客户看到您的测试代码。(我不喜欢这个原因……但是,如果您正在开发一个封闭源代码项目,那么无论如何测试代码都不太可能对客户有所帮助。)
现在,每个问题都有可能的解决方法。但是,海事组织(IMO),首先不去那里比较容易。
值得一提的是,在早期,Java程序员曾经做过这种事情。例如main(...)
,在类中包括一种有助于测试的方法。这个想法几乎完全消失了。使用某种测试框架分别实施测试是行业惯例。
值得一提的是,“精简编程”(由Knuth构想)从未在软件工程行业中流行。
出于许多相同的原因,您试图避免代码中的类之间紧密耦合,所以避免测试与代码之间不必要的耦合也是一个好主意。
创建:测试和代码可由不同的人在不同的时间编写。
控制:如果使用测试来指定需求,那么您肯定希望它们受制于不同的规则,即谁可以更改它们以及何时更改它们,而不是实际的代码。
可重用性:如果将测试内联,则不能将它们与另一段代码一起使用。
想象一下,您有一大堆代码可以正确完成工作,但是无论在性能,可维护性方面,还是有很多不足之处。您决定用新的和改进的代码替换该代码。使用相同的测试集可以帮助您验证新代码是否产生与旧代码相同的结果。
选择性:将测试与代码分开可以更轻松地选择要运行的测试。
例如,您可能有一小套测试,它们仅与您当前正在处理的代码相关,而另一套测试则用于测试整个项目。
如果测试是内联的,则在将产品交付给客户时,有必要删除测试所需的代码。那么,你存储你的测试一个额外的地方简单的代码之间的分离,你需要和你的代码的客户需求。
在基于对象或面向对象的设计上下文中,这种想法仅相当于“ Self_Test”方法。如果使用像Ada这样的基于对象的已编译语言,编译器将在生产编译期间将所有自检代码标记为未使用(从不调用),因此将对其进行全部优化-它们都不会出现在生成的可执行文件。
使用“ Self_Test”方法是一个非常好的主意,如果程序员真的关心质量,那么他们都会这么做。但是,一个重要的问题是“ Self_Test”方法需要严格的纪律,因为它无法访问任何实现细节,而只能依赖对象规范内的所有其他已发布方法。显然,如果自检失败,则需要更改实现。自检应该严格测试对象方法的所有已发布属性,但决不以任何方式依赖任何特定实现的任何细节。
基于对象的语言和面向对象的语言经常确实针对测试对象外部的方法提供了这种类型的规范(它们强制执行对象的规范,从而阻止了对其实现细节的任何访问,如果检测到任何此类尝试,就会引发编译错误) )。但是,对象自己的内部方法都可以完全访问每个实现细节。因此,自测方法处于一种独特的情况:由于其性质,它必须是一种内部方法(自测显然是被测试对象的一种方法),但是它需要接受外部方法的所有编译器准则(它必须独立于对象的实现细节)。很少有编程语言能够提供训练对象的能力。的内部方法,就好像它是外部方法一样。因此,这是一个重要的编程语言设计问题。
在没有适当的编程语言支持的情况下,最好的方法是创建一个伴随对象。换句话说,对于您编码的每个对象(我们称其为“ Big_Object”),您还将创建第二个伴随对象,其名称由标准后缀和“真实”对象的名称组成(在本例中为“ Big_Object_Self_Test”) ”,其规范包含一个方法(“ Big_Object_Self_Test.Self_Test(This_Big_Object:Big_Object)返回布尔值;”)。然后,伴随对象将取决于主对象的规范,并且编译器将针对伴随对象的实现完全执行该规范的所有规则。
这是对大量评论的回应,这些评论表明未进行内联测试,因为很难甚至不可能从发行版中删除测试代码。这是不正确的。几乎所有的编译器和汇编器都已经使用诸如C,C ++,C#之类的编译语言来支持此功能,这是通过所谓的编译器指令完成的。
在c#的情况下(我也相信c ++,语法可能会略有不同,具体取决于您使用的是什么编译器),这就是您可以执行的操作。
#define DEBUG // = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */
//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif
因为这使用了编译器指令,所以如果未设置标志,则代码将不存在于生成的可执行文件中。这也是您为多个平台/硬件制作“编写一次,编译两次”程序的方式。
我们在Perl代码中使用内联测试。有一个模块Test :: Inline,可以从内联代码生成测试文件。
我并不是特别擅长组织测试,并且发现它们更容易且更容易在内联时得以维护。
对提出的一些担忧作出回应:
+-- 33 lines: #test----
。当您想使用测试时,只需扩展它即可。以供参考: