当前位置:天才代写 > tutorial > JAVA 教程 > 简述Java测试的自界说断言

简述Java测试的自界说断言

2017-11-02 08:00 星期四 所属: JAVA 教程 浏览:452

副标题#e#

对付测试来说,编写断言好像很简朴:我们只需要对功效和预期举办较量,凡是利用断言要领举办判定,譬喻测试框架提供的assertTrue()可能assertEquals()要领。然而,对付更巨大的测试场景,利用这些基本的断言验证功效大概会显得相当鸠拙。

利用这些基本断言的主要问题是,底层细节掩盖了测试自己,这是我们不但愿看到的。在我看来,应该争取让这些测试利用业务语言来措辞。

在本篇文章中,我将展示如何利用“匹配器类库”(matcher library);来实现自界说断言,从而提高测试代码的可读性和可维护性。

为了利便演示,我们假设有这样一个任务:让我们想象一下,我们需要为应用系统的报表模块开拓一个类,输入两个日期(开始日期和竣事日期),这个类将给出这两个日期之间所有的每小时隔断。然后利用这些隔断从数据库查询所需数据,并以直观的图表方法揭示给最终用户。

尺度要领

我们先回收“尺度”的要领来编写断言。我们以JUnit为例,虽然你也可以利用TestNG。我们将利用像assertTrue()、assertNotNull()或assertSame()这样的断言要领。

下面展示了HourRangeTest类的个中一个测试要领。它很是简朴。首先挪用getRanges()要领,获得两个日期之间所有的每小时范畴。然后验证返回的范畴是否正确。

private final static SimpleDateFormat SDF 
        = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 
@Test 
public void shouldReturnHourlyRanges() throws ParseException { 
       // given 
       Date dateFrom = SDF.parse("2012-07-23 12:00"); 
       Date dateTo = SDF.parse("2012-07-23 15:00"); 
       // when 
       final List<range> ranges = HourlyRange.getRanges(dateFrom, dateTo);         
       // then 
       assertEquals(3, ranges.size()); 
       assertEquals(SDF.parse("2012-07-23 12:00").getTime(), ranges.get(0).getStart()); 
       assertEquals(SDF.parse("2012-07-23 13:00").getTime(), ranges.get(0).getEnd()); 
       assertEquals(SDF.parse("2012-07-23 13:00").getTime(), ranges.get(1).getStart()); 
       assertEquals(SDF.parse("2012-07-23 14:00").getTime(), ranges.get(1).getEnd()); 
       assertEquals(SDF.parse("2012-07-23 14:00").getTime(), ranges.get(2).getStart()); 
       assertEquals(SDF.parse("2012-07-23 15:00").getTime(), ranges.get(2).getEnd()); 
}

毫无疑问这是个有效的测试。然而,它有个严重的缺点。在//then后头有大量的反复代码。显然,它们是复制和粘贴的代码,履历汇报我,它们将不行制止地会发生错误。另外,假如我们写更多雷同的测试(我们必定还要写更多的测试来验证HourlyRange类),同样的断言声明将在每一个测试中不绝地反复。

过多的断言和每个断言的巨大性削弱了当前测试的可读性。大量的底层噪音使我们无法快速精确地相识这些测试的焦点场景。我们都知道,阅读代码的次数远大于编写的次数(我认为这同样合用于测试代码),所以我们理所虽然地要想步伐提高其可读性。

在我们重写这些测试之前,我还想重点说一下它的另一个缺点,这与错误信息有关。譬喻,假如getRanges()要领返回的个中一个Range与预期差异,我们将获得雷同这样的信息:

org.junit.ComparisonFailure: 
Expected :1343044800000 
Actual :1343041200000

这些信息太不清晰,理应获得改进。

私有要领

那么,我们毕竟能做些什么呢?好吧,最显而易见的步伐是将断言抽成一个私有要领:

