邪恶单元测试

2020-09-24 21:21:54

这个页面是由Paul Wheaton作为承包商从javaranch.com.jsp Evil Unit Testing迁移而来的,我现在已经出现在不少于六家不同的公司,他们自豪地微笑着 单元测试,再加上这种巨大的自豪感,令人担忧的是,这一单元测试最终将成为一项 麻烦了。其他人谈论单元测试有多棒,但这真的变得很痛苦。*测试需要45分钟 运行几分钟,代码每做一点改动,我们就会破坏大约七个测试!";

这些人拥有的是一大堆功能测试。他们落入了流行的陷阱,认为是因为 它们使用JUnit运行测试,它们必须是单元测试。他们90%的问题本可以通过一个 一点点词汇。

软件测试词汇单元测试:最少的可测试代码。通常是单个方法/函数, 它不再使用其他方法或类。快!上千个单元测试可以在十秒甚至更短的时间内运行! *单元测试从不使用: 简单的单元测试几乎总是回归套件的一部分。

回归套件:可以一次运行所有测试的集合。举个例子来说, 可能放在某个目录中的所有测试都将由JUnit运行。*开发人员可以运行单元测试 每天使用回归套件20次。或者,他们可能每月运行两次功能测试回归套件。

功能测试:大于一个单元,小于完整组件测试。 通常一起执行几个方法/函数/类。它被允许享受它的甜蜜时光: 数百项测试可能需要数小时才能运行。大多数功能测试都是功能测试回归套件的一部分。 它通常从JUnit运行。

集成测试:测试一起工作的两个或多个组件。 有时是回归套件的一部分。

组件测试:单独运行一个组件。QA、经理、 这类测试不是回归套件的一部分,也不是由JUnit运行的。

组件验收测试(C.A.T.):在一大群人面前进行的组件测试 这是正式程序的一部分。房间里的人共同决定组件是否符合要求的标准。

系统验收测试(S.A.T.):在一大群人面前运行的系统测试 这是正式程序的一部分。房间里的人集体决定系统是否满足要求的标准。

压力测试:另一个程序加载一个组件、一些组件,或者可能加载整个组件 这是一个新的系统。我看到一些小的压力测试在一些回归功能测试中起作用-这是一种相当聪明的测试方式 下载一些并发代码!

模拟:在单元和功能测试中使用的脑死亡代码片段,以确保 但是,您尝试测试的一段代码不会使用其他一些生产代码。*通常是模拟类 将重写生产类中的所有公共方法,并将其插入可能尝试使用 我们的制作班级。有时,Mock类实现接口并替换实现相同接口的产品代码 它的界面。

分流:有点像扩展产品代码的模拟类,只是目的不是 我可以重写所有的方法,但只要足够,您就可以练习一些生产方法,同时嘲笑其余的 改变了生产方式。如果您要测试可能尝试使用I/O的类,则此功能尤其有用。您的分流器可以覆盖 在测试非I/O方法的同时测试I/O方法。

现在让您有机会告诉作者,他是个笨蛋,对coderanch线程单元测试词汇表中的这些东西一无所知。

太多功能测试的麻烦不要误会我。这些功能测试具有很大的价值。*我认为经过良好测试的应用程序将拥有一套回归套件 功能测试和非回归功能测试的集合。通常,对于每磅生产代码,我 我希望看到大约两磅的单元测试和两盎司的功能测试(一点点就会有很长的路要走)。 我在太多的商店里看到的问题 就是零个单元测试和一磅功能测试。

下面两个图像演示了使用使用类的类的类。有一些功能测试可以锻炼 这些班级一起工作。修复一个类中的错误会破坏许多功能测试……。

这种情况我已经看过很多次了。但在一个案例中,一个小小的变化就打破了47项测试。他们举行了几次会议,以决定是否 BUG应该留在代码中!最后,决定关闭所有测试,直到可以留出时间为止 来修复所有的测试。几个月过去了。事情变得非常臭气熏天…

