最近,我一直在阅读有关Literate Programming的文章,这让我开始思考...写得井井有条的测试(尤其是BDD风格的规范)在解释代码作用方面比散文效果更好,并且具有以下优点:验证他们自己的准确性。

我从未见过用他们所测试的代码内联编写的测试。这仅仅是因为语言在编写到同一源文件中时不会趋于使应用程序和测试代码分离变得简单(并且没有人使它变得容易),还是人们在原则上将测试代码与应用程序代码分开?

评论

一些编程语言(例如带有doctest的python)允许您执行此操作。

您可能会觉得BDD风格的规范在解释代码方面比散文要好,但这并不意味着两者结合起来并不是更好。

这里的一半参数也适用于内联文档。

@Simon doctest对于过于严格的测试而言过于简单了,主要是因为它们不是为此而设计的。它们旨在并擅长在文档中提供可以自动验证的代码示例。现在,有些人也将它们用于单元测试,但是最近(就像过去几年一样)花了很多时间,因为它往往以易碎的混乱,过于冗长的“文档”和其他混乱为结尾。 br />
按合同设计允许在线规格,使测试变得简单。

#1 楼

我可以想到的用于内联测试的唯一好处是减少了要写入的文件数量。对于现代IDE,这实际上没什么大不了的。

但是,内联测试有许多明显的缺点:


它违反了分离的关注。这可能值得商bat,但对我而言,测试功能与实现功能的责任不同。 。
较大的源文件更难以使用:更难阅读,更难以理解,您更有可能不得不处理源代码控制冲突。
我认为这将使放置您的源代码更加困难可以这么说。如果您正在查看实施细节,您将更倾向于跳过实施某些测试。


评论


那很有意思。我想我能看到的好处是,当您戴上“编码器”帽子时,您想考虑一下测试,但是要指出的是,事实并非如此,这是一个好主意。

–克里斯·德弗勒克斯(Chris Devereux)
2013年2月25日14:24



按照这些思路,有可能(也许是合乎需要的)让一个人创建测试,然后由另一个人实际执行代码。在线进行测试会使这变得更加困难。

– Jim Nutt
13年2月25日在16:07

如果可以的话,我会拒绝投票。无论如何,这是一个怎样的答案?实施者不编写测试吗?人们是否在查看实施细节时跳过测试? “太难了”在大文件上发生冲突??以及如何将测试与实现细节相混淆???

–巴尔
13年2月26日在11:15



@bharal另外,对“只是太难了”,受虐狂是傻瓜的美德。除了我实际上要解决的问题外,我希望一切都变得简单。

–deworde
13年2月26日在11:33

单元测试可以视为文档。这表明出于与注释相同的原因,应在代码中包含单元测试,以提高可读性。但是,这样做的问题是往往会有大量的单元测试,并且有大量的测试实现开销没有指定预期的结果。甚至代码中的注释也应保持简洁,将较大的解释移开:移至函数外部的注释块,单独的文件或设计文档中。 IMO的单元测试很少,即使足够短,也无法保留在注释之类的测试代码中。

–Steve314
13年5月5日,1:11

#2 楼

我可以想到一些:


可读性。散布着“真实”代码和测试将使阅读真实代码变得更加困难。
代码膨胀。将“真实”代码和测试代码混合到相同的文件/类/可能会导致较大的编译文件等中。这对于绑定较晚的语言尤其重要。
您可能不希望客户/客户查看您的测试代码。 (我不喜欢这个原因...但是,如果您正在开发一个封闭源代码项目,那么无论如何测试代码都不太可能对客户有所帮助。)

现在每种方法都有可能的解决方法这些问题。但是IMO,首先不要去那里比较容易。


值得一提的是,在早期,Java程序员曾经做这种事情。例如在类中包括main(...)方法以方便测试。这个想法几乎完全消失了。使用某种测试框架分别实施测试是一种行业惯例。

值得一提的是,Literate编程(如Knuth所设想的)从未在软件工程行业中流行。
/>

评论


+1可读性问题-测试代码可能与实现代码成比例地更大,尤其是在OO设计中。

–负责人
13年2月25日在17:22

+1指出使用测试框架。我无法想象在生产代码中同时使用良好的测试框架。

– joshin4colours
13年2月25日在17:23

RE:您可能不希望您的客户/客户看到您的测试代码。 (我不喜欢这个原因...但是,如果您正在开发一个封闭源代码项目,无论如何测试代码都不太可能对客户有所帮助。)-可能需要在客户端计算机上运行测试。运行测试可能有助于快速识别问题所在,并帮助识别客户端环境中的差异。

