首页 测试 体会 查看内容

如何实现Java测试的自定义断言

2014-8-5 23:13| 发布者: tianzc| 查看: 787| 评论: 0

摘要:   对于测试来说,编写断言似乎很简单:我们只需要对结果和预期进行比较,通常使用断言方法进行判断,例如测试框架提供的assertTrue()或者assertEquals()方法。然而,对于更复杂的测试场景,使用这些基础的断言验证 ...
@Testpublic void shouldReturnHourlyRanges() throws ParseException {// givenDate dateFrom = SDF.parse("2012-07-23 12:00");Date dateTo = SDF.parse("2012-07-23 15:00");// whenList<Range> ranges = HourlyRange.getRanges(dateFrom, dateTo);// thenRangeAssert.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");}  即便是上面这么小的一个例子,我们也能看出自定义断言的一些优势。首先要注意的是//then后面的代码确实变少了,可读性也更好了。  将自定义断言应用于更大的代码库时,将显现出其它优势。当我们继续使用自定义断言时,我们将注意到:  可以很容易地复用它们。我们不强迫使用所有断言,但对特定测试用例,我们可以只选择那些重要的断言。  特定领域语言属于我们,也就是说,对于特定测试场景,我们可以根据自己的喜好改变它(例如,传入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("rangesn%sndo 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  总结  在本文中,我们讨论了很多编写断言的方法。我们从“传统”的方式开始,也就是基于测试框架提供的断言方法。对于很多场景,这已经非常好了。但是正如我们所看到的,它在表达测试意图时,有时候缺少了一些灵活性。之后,我们通过引入私有断言方法,取得了一点改善,但仍然不是理想的解决方案。最后,我们尝试使用AssertJ编写自定义断言,我们的测试代码取得了非常好的可读性和可维护性。  如果要我提供一些关于断言的建议,我将会建议以下内容:如果你停止使用测试框架(例如JUnit或TestNG)提供的断言,改为使用匹配器类库(例如AssertJ或者Hamcrest),你的测试代码将得到极大的改善。你将可以使用大量可读性很强的断言,减少测试代码中//then之后的复杂声明。  尽管编写自定义断言的成本非常低,但也没有必要因为你会写就一定要使用它们。当你的测试代码的可读性并且/或者可维护性变差时使用它们。根据我的经验,我会鼓励你在以下场景中使用自定义断言:  当你发现使用匹配器类库提供的断言无法清晰表达测试意图时;  作为私有断言方法的替代方案。  我的经验告诉我,单元测试几乎不需要自定义断言。而在集成测试和端到端测试(功能测试)中,我敢说你肯定会发现它们是不可替代的。它们能让你的测试用领域语言说话(而不是实现语言),它们还封装了技术细节,使测试更易于更新。  关于作者  Tomek Kaczanowski是CodeWise公司(克拉科夫,波兰)的一名Java开发人员。他专注于代码质量、测试和自动化。他是TDD的狂热者、开源的倡导者和敏捷的崇拜者。具有强烈的分享知识倾向。书的作者、博客和会议发言人。Twitter: @tkaczanowski
  自定义断言  拥有AssertJ或者Hamcrest提供的更强大的断言集合的确很好,但对于HourRange类来说,这并不是我们真正想要的。匹配器类库的另一个功能是允许你编写自己的断言。这些自定义断言的行为将与AssertJ的默认断言一样,也就是说,你能够把它们串在一起。这正是我们接下来要做的。  接下来我们将看到一个自定义断言的示例实现,但现在让我们先看看最终效果。这次我们将使用(我们自己的)RangeAssert类的assertThat()方法。
123

鲜花

握手

雷人

路过

鸡蛋

扫一扫关注最新动态

毒镜头:老镜头、摄影器材资料库、老镜头样片、摄影
爱评测 aipingce.com  
返回顶部