private void assertThatRangeExists(List<Range> ranges, int rangeNb, 
                                   String start, String stop) throws ParseException { 
        assertEquals(ranges.get(rangeNb).getStart(), SDF.parse(start).getTime()); 
        assertEquals(ranges.get(rangeNb).getEnd(), SDF.parse(stop).getTime()); 
} 
@Test 
public void shouldReturnHourlyRanges() throws ParseException { 
       // given 
       Date dateFrom = SDF.parse("2012-07-23 12:00"); 
       Date dateTo = SDF.parse("2012-07-23 15:00"); 
       // when 
       final List<Range> ranges = HourlyRange.getRanges(dateFrom, dateTo); 
       // then 
       assertEquals(ranges.size(), 3); 
       assertThatRangeExists(ranges, 0, "2012-07-23 12:00", "2012-07-23 13:00"); 
       assertThatRangeExists(ranges, 1, "2012-07-23 13:00", "2012-07-23 14:00"); 
       assertThatRangeExists(ranges, 2, "2012-07-23 14:00", "2012-07-23 15:00"); 
}

这样是不是好些?我会说是的。淘汰了反复代码的数量,提高了可读性,这虽然是件功德。

这种要领的另一个优势是,我们此刻可以更容易地改进验证失败时的错误信息。因为断言代码被抽到了一个要领中,所以我们可以改进断言,很容易地提供更可读的错误信息。

为了更好地复用这些断言要领,可以将它们放到测试类的基类中。

不外,我以为我们也许能做得更好:利用私有要领也有缺点,跟着测试代码的增长,许多测试要领都将利用这些私有要领,其缺点将越发明明:

断言要领的定名很难清晰反应其校验的内容。

跟着需求的增长,这些要领将会趋向于吸收更多的参数,以满意更巨大查抄的要求。(assertThatRangeExists()此刻有4个参数,已经太多了!)

#p#分页标题#e#

有时候,为了在多个测试中复用这些代码,会在这些要领中引入一些巨大逻辑(凡是以布尔符号的形式校验它们,或在某些非凡的环境下,忽略它们)。

从久远来看,所有利用私有断言要领编写的测试,意味着在可读性和可维护性方面将会碰着一些问题。我们来看一下别的一种没有这些缺点的办理方案。

查察本栏目


#p#副标题#e#

匹配器类库

在我们继承之前,我们先来相识一些新东西。正如之前提到的,JUnit可能TestNG提供的断言缺少足够的机动性。在Java世界,至少有两个开源类库可以或许满意我们的需求:AssertJ(FEST Fluent Assertions项目标一个分支)和 Hamcrest。我倾向于第一个,但这只是小我私家爱好。这两个看起来都很是强大,都能让你取得相似的结果。我更倾向于AssertJ的主要原因是它基于Fluent接口,而IDE可以或许完美支持该接口。

集成AssertJ和JUnit可能TestNG很是简朴。你只要增加所需的import,遏制利用测试框架提供的默认断言要领,改用AssertJ提供的要领就可以了。

AssertJ提供了一些现成的很是有用的断言。它们都利用沟通的“模式”:先挪用assertThat()要领,这是Assertions类的一个静态要领。该要领吸收被测试工具作为参数,为更多的验证做好筹备。之后是真正的断言要领,每一个都用于校验被测工具的各类属性。我们来看一些例子:

assertThat(myDouble).isLessThanOrEqualTo(2.0d); 
 
assertThat(myListOfStrings).contains("a"); 
 
assertThat("some text") 
       .isNotEmpty() 
       .startsWith("some") 
       .hasLength(9);

从这能看出,AssertJ提供了比JUnit和TestNG富厚得多的断言荟萃。就像最后一个assertThat("some text")例子显示的,你甚至可以将它们串在一起。尚有一个很是利便的工作是,你的IDE可以或许按照被测工具的范例,自动为你提示可用的要领。举例来说,对付一个double值,当你输入“assertThat(myDouble).”,然后按下CTRL + SPACE(可能其它IDE提供的快捷键),IDE将为你显示可用的要领列表,譬喻isEqualTo(expectedDouble)、isNegative()或isGreaterThan(otherDouble),所有这些都可用于double值的校验。这简直是一个很酷的成果。