–sixtyfootersdude
2013年2月25日在18:24



@sixtyfootersdude-那是非常不寻常的情况。并且假设您正在开发封闭源代码,则您不希望在标准二进制发行版中包含测试,以防万一。 (您将创建一个单独的捆绑包,其中包含您要客户运行的测试。)

– Stephen C
13年2月26日,0:02

1)您是否错过了我给出三个实际原因的答案的第一部分? .... 2)您是否错过了第二部分,我曾经说过Java程序员曾经这样做过,但是现在不知道吗?显然,程序员有充分的理由停止这样做了……?

– Stephen C
2015年12月11日在12:39



#3 楼

实际上,您可以考虑按合同设计。问题是大多数编程语言都不允许您这样编写代码:(手动测试前置条件非常容易,但是后置条件是不改变编写代码方式的真正挑战(一个巨大的负面IMO)。 br />
Michael Feathers对此进行了介绍,这是他提到的可以提高代码质量的多种方式之一。

#4 楼

出于许多相同的原因,您尝试避免代码中的类之间紧密耦合,所以避免测试和代码之间不必要的耦合也是一个好主意。

创建:测试和代码可以在以下位置编写

控制:如果使用测试来指定需求,那么您肯定希望它们受制于不同的规则,即谁可以更改它们以及何时更改它们,而不是实际的代码。

可重用性:如果将测试内联,则不能将它们与另一段代码一起使用。

想象一下,您有一大堆代码可以正确地工作,但是在性能,可维护性等方面还有很多需要改进的地方。您决定用新的和改进的代码替换该代码。使用相同的测试集可以帮助您验证新代码产生的结果与旧代码相同。

可选择性:将测试与代码分开可以更轻松地选择想要的测试。跑。

例如,您可能有一小套测试,它们仅与您当前正在使用的代码相关,而另一套测试则用于测试整个项目。

评论


我对您的原因感到困惑:TDD已经说过,测试创建是在生产代码之前(或同时)进行的,并且必须由相同的编码器完成!他们还暗示测试非常像需求。当然,如果您不订阅TDD教条(这是可以接受的,但您必须明确说明!),这些异议就不适用。另外,什么是“可重用”测试?根据定义,不是针对测试的代码进行测试吗?

– Andres F.
2013年5月5日15:32



@AndresF。不,测试不是特定于他们要测试的代码。它们特定于他们要测试的行为。因此,假设您已经完成了一个Widget模块,其中包含一组测试,这些测试可以验证Widget的行为是否正确。您的同事想到了BetterWidget,它的功能与Widget相同,但速度要快三倍。如果以与Literate Programming将文档嵌入源代码中的方式相同的方式将Widget的测试嵌入到Widget的源代码中,则无法很好地将这些测试应用于BetterWidget来验证其行为与Widget相同。

–卡莱布
13年5月6日,下午3:54

@AndresF。无需指定您不遵循TDD。这不是宇宙的默认值。至于重用点。测试系统时,您关心的是输入和输出,而不是内部。然后,当您需要创建一个性能与新系统相同但实现方式不同的新系统时,最好具有可以在新旧系统上运行的测试。这不止一次地发生在我身上,有时您需要在旧系统仍处于生产状态时使用新系统,甚至并排运行它们。看看Facebook通过反应测试来测试“反应纤维”以达到均等的方式。

–user1852503
17年8月19日在22:32

#5 楼

我想到的还有其他一些原因:


将测试放在单独的库中可以更轻松地仅将库与您的测试框架链接,而不能将您的生产代码链接(这可能是某些预处理程序可以避免,但是当更简单的解决方案是在单独的位置编写测试时,为什么要构建这样的东西呢?
功能,类,库的测试通常是从“用户”角度编写的视图(该功能/类/库的用户)。这种“使用代码”通常写在单独的文件或库中,并且如果模拟这种情况,则测试可能更清晰或更“现实”。


#6 楼

如果测试是内联的,则在将产品交付给客户时,有必要删除测试所需的代码。因此,将测试存储在额外的位置可以简单地将所需的代码与客户所需的代码分开。

评论


不是不可能。就像LP一样,这将需要一个额外的预处理阶段。例如,可以使用C或js编译语言轻松完成此操作。

–克里斯·德弗勒克斯(Chris Devereux)
13年2月25日在13:44

+1向我指出。我已经编辑了答案以表示这一点。

–mhr
13年2月25日在15:23

还假设在每种情况下代码大小都很重要。仅在某些情况下重要并不意味着在所有情况下都重要。在很多环境中,程序员都不会被驱动来优化源代码的大小。如果真是这样,那么他们就不会创建太多的类。

– zumalifeguard
2015年12月11日,下午3:49

#7 楼

在基于对象或面向对象的设计上下文中,这种想法仅相当于“ Self_Test”方法。如果使用诸如Ada之类的基于对象的已编译语言,则编译器将在生产编译期间将所有自检代码标记为未使用(从未调用),因此将对其进行全部优化-它们都不会出现在生成的可执行文件。

使用“ Self_Test”方法是一个非常好的主意,如果程序员真的关心质量,那么他们都会这么做。但是,一个重要的问题是“ Self_Test”方法需要严格的纪律,因为它无法访问任何实现细节,而只能依赖对象规范内的所有其他已发布方法。显然,如果自检失败,则需要更改实现。自检应该严格测试对象方法的所有已发布属性,但决不以任何方式依赖任何特定实现的任何细节。

基于对象的语言和面向对象的语言经常提供相对于被测试对象外部的方法而言,正是这种类型的纪律(它们强制执行对象的规范,从而阻止对其实现详细信息的任何访问,如果检测到任何此类尝试,则会引发编译错误)。但是对象的内部方法都可以完全访问每个实现细节。因此,自测方法处于一种独特的情况:由于其性质,它必须是一个内部方法(自测显然是被测试对象的一种方法),但是它需要接受外部方法的所有编译规则(它必须独立于对象的实现细节)。几乎没有编程语言能够像对象外部方法一样对对象的内部方法进行约束。因此,这是一个重要的编程语言设计问题。

在没有适当的编程语言支持的情况下,最好的方法是创建一个伴随对象。换句话说,对于您编码的每个对象(我们称其为“ Big_Object”),您还将创建第二个伴随对象,其名称由标准后缀和“真实”对象的名称组成(在本例中为“ Big_Object_Self_Test”) ”,其规范由单个方法组成(“ Big_Object_Self_Test.Self_Test(This_Big_Object:Big_Object)返回布尔值;”)。然后,伴随对象将取决于主对象的规范,并且编译器将针对伴随对象的实现完全执行该规范的所有规则。

#8 楼

这是对大量评论的回应,这些评论表明没有进行内联测试,因为很难甚至不可能从发行版中删除测试代码。这是不正确的。几乎所有的编译器和汇编器都已经通过诸如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


因为这使用了编译器指令,所以代码将如果未设置标志,则在生成的可执行文件中不存在。这也是您为多个平台/硬件制作“编写一次,编译两次”程序的方式。

#9 楼

Erlang 2实际上支持内联测试。代码中任何未使用的布尔表达式(例如,分配给变量或通过)都将自动视为测试并由编译器评估;如果表达式为假,则代码不会编译。

评论


生锈了! doc.rust-lang.org/1.4.0/book/testing.html,您可以将测试移至其自己的测试块中,并将其也保留在源文件的底部,例如doc.rust-lang.org / stable / rust-by-example / testing /…

–西蒙B.
20年5月5日,下午1:36

#10 楼

我们在Perl代码中使用内联测试。有一个模块Test :: Inline从内联代码生成测试文件。

我不是特别擅长组织测试,并且发现内联时它们更容易维护。

响应一些提出的问题:


内联测试用POD部分编写,因此它们不是实际代码的一部分。解释器将忽略它们,因此不会出现代码膨胀。
我们使用Vim折叠来隐藏测试部分。您看到的唯一一件事是,每种方法上方的一行都被测试,例如+-- 33 lines: #test----。当您要使用测试时,只需对其进行扩展。
Test :: Inline模块将测试“编译”为普通的TAP兼容文件,因此它们可以与传统测试共存。

供参考:


POD
TAP


#11 楼

分离测试的另一个原因是,与实际实现相比,您经常使用其他甚至不同的库进行测试。如果混合使用测试和实现,编译器将无法捕获实现中测试库的意外使用。

另外,测试往往比测试的实现部分具有更多的代码行,因此您将很难在所有测试之间找到实现。 :-)

#12 楼

这不是真的在生产代码时,尤其是在生产例程是纯净的时候,最好将单元测试放在生产代码旁边。

例如,如果您在.NET下进行开发,则可以进行测试生产装配体中的代码,然后在运送之前使用Scalpel删除它们。