JAVA

JUnit + Mock

转载:https://blog.csdn.net/why_still_confused/article/details/54865296

一、JUnit常用注解与断言


1.@Test

@Test注解的public void方法将会被当做测试用例

JUnit每次都会创建一个新的测试实例,然后调用@Test注解方法,任何异常的抛出都会认为测试失败

@Test注解提供2个参数:

  • I.”expected”,定义测试方法应该抛出的异常,如果测试方法没有抛出异常或者抛出了一个不同的异常,测试失败
  • II.”timeout”,如果测试运行时间长于该定义时间,测试失败(单位为毫秒)

@Test(expected = IllegalArgumentException.class) 表示验证这个测试方法将抛出 IllegalArgumentException 异常,如果没有抛出的话,则测试失败。

2.@Before

当编写测试方法时,经常会发现一些方法在执行前需要创建相同的对象

使用@Before注解一个public void 方法会使该方法在所有@Test注解方法被执行前执行(那么就可以在该方法中创建相同的对象),父类的@Before注解方法会在子类的@Before注解方法执行前执行

3.@After

如果在@Before注解方法中分配了额外的资源,那么在测试执行完后,需要释放分配的资源。

使用@After注解一个public void方法会使该方法在所有@Test注解方法执行后被执行(即使在@Before注解方法、@Test注解方法中抛出了异常,所有的@After注解方法依然会被执行),父类中的@After注解方法会在子类@After注解方法执行后被执行

4.@BeforeClass 和 @AfterClass

@BeforeClass 和 @AfterClass。 @BeforeClass 的作用是,在跑一个测试类的所有测试方法之前,会执行一次被 @BeforeClass 修饰的方法,执行完所有测试方法之后,会执行一遍被 @AfterClass 修饰的方法。这两个方法可以用来setup和release一些公共的资源,需要注意的是,被这两个annotation修饰的方法必须是静态的,并且只执行一次。

5.Assert

Assert(断言)在单元测试中十分常用,包括有

  • Assert.assertTrue();
  • Assert.assertEquals();
  • Assert.assertNotNull();

注:

  • 1.Double类型的数据比较时需要用以下语句,其中delta为允许误差
Assert.assertEquals(expected, actual, delta);
  • 2.整体断言:当需要对大量字段进来断言时,可以采用”整体断言”的方法。
  • 3.在实际编写测试代码时,可以先不写断言,而是把结果转为JSON并打印出来,经过人工检验无误后,再将打印结果作为预期结果加上断言,这可以大大节省时间。

二、Mock


1.Mock

Mock通常是指,在测试一个对象A时,我们构造一些假的对象来模拟与A之间的交互,而这些Mock对象的行为是我们事先设定且符合预期。通过这些Mock对象来测试A在正常逻辑,异常逻辑或压力情况下工作是否正常。

引入Mock最大的优势在于:Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果。

先来看看下面这个示例:

从上图可以看出如果我们要对A进行测试,那么就要先把整个依赖树构建出来,也就是BCDE的实例。

一种替代方案就是使用mocks

从图中可以清晰的看出

mock对象就是在调试期间用来作为真实对象的替代品。

mock测试就是在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试。

Mock Object的使用通常会带来以下一些好处:

  • 隔绝其他模块出错引起本模块的测试错误。
  • 隔绝其他模块的开发状态,只要定义好接口,不用管他们开发有没有完成。
  • 一些速度较慢的操作,可以用Mock Object代替,快速返回。

对于分布式系统的测试,使用Mock Object会有另外两项很重要的收益:

  • 通过Mock Object可以将一些分布式测试转化为本地的测试
  • 将Mock用于压力测试,可以解决测试集群无法模拟线上集群大规模下的压力

2.Mock的应用场景

在使用Mock的过程中,发现Mock是有一些通用性的,对于一些应用场景,是非常适合使用Mock的:

  • 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
  • 真实对象很难被创建(比如具体的web容器)
  • 真实对象的某些行为很难触发(比如网络错误)
  • 真实情况令程序的运行速度很慢
  • 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
  • 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)

当然,也有一些不得不Mock的场景:

  • 一些比较难构造的Object:这类Object通常有很多依赖,在单元测试中构造出这样类通常花费的成本太大。
  • 执行操作的时间较长Object:有一些Object的操作费时,而被测对象依赖于这一个操作的执行结果,例如大文件写操作,数据的更新等等,出于测试的需求,通常将这类操作进行Mock。
  • 异常逻辑:一些异常的逻辑往往在正常测试中是很难触发的,通过Mock可以人为的控制触发异常逻辑。

在一些压力测试的场景下,也不得不使用Mock,例如在分布式系统测试中,通常需要测试一些单点(如namenode,jobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量),这时候就需要应用Mock去满足。

JUnit5+Mockitoで単体テストサンプル


サンプルコード作成

テスト対象となる、サンプルクラスを作成。
MainServiceクラスからSubServiceクラスを呼び出す想定としている。

public class MainService {
    private SubService subService = new SubService();

    public int getSum() {
        int sum = 0;
        for (int i : subService.getRandomIntegerList()) {
            sum += i;
        }
        return sum;
    }
}
public class SubService {
    public int[] getRandomIntegerList() {
        return new int[]{8, 7, 2, 3, 6, 4, 5, 8, 4, 0};
    }
}

テストコード作成
MainServicegetSum()をテストするコードを作成する。

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)  // JUnit5でMockito使うには必要
class MainServiceTest {

    @Mock // モック(スタブ)に置き換えたいインスタンスに定義。すべてのメソッドがモックになる
    //@Spy // 一部のメソッドだけモックにしたいときはこれを定義
    private SubService subService;

    @InjectMocks // @Mockでモックにしたインスタンスの注入先となるインスタンスに定義
    private MainService mainService;

    @Test
    public void testGetSum() {
        Mockito.when(this.subService.getRandomIntegerList()) // このモックを呼び出したとき
                .thenReturn(new int[]{10, 20, 30, 40});  //このデータを返すようにモックする
        Assertions.assertEquals(this.mainService.getSum(), 100);
    }
}

@ExtendWithってなに?

JUnit4では@RunWithだったもの。JUnit5ではこれになった。
また、Mockitoを使うときは、Mockitoが専用のExtension(MockitoExtension)を用意しているのでこれを指定しないといけない。