简述Java测试的自定义断言

自界说断言

拥有AssertJ可能Hamcrest提供的更强大的断言荟萃简直很好,但对付HourRange类来说,这并不是我们真正想要的。匹配器类库的另一个成果是答允你编写本身的断言。这些自界说断言的行为将与AssertJ的默认断言一样,也就是说,你可以或许把它们串在一起。这正是我们接下来要做的。

接下来我们将看到一个自界说断言的示例实现,但此刻让我们先看看最终结果。这次我们将利用(我们本身的)RangeAssert类的assertThat()要领。

@Test 
public void shouldReturnHourlyRanges() throws ParseException { 
       // given 
       Date dateFrom = SDF.parse("2012-07-23 12:00"); 
       Date dateTo = SDF.parse("2012-07-23 15:00"); 
       // when 
       List<Range> ranges = HourlyRange.getRanges(dateFrom, dateTo); 
       // then 
       RangeAssert.assertThat(ranges) 
            .hasSize(3) 
            .isSortedAscending() 
            .hasRange("2012-07-23 12:00", "2012-07-23 13:00") 
            .hasRange("2012-07-23 13:00", "2012-07-23 14:00") 
            .hasRange("2012-07-23 14:00", "2012-07-23 15:00"); 
}

查察本栏目

#p#副标题#e#

即即是上面这么小的一个例子,我们也能看出自界说断言的一些优势。首先要留意的是//then后头的代码确实变少了,可读性也更好了。

将自界说断言应用于更大的代码库时,将显现出其它优势。当我们继承利用自界说断言时,我们将留意到:

可以很容易地复用它们。我们不强迫利用所有断言,但对特定测试用例,我们可以只选择那些重要的断言。

#p#分页标题#e#

特定规模语言属于我们,也就是说,对付特定测试场景,我们可以按照本身的爱好改变它(譬喻,传入Date工具,而不是字符串)。更重要的是这样的改变不会影响到其它测试。

高可读性。毫无疑问,因为断言包罗了许多小断言要领,每一个都只存眷校验的很小的某个方面,因此可觉得校验要领取一个得当的名字。

与私有断言要领对比,自界说断言的独一不敷是事情量要大一些。我们来看一下自界说断言的代码,它是否真的是一个很难的任务。

要建设自界说断言,我们需要担任AssertJ的AbstractAssert类可能其子类。如下所示,我们的RangeAssert担任自AssertJ的ListAssert类。这很正常,因为我们的自界说断言将校验一个Range列表(List<Range>)。

每一个利用AssertJ的自界说断言城市包括建设断言工具、注入被测工具的代码,然后可以利用更多的要领对其举办操纵。如下面的代码所示,结构要领和静态assertThat()要领的参数都是List<Range>。

public class RangeAssert extends ListAssert<Range> { 
 
  protected RangeAssert(List<Range> ranges) { 
    super(ranges); 
  } 
 
  public static RangeAssert assertThat(List<Range> ranges) { 
    return new RangeAssert(ranges); 
  }

此刻我们看看RangeAssert类的其余内容。hasRange()和isSortedAscending()要领(显示在下一个代码列表中)是自界说断言要领的典规范子。它们具有以下配合点:

它们都先挪用isNotNull()要领,查抄被测工具是否为null。确保这个校验不会失败并抛出NullPointerException异常动静。(这一步不是必需的,但发起有这一步)

它们都返回“this”(也就是自界说断言类的工具,对应例子中RangeAssert类的工具)。这使得所有要领可以串在一起。

它们都利用AssertJ Assertions类(属于AssertJ框架)提供的断言要领执行校验。

它们都利用“真实”的工具(由父类ListAssert提供),确保Range列表(List<Range>)被校验。

private final static SimpleDateFormat SDF 
             = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 
 
     public RangeAssert isSortedAscending() { 
            isNotNull(); 
            long start = 0; 
            for (int i = 0; i < actual.size(); i++) { 
                     Assertions.assertThat(start) 
                            .isLessThan(actual.get(i).getStart()); 
                     start = actual.get(i).getStart(); 
            } 
            return this; 
     } 
 
