写给精明 Java 开发者的顶级测试技巧 已翻译 100%

Luchiao 投递于 2015/01/27 09:33 (共 18 段, 翻译完成于 01-15)
阅读 3335
收藏 67
9
加载中

我们经常为我们的业务代码写测试用例,对吧?毫无疑问,大多数答案会落在“不错,但是你知道怎样避免它么?”和“当然,我喜欢测试”之间的某种状态。这里我将介绍一些小窍门,让你明白写好测试用例也是如此简单。这也将帮助你写更少的碎片化的测试,以确保你的应用更加强壮。

同时,如果你的答案是“不,我从来不写测试”,那我也希望这些简单有效的技术让你看到写测试用例的好处,你也将会看到写出明确无价的测试集并不像你想的那样困难。

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 09:18
0

如何写测试用例和什么是管理测试套件的最好实践,如今是一个新的主题。

我们过去已经讨论了很多主题。从如何在编译流程中正确地使用集成测试,到如何在单元测试中模拟测试环境,再到代码覆盖率和如何找出实际需要测试的代码等

今天,我想给你一些新的思路,教你如何从低级到高级构建测试蓝图,组织测试的心理画像。从如何构造一个简单的单元测试用例,到更高层级的工具的应用等。比如: 你会明白模拟(mock)、侦测(spy)和复制粘贴测试代码(copy-pasting 这里估计是指代码复用)等。让我们开始吧!

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/26 22:15
0

AAArrr, 听起来就像是海盗,对吧~~~

在大量的软件开发中,找到合适的设计模式来采用会是一个好的开端。你是否想通过工厂创建对象?亦或者是否需要把你的web应用分为模型,视图和控制器等模块?在这背后经常会有一种模式帮助你实现你的想法。那么,一个典型的测试模式应该看起来是什么样的呢?

在写测试代码时,一个最有效,也最简单的模式是“准备(Arrange)---动作(Act)---断言(Assert)”模型,也叫做 AAA.

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 09:30
0

这个模型的前提是:所有的测试应该遵循这个默认布局。被测系统的所有预置条件和输入应该在测试一开始就安排好。等所有前置条件确定后,我们就可以针对被测系统执行动作(Act)了, 比如执行一个方法或检查一些系统状态。最后,我们还需要对被测系统产生的结果进行检查(Assert)

让我们看一个Java JUnit中使用该模式的测试用例:

    @Test
    public void testAddition() {
    
        // Arrange
        Calculator calculator = new Calculator();
        
        // Act
        int result = calculator.add(1, 2);
        
        // Assert
        assertEquals("Calculator.add returns invalid result", 3, result);
    }

怎么样?这样的代码看起来不错吧?准备(Arrange),动作(Act),断言(Assert)模式可以让你马上明白这个测试用例正在做什么。

偏离这个模式可能会导致更加凌乱的代码结构。 

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/26 15:29
0

请记住迪米特原则

迪米特原则是指各单元之间应该只使用最少的知识(或联系),以保持松耦合的状态。在软件开发中,迪米特原则总是一个设计目标。

迪米特原则可以被描述为以下一系列规则:

  • 在一个方法中,一个类实例可以调用该类中的其它方法。

  • 在一个方法中,一个实例可以查询它自己的数据,而不能是数据的数据。

  • 当一个方法需要参数的时候,第一层的方法可以通过给定的参数被调用。

  • 当一个方法实例化本地变量时,类实例可以调用这些本地变量的方法。

  • 不要调用全局对象的方法。

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 13:58
0

那么,迪米特原则在测试中又意味着什么呢?这意味着你的应用更容易进行单元测试,因为迪米特原则的应用提升了你程序的松耦合度。为了说明该原则如何辅助单元测试,让我们来看看一个不符合该原则的例子:

考虑以下类,我们需要对它进行测试:

public class Foo() {

	public Bar doSomething(Baz aParameter) {
		Bar bar = null;
		if (aParameter.getValue().isValid()) {
			aParameter.getThing().increment();
			bar = BarManager.getBar(new Thing());
		}
		return bar;
	}
}

如果我们尝试测试该方法,因为该类的设计问题,我们将立即遇到一些麻烦。

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 14:05
0

在测试该方法的过程中,我们遇到的第一个困难是:我们调用了一个静态方法 --- BarManager.getBar()。该方法在单元测试的约束下是如何工作的?我们没有办法很容易地知道。还记得我们之前讲的"准备,动作,断言“3A模式吗?这里,在调用 doSomething()方法之前(act 动作),我们没有办法对 BarManager 进行配置(Arrange 准备)。如果 BarManager.getBar() 是非静态的,我们可以传递一个 BarManager 实例给 doSomething() 方法,那样也更容易在测试套件中传递统一的用例值,以对该方法的过程进行更好的和可预测的控制。

在这个方法中,也可以看到我们进行了一个方法链的调用:aParameter.getValue().isValid() 和 aParameter().getThing().increment(). 为了对它们进行测试,我们必须知道对象 aParameter.getValue() 和 aParameter.getThing() 的返回类型是什么?知道了返回类型,我们才可以在测试中构造合适的值对其进行测试。

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 15:23
0

如果我们要这样做(译者注:这里指的是构造合适的值),我们必须非常熟悉这些方法返回的对象,并且我们的单元测试将开始变成一大堆不可维护的脆弱代码。我们将打破单元测试的一个基本规则,那就是测试单个单元,而不是这些单元实现的细节。

我并不是说单元测试只能测试单个类,但是在大多数情况下,将类看作单个单元可能是一个好主意。然而,有时两个或更多的类可以被认为是一个单元。

我将把它留给读者作为练习,以便将这个方法完全重构为更容易测试的方法。但是对于初学者,我们可以将 aParmater.getValue() 对象作为参数传递到方法中。这将满足我们的一些设定,并使该方法更易于测试。

LaingKe
LaingKe
翻译于 2018/12/14 10:20
0

知道什么时候才使用断言

JUnitTesgNG是两个非常优秀的测试框架,它们提供了丰富的断言方法,比如检查值是否相等或不等,是否为空等。

是的,我们也认为断言非常酷,那我们就随性地到处使用吧。且慢,且慢,过度使用断言会使得你的测试用例难以维护,我知道那个坑有多深... ...,它会导致应用不可测,不稳定。

请考虑以下测试方法:

@Test
public void testFoo {
    // Arrange
    Foo foo = new Foo();
    double result = …;

    // Act
    double value = foo.bar( 100.0 );

    // Assert
    assertEquals(value, result);
    assertNotNull( foo.getBar() );
    assertTrue( foo.isValid() );
}

代码乍一看没有问题。我们遵循了AAA模式,也对将要发生的动作进行了正确的断言。这有什么错呢?

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 15:02
0

首先,从这个测试方法名字,testFoo,我们无法得到该测试的任何有效信息,它也不能匹配我们正在检查的任何断言。

那么,如果其中的一个断言失败,我们如何确定被测系统的哪一部分导致的失败呢?是我们的执行动作中 foo.bar(100.0)失败,还是foo.getBar()失败?亦或者是foo.isValid()失败?如果不通过对测试代码的深入debug分析以检查到底发生了什么,那我们就没有办法知道。

xiaoaiwhc1
xiaoaiwhc1
翻译于 2018/10/23 14:36
0
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(1)

叫我刀刀
叫我刀刀
低级开发者 路过
返回顶部
顶部