通过只编写功能测试,我编写了更少的测试代码,而执行了更多的生产代码!";是真的!但是在 降低让你的项目变得脆弱的代价。此外,您的应用程序中有一些更好的地方将会很多 在不使用单元测试的情况下更难进行测试。*最好的覆盖范围和灵活性只能通过混合使用单元和 强调单元测试,轻功能测试的功能测试。

我的业务逻辑是所有这些类一起工作,所以只测试一个方法是没有意义的。";我建议 您可以单独测试所有的方法。此外,我并不是建议你没有功能测试- 它们有它们的价值。

我不介意我的单元测试套件运行几分钟。但是你们团队中的其他人介意吗? 你的团队是领头羊吗?你的经理呢?如果需要几分钟而不是几秒钟,你还会运行全套a套吗? 一天十几次?什么时候人们会停止运行测试?

单元测试是由JUnit.";运行的任何东西,在我们的行业中,单元测试这个术语确实是主观的。我认为 这就是我在这里表达的是对单元测试这个术语最流行的解释。

查看Big Moose Saloon的论坛,了解关于功能测试与单元测试的帖子。 这里的单元测试模拟基础是一种非常明显的单元测试形式。我在一种不依赖的方法上尝试各种古怪的东西 其他方法。

Public void testLongize() { **assertEquals(";-111.44";,Normalize.经度(";111.44w";)); **assertEquals(";-111.44";,Normalize.经度(";111.44W";)); 在AssertEquals(";-111.44&34;,正规化经度(";111.44 w&34;)); **AssertEquals(";-111.44";,Normalize.经度(";111.44 W&34;)); **assertEquals(";-111.44";,Normalize.Length(";111.44;w";)); **AssertEquals(";-111.44";,Normalize.Length(";-111.44w";)); **assertEquals(";-111.44&34;,Normalize.Length(";-111.44W";)); **AssertEquals(";-111.44";,Normalize.Length(";-111.44 w";)); *assertEquals(";-111.44&34;,标准经度(";-111.44 W";); **AssertEquals(";-111.44";,Normalize.Length(";-111.44";)); **AssertEquals(";-111.44";,Normalize.Length(";111.44-";)); **AssertEquals(";-111.44";,Normalize.Length(";111.44-";)); **assertEquals(";-111.44";,Normalize.经度(";111.44West";)); *//…… }。

当然,任何笨蛋都可以对这类东西进行单元测试。但大多数业务逻辑使用其他业务逻辑:

公共类FarmServlet扩展ActionServlet { **公共void doAction(ServletData ServletData)抛出异常 { *字符串种=servletData.getParameter(";种";); *字符串buildingID=servletData.getParameter(";buildingID";); 如果(Str.usable(物种)&;&;Str.usable(BuildingID)) { *FarmEJBRemote Remote Remote=FarmEJBUtil.getHome().create(); *REMOTE.addAnimal(种,buildingID); } } }

这不仅调用了其他业务逻辑,还调用了应用程序服务器!可能是通过网络! 数以千计的这些将需要十多秒的时间。另外,EJB内容中的更改可能会破坏我这里的测试! 因此需要引入模拟对象。

如果我能模拟出所有的EJB内容,我就会坐享其成了。嗯……。如果代码以某种方式 如果不能拿到我的模拟农场,我就会在肥城。

首先,创建模拟。如果FarmEJBRemote是一个类,我会扩展它并覆盖所有方法。但自那以后 如果它恰好是一个接口,我将只做一个新的类并实现所有的方法:

公共类MockRemote实现FarmEJBRemote { *字符串addAnimal_Species=NULL; *字符串addAnimal_buildingID=NULL; *int addAnimal_call=0; *public void addAnimal(String种,String buildingID) { *addAnimal_Species=物种; *addAnimal_buildingID=buildingID; *addAnimal_Calls++; } }。

这个嘲弄是哑巴的。我真的很笨。它只是在我的单元测试和我试图运行的代码之间传输数据。

这门课是不是让你..。不舒服?应该是这样。关于这类课程,有两件事是令人烦恼的 我第一次接触到它时:类属性不是私有的,并且它们在其中有下划线。我是第一个 上次我看到这样的模拟物体 我听说你的单元测试代码不会投入生产,所以可以偷工减料。但我不知道。我只想写头等舱。 一直在写代码!甚至不到一个小时,我就需要模仿java.sql.Connection。40种方法!每种方法都有getter和setter 是否有每个方法的参数、返回值和计数器??嗯……。仔细考虑一下…… 我们将属性设为私有的原因是为了封装-隐藏在 我在里面,这样我们以后就可以更改我们的业务逻辑,而不会破坏一堆决定进入我们的 这是内脏。但这并不真的适用于模拟,不是吗?根据定义,模拟没有业务逻辑。更进一步, 但是,它没有任何东西,它没有任何东西,只是从别人那里抄袭来的。任何地方的所有模拟对象都可以 它可以很容易地在构建时100%生成!所以我有时仍然对此感到有点反胃,但最终 我最后总是再次说服自己,这是最好的方法。所以它始终是第一类代码-它只是 它闻起来有点异味。但是它闻起来比我用另一种方式做的味道要好。

现在,我需要获取代码来接收我的模拟对象,而不是启动某个应用程序服务器。“那么,这就是那段代码。” 代码,我已经突出显示了我想要使用模拟的代码行。

公共类FarmServlet扩展ActionServlet { **公共void doAction(ServletData ServletData)抛出异常 { *字符串种=servletData.getParameter(";种";); *字符串buildingID=servletData.getParameter(";buildingID";); *是否(Str.usable(物种)&;&;Str.usable(BuildingID)) { *;font color=red>;FarmEJBRemote Remote=FarmEJBUtil.getHome().create();<;/font>; *REMOTE.addAnimal(种,buildingID); } } }

公共类FarmServlet扩展ActionServlet { *<;font color=red>;Private FarmEJBRemote getRemote() { *将返回FarmEJBUtil.getHome().create(); {##**$$}<;/font>; **公共void doAction(ServletData ServletData)抛出异常 { *字符串种=servletData.getParameter(";种";); *字符串buildingID=servletData.getParameter(";buildingID";); *是否(Str.usable(物种)&;&;Str.usable(BuildingID)) { *:<;font color=red>;FarmEJBRemote remote=getRemote();<;/font>; *REMOTE.addAnimal(种,buildingID); } } }。

这会有点疼的..。我现在将扩展我的生产类并重写getRemote(),这样我就可以强制 模拟执行此操作。所以我需要做一个小小的改变…。

公共类FarmServlet扩展ActionServlet { *<;font color=red>;FarmEJBRemote getRemote()<;/font>; { *将返回FarmEJBUtil.getHome().create(); } **公共void doAction(ServletData ServletData)抛出异常 { *字符串种=servletData.getParameter(";种";); *字符串buildingID=servletData.getParameter(";buildingID";); *是否(Str.usable(物种)&;&;Str.usable(BuildingID)) { *FarmEJBRemote Remote Remote=getRemote(); *REMOTE.addAnimal(种,buildingID); } } }。

如果你是一个好的面向对象工程师,你现在应该疯了!哦,当然,在单元中违反了封装 测试代码让人非常不舒服,但是违反生产代码中的封装已经完成了!(如果您错过了它, *我去掉了关键字";private";,使方法";package";-现在,同一个包中的任何东西都可以 (请看那个方法)再说一遍,冗长的解释可能会帮助事情平息一点。我要把它省下来 对于java论坛,现在说:永远要警惕您的产品代码中的第一类封装。 但是.。每隔一段时间..。您可以考虑用价值1美元的包装换取价值20美元的 可测试性。然后,为了减轻你的痛苦/羞愧,你可以添加一条评论:

公共类FarmServlet扩展ActionServlet { *<;font color=red>;//仅用于单元测试目的!<;/font>; *FarmEJBRemote getRemote() { *将返回FarmEJBUtil.getHome().create(); } **公共void doAction(ServletData ServletData)抛出异常 { *字符串种=servletData.getParameter(";种";); *字符串buildingID=servletData.getParameter(";buildingID";); *是否(Str.usable(物种)&;&;Str.usable(BuildingID)) { *FarmEJBRemote Remote Remote=getRemote(); *REMOTE.addAnimal(种,buildingID); } } }。

类FarmServletShut扩展了FarmServlet { **FarmEJBRemote getRemote_Return=NULL; *FarmEJBRemote getRemote() { *将返回getRemote_Return; } }。

注意这个奇怪的名字:“分流”。我不确定,但我想这个词来自电气工程/修修补补 并且指的是使用导线临时完成电路。一开始对我来说听起来真的很愚蠢,但过了一段时间 我已经习惯了。

分流有点像模拟,不同的是你不能覆盖所有的方法。这样一来,你就嘲笑了一些方法 同时也在测试其他人。*单元测试可能会以几个分流结束,所有分流都覆盖同一个类,每个测试都不同 班级的一部分。分流通常是内部类。

公共类TestFarmServlet扩展了TestCase { **静态类FarmServletShut扩展FarmServlet { *FarmEJBRemote getRemote_Return=NULL; *FarmEJBRemote getRemote() { *RETURN:getRemote_Return; } } *公共void testAddAnimal()引发异常 { *MockRemote mockRemote=new MockRemote(); *FarmServletShun=new FarmServletShun(); *shunt.getRemote_return=mockRemote(); *//只是另一个要做的嘲弄 *MockServletData mockServletData=new MockServletData();* (mockServletData.getParameter_returns.put(";species";,&34;Dog;); (mockServletData.getParameter_returns.put(";buildingID";,&34;27&34;); *shunt.doAction(MockServletData); *assertEquals(1,mockRemote.addAnimal_call); *assertEquals(";dog";,mockRemote.addAnimal_Species); *assertEquals(27,mockRemote.addAnimal_buildingID); } }。

现在基本框架已经就位,我只需要添加大量断言。

TestFarmServlet vs FarmServletTest:你必须加入一个阵营或另一个阵营。如果来自后一个阵营的人 这是一个非常好的观点:FarmServletTest听起来更像是一个名词,因此更多的是面向对象的。“我在前一个阵营。” 我已经对IDE上瘾了,很享受它完成类名的方式 对我来说。*当我有一套丰富的测试,并且我的测试类名都以";Test";结尾时, 然后,我的IDE提出的建议是我希望的两倍。*当我的测试类名都以";Test";开头时, 我的IDE提供的建议数量恰到好处。

在名为Creating Unit Tests(创建单元测试)的论坛帖子中讨论这个问题(带有模仿和分流!)。

要了解一个项目可能如何使所有这些东西保持整齐,请看一看。

示例目录结构项目名 */src*//生产来源 */java*/java来源。这里的第一个目录是";com"; 创建文件/db文件,创建文件文件,创建文件//数据库DDL(或.sql)文件。 */测试*/测试来源 */单位*//此目录中以以下开头的每个类 *#34;Test";作为单元测试运行 */Functional*//此目录中以以下字符开头的每个类 *#34;Test";作为功能测试运行 *支持/非回归:支持//在开发过程中手动在此处运行内容 */lib*//jar文件等项目使用的类似文件 */生产*/需要复制到生产中的*//材料。 */开发**/仅在开发过程中使用的内容// * /。Build//此目录不在版本控制中。 * */gen-src*//如果您的应用程序生成任何源代码,它放在这里 * *//编译好的生产类 */测试类:**//编译后的单元测试代码、功能测试代码、 *。等。 * *文件通常是WAR文件、EAR文件或JAR文件。 *build.xml*//ant构建文件。

人们将他们的单元测试内容放在与他们正在测试的产品代码相同的包中-但是!在一种不同的 目录结构。*因此,如果您正在测试src/java/com/javaranch/Str.java中的com.javaranch.Str.java,您可以 可能在test/unit/com/javaranch/TestStr.java中找到测试类com.javaranch.TestStr.java。

FIN这只是本主题的简要概述

.