     public RangeAssert hasRange(String from, String to) throws ParseException { 
            isNotNull(); 
 
            Long dateFrom = SDF.parse(from).getTime(); 
            Long dateTo = SDF.parse(to).getTime(); 
 
            boolean found = false; 
            for (Range range : actual) { 
                   if (range.getStart() == dateFrom && range.getEnd() == dateTo) { 
                           found = true; 
                   } 
            } 
            Assertions 
                   .assertThat(found) 
                   .isTrue(); 
            return this; 
 
     } 
}

那么错误信息呢?AssertJ让我们可以很容易地添加错误信息。对付简朴的场景,譬喻值的较量,凡是利用as()要领就足够了,示譬喻下:

Assertions 
            .assertThat(actual.size()) 
            .as("number of ranges") 
            .isEqualTo(expectedSize);

正如你所见到的,as()只是AssertJ框架提供的另一个要领。当测试失败时,它打印下面的信息,我们当即就能知道哪儿错了:

org.junit.ComparisonFailure: [number of ranges] 
    Expected :4 
    Actual :3

有时候只知道被测工具的名字是不足的,我们需要更多信息以相识到底产生了什么。以hasRange()要领为例,当测试失败时,假如可以或许打印所有range就更好了。我们可以通过overridingErrorMessage()要领来实现这种结果:

public RangeAssert hasRange(String from, String to) throws ParseException { 
       ... 
       String errMsg = String.format("ranges\n%s\ndo not contain %s-%s",
                                    actual ,from, to); 
 
       ... 
       Assertions.assertThat(found) 
              .overridingErrorMessage(errMsg) 
              .isTrue(); 
       ... 
}

此刻,当测试失败时,我们可以或许获得很是具体的信息。它的内容取决于Range类的toString()要领。譬喻,它看起来大概是这样的:

HourlyRange{Mon Jul 23 12:00:00 CEST 2012 to Mon Jul 23 13:00:00 CEST 2012}, 
HourlyRange{Mon Jul 23 13:00:00 CEST 2012 to Mon Jul 23 14:00:00 CEST 2012}, 
HourlyRange{Mon Jul 23 14:00:00 CEST 2012 to Mon Jul 23 15:00:00 CEST 2012}] 
do not contain 2012-07-23 16:00-2012-07-23 14:00

总结

#p#分页标题#e#

在本文中,我们接头了许多编写断言的要领。我们从“传统”的方法开始,也就是基于测试框架提供的断言要领。对付许多场景,这已经很是好了。可是正如我们所看到的,它在表达测试意图时,有时候缺少了一些机动性。之后,我们通过引入私有断言要领,取得了一点改进,但仍然不是抱负的办理方案。最后,我们实验利用AssertJ编写自界说断言,我们的测试代码取得了很是好的可读性和可维护性。

假如要我提供一些关于断言的发起,我将会发起以下内容:假如你遏制利用测试框架(譬喻JUnit或TestNG)提供的断言,改为利用匹配器类库(譬喻AssertJ可能Hamcrest),你的测试代码将获得极大的改进。你将可以利用大量可读性很强的断言,淘汰测试代码中//then之后的巨大声明。

尽量编写自界说断言的本钱很是低,但也没有须要因为你会写就必然要利用它们。当你的测试代码的可读性而且/可能可维护性变差时利用它们。按照我的履历,我会勉励你在以下场景中利用自界说断言:

当你发明利用匹配器类库提供的断言无法清晰表达测试意图时;

作为私有断言要领的替代方案。

我的履历汇报我,单位测试险些不需要自界说断言。而在集成测试和端到端测试(成果测试)中,我敢说你必定会发明它们是不行替代的。它们能让你的测试用规模语言措辞(而不是实现语言),它们还封装了技能细节,使测试更易于更新。

查察本栏目

 

    关键字:

天才代写-代写联系方式