|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
你总不能说你写框架吧,那无疑会加大工作量,现在大多企业采取的是折中的办法,就是改别人写好的框架,可要改框架,前提是你对这个框架足够的了解,这就更难了。关于测试来讲,编写断言仿佛很复杂:我们只必要对了局和预期举行对照,一般利用断言办法举行判别,比方测试框架供应的assertTrue()大概assertEquals()办法。但是,关于更庞大的测试场景,利用这些基本的断言考证了局大概会显得相称愚笨。
利用这些基本断言的次要成绩是,底层细节掩饰了测试自己,这是我们不但愿看到的。在我看来,应当争夺让这些测试利用营业言语来讲话。
在本篇文章中,我将展现怎样利用“婚配器类库”(matcherlibrary);来完成自界说断言,从而进步测试代码的可读性和可保护性。
为了便利演示,我们假定有如许一个义务:让我们设想一下,我们必要为使用体系的报表模块开辟一个类,输出两个日期(入手下手日期和停止日期),这个类将给出这两个日期之间一切的每小工夫隔。然后利用这些距离从数据库查询所需数据,并以直不雅的图表体例展示给终极用户。
尺度办法
我们先接纳“尺度”的办法来编写断言。我们以JUnit为例,固然你也能够利用TestNG。我们将利用像assertTrue()、assertNotNull()或assertSame()如许的断言办法。
上面展现了HourRangeTest类的个中一个测试办法。它十分复杂。起首挪用getRanges()办法,失掉两个日期之间一切的每小时局限。然后考证前往的局限是不是准确。- privatefinalstaticSimpleDateFormatSDF=newSimpleDateFormat("yyyy-MM-ddHH:mm");@TestpublicvoidshouldReturnHourlyRanges()throwsParseException{//givenDatedateFrom=SDF.parse("2012-07-2312:00");DatedateTo=SDF.parse("2012-07-2315:00");//whenfinalList<range>ranges=HourlyRange.getRanges(dateFrom,dateTo);//thenassertEquals(3,ranges.size());assertEquals(SDF.parse("2012-07-2312:00").getTime(),ranges.get(0).getStart());assertEquals(SDF.parse("2012-07-2313:00").getTime(),ranges.get(0).getEnd());assertEquals(SDF.parse("2012-07-2313:00").getTime(),ranges.get(1).getStart());assertEquals(SDF.parse("2012-07-2314:00").getTime(),ranges.get(1).getEnd());assertEquals(SDF.parse("2012-07-2314:00").getTime(),ranges.get(2).getStart());assertEquals(SDF.parse("2012-07-2315:00").getTime(),ranges.get(2).getEnd());}
复制代码 毫无疑问这是个无效的测试。但是,它有个严峻的弱点。在//then前面有大批的反复代码。明显,它们是复制和粘贴的代码,履历告知我,它们将不成制止地会发生毛病。别的,假如我们写更多相似的测试(我们一定还要写更多的测试来考证HourlyRange类),一样的断言声明将在每个测试中不休地反复。
过量的断言和每一个断言的庞大性削弱了以后测试的可读性。大批的底层乐音使我们没法疾速正确地懂得这些测试的中心场景。我们都晓得,浏览代码的次数宏大于编写的次数(我以为这一样合用于测试代码),以是我们天经地义地要想举措进步其可读性。
在我们重写这些测试之前,我还想重点说一下它的另外一个弱点,这与毛病信息有关。比方,假如getRanges()办法前往的个中一个Range与预期分歧,我们将失掉相似如许的信息:- org.junit.ComparisonFailure:Expected:1343044800000Actual:1343041200000
复制代码 这些信息太不明晰,理应失掉改良。
公有办法
那末,我们事实能做些甚么呢?好吧,最不言而喻的举措是将断言抽成一个公有办法:- privatevoidassertThatRangeExists(List<Range>ranges,intrangeNb,Stringstart,Stringstop)throwsParseException{assertEquals(ranges.get(rangeNb).getStart(),SDF.parse(start).getTime());assertEquals(ranges.get(rangeNb).getEnd(),SDF.parse(stop).getTime());}@TestpublicvoidshouldReturnHourlyRanges()throwsParseException{//givenDatedateFrom=SDF.parse("2012-07-2312:00");DatedateTo=SDF.parse("2012-07-2315:00");//whenfinalList<Range>ranges=HourlyRange.getRanges(dateFrom,dateTo);//thenassertEquals(ranges.size(),3);assertThatRangeExists(ranges,0,"2012-07-2312:00","2012-07-2313:00");assertThatRangeExists(ranges,1,"2012-07-2313:00","2012-07-2314:00");assertThatRangeExists(ranges,2,"2012-07-2314:00","2012-07-2315:00");}
复制代码 如许是否是好些?我会说是的。削减了反复代码的数目,进步了可读性,这固然是件功德。
这类办法的另外一个上风是,我们如今能够更简单地改良考证失利时的毛病信息。由于断言代码被抽到了一个办法中,以是我们能够改良断言,很简单地供应更可读的毛病信息。
为了更好地复用这些断言办法,能够将它们放到测试类的基类中。
不外,我以为我们大概能做得更好:利用公有办法也出缺点,跟着测试代码的增加,良多测试办法都将利用这些公有办法,其弱点将加倍分明:
- 断言办法的定名很难明晰反应其校验的内容。
- 跟着需求的增加,这些办法将会趋势于吸收更多的参数,以满意更庞大反省的请求。(assertThatRangeExists()如今有4个参数,已太多了!)
- 偶然候,为了在多个测试中复用这些代码,会在这些办法中引进一些庞大逻辑(一般以布尔标记的情势校验它们,或在某些特别的情形下,疏忽它们)。
从久远来看,一切利用公有断言办法编写的测试,意味着在可读性和可保护性方面将会碰到一些成绩。我们来看一下别的一种没有这些弱点的办理计划。
婚配器类库
在我们持续之前,我们先来懂得一些新工具。正如之条件到的,JUnit大概TestNG供应的断言短少充足的天真性。在Java天下,最少有两个开源类库可以满意我们的需求:AssertJ(FESTFluentAssertions项目标一个分支)和Hamcrest。我偏向于第一个,但这只是团体喜欢。这两个看起来都十分壮大,都能让你获得类似的效果。我更偏向于AssertJ的次要缘故原由是它基于Fluent接口,而IDE可以完善撑持该接口。
集成AssertJ和JUnit大概TestNG十分复杂。你只需增添所需的import,中断利用测试框架供应的默许断言办法,改用AssertJ供应的办法就能够了。
AssertJ供应了一些现成的十分有效的断言。它们都利用不异的“形式”:先挪用assertThat()办法,这是Assertions类的一个静态办法。该办法吸收被测试对象作为参数,为更多的考证做好筹办。以后是真实的断言办法,每个都用于校验被测对象的各类属性。我们来看一些例子:- assertThat(myDouble).isLessThanOrEqualTo(2.0d);assertThat(myListOfStrings).contains("a");assertThat("sometext").isNotEmpty().startsWith("some").hasLength(9);
复制代码 从这能看出,AssertJ供应了比JUnit和TestNG丰厚很多的断言汇合。就像最初一个assertThat("sometext")例子显现的,你乃至能够将它们串在一同。另有一个十分便利的事变是,你的IDE可以依据被测对象的范例,主动为你提醒可用的办法。举例来讲,关于一个double值,当你输出“assertThat(myDouble).”,然后按下CTRL+SPACE(大概别的IDE供应的快速键),IDE将为你显现可用的办法列表,比方isEqualTo(expectedDouble)、isNegative()或isGreaterThan(otherDouble),一切这些都可用于double值的校验。这切实其实是一个很酷的功效。
自界说断言
具有AssertJ大概Hamcrest供应的更壮大的断言汇合切实其实很好,但关于HourRange类来讲,这并非我们真正想要的。婚配器类库的另外一个功效是同意你编写本人的断言。这些自界说断言的举动将与AssertJ的默许断言一样,也就是说,你可以把它们串在一同。这恰是我们接上去要做的。
接上去我们将看到一个自界说断言的示例完成,但如今让我们先看看最后的效果。此次我们将利用(我们本人的)RangeAssert类的assertThat()办法。- @TestpublicvoidshouldReturnHourlyRanges()throwsParseException{//givenDatedateFrom=SDF.parse("2012-07-2312:00");DatedateTo=SDF.parse("2012-07-2315:00");//whenList<Range>ranges=HourlyRange.getRanges(dateFrom,dateTo);//thenRangeAssert.assertThat(ranges).hasSize(3).isSortedAscending().hasRange("2012-07-2312:00","2012-07-2313:00").hasRange("2012-07-2313:00","2012-07-2314:00").hasRange("2012-07-2314:00","2012-07-2315:00");}
复制代码 即使是下面这么小的一个例子,我们也能看出自界说断言的一些上风。起首要注重的是//then前面的代码的确变少了,可读性也更好了。
将自界说断言使用于更年夜的代码库时,将展现出别的上风。当我们持续利用自界说断言时,我们将注重到:
- 能够很简单地复用它们。我们不强制利用一切断言,但对特定测试用例,我们能够只选择那些主要的断言。
- 特定范畴言语属于我们,也就是说,关于特定测试场景,我们能够依据本人的喜欢改动它(比方,传进Date对象,而不是字符串)。更主要的是如许的改动不会影响到别的测试。
- 高可读性。毫无疑问,由于断言包含了良多小断言办法,每个都只存眷校验的很小的某个方面,因而能够为校验办法取一个得当的名字。
与公有断言办法比拟,自界说断言的独一不敷是事情量要年夜一些。我们来看一下自界说断言的代码,它是不是真的是一个很难的义务。
要创立自界说断言,我们必要承继AssertJ的AbstractAssert类大概其子类。以下所示,我们的RangeAssert承继自AssertJ的ListAssert类。这很一般,由于我们的自界说断言将校验一个Range列表(List<Range>)。
每个利用AssertJ的自界说断言城市包括创立断言对象、注进被测对象的代码,然后可使用更多的办法对其举行操纵。以下面的代码所示,机关办法和静态assertThat()办法的参数都是List<Range>。- publicclassRangeAssertextendsListAssert<Range>{protectedRangeAssert(List<Range>ranges){super(ranges);}publicstaticRangeAssertassertThat(List<Range>ranges){returnnewRangeAssert(ranges);}
复制代码 如今我们看看RangeAssert类的其他内容。hasRange()和isSortedAscending()办法(显现鄙人一个代码列表中)是自界说断言办法的典范例子。它们具有以下配合点:
- 它们都先挪用isNotNull()办法,反省被测对象是不是为null。确保这个校验不会失利并抛出NullPointerException非常动静。(这一步不是必需的,但倡议有这一步)
- 它们都前往“this”(也就是自界说断言类的对象,对应例子中RangeAssert类的对象)。这使得一切办法能够串在一同。
- 它们都利用AssertJAssertions类(属于AssertJ框架)供应的断言办法实行校验。
- 它们都利用“实在”的对象(由父类ListAssert供应),确保Range列表(List<Range>)被校验。
- privatefinalstaticSimpleDateFormatSDF=newSimpleDateFormat("yyyy-MM-ddHH:mm");publicRangeAssertisSortedAscending(){isNotNull();longstart=0;for(inti=0;i<actual.size();i++){Assertions.assertThat(start).isLessThan(actual.get(i).getStart());start=actual.get(i).getStart();}returnthis;}publicRangeAsserthasRange(Stringfrom,Stringto)throwsParseException{isNotNull();LongdateFrom=SDF.parse(from).getTime();LongdateTo=SDF.parse(to).getTime();booleanfound=false;for(Rangerange:actual){if(range.getStart()==dateFrom&&range.getEnd()==dateTo){found=true;}}Assertions.assertThat(found).isTrue();returnthis;}}
复制代码 那末毛病信息呢?AssertJ让我们能够很简单地增加毛病信息。关于复杂的场景,比方值的对照,一般利用as()办法就充足了,示比方下:- Assertions.assertThat(actual.size()).as("numberofranges").isEqualTo(expectedSize);
复制代码 正如你所见到的,as()只是AssertJ框架供应的另外一个办法。当测试失利时,它打印上面的信息,我们当即就可以晓得哪儿错了:- org.junit.ComparisonFailure:[numberofranges]Expected:4Actual:3
复制代码 偶然候只晓得被测对象的名字是不敷的,我们必要更多信息以懂得究竟产生了甚么。以hasRange()办法为例,当测试失利时,假如可以打印一切range就更好了。我们能够经由过程overridingErrorMessage()办法来完成这类效果:- publicRangeAsserthasRange(Stringfrom,Stringto)throwsParseException{...StringerrMsg=String.format("rangesn%sndonotcontain%s-%s",actual,from,to);...Assertions.assertThat(found).overridingErrorMessage(errMsg).isTrue();...}
复制代码 如今,当测试失利时,我们可以失掉十分具体的信息。它的内容取决于Range类的toString()办法。比方,它看起来多是如许的:- org.junit.ComparisonFailure:Expected:1343044800000Actual:13430412000000
复制代码 总结
在本文中,我们会商了良多编写断言的办法。我们从“传统”的体例入手下手,也就是基于测试框架供应的断言办法。关于良多场景,这已十分好了。可是正如我们所看到的,它在表达测试企图时,偶然候短少了一些天真性。以后,我们经由过程引进公有断言办法,获得了一点改良,但仍旧不是幻想的办理计划。最初,我们实验利用AssertJ编写自界说断言,我们的测试代码获得了十分好的可读性和可保护性。
假如要我供应一些关于断言的倡议,我将会倡议以下内容:假如你中断利用测试框架(比方JUnit或TestNG)供应的断言,改成利用婚配器类库(比方AssertJ大概Hamcrest),你的测试代码将失掉极年夜的改良。你将可使用大批可读性很强的断言,削减测试代码中//then以后的庞大声明。
只管编写自界说断言的本钱十分低,但也没有需要由于你会写就必定要利用它们。当你的测试代码的可读性而且/大概可保护性变差时利用它们。依据我的履历,我会勉励你在以了局景中利用自界说断言:
JAVA学习必须明确这是一项投资,对于大多数的人来说,学习JAVA是为了就业,还有就是刚走向工作位置的朋友想尽快赶上工作的节奏。 |
|