使用JUnit + Mockito进行单元测试

本文只记录了最基础的单元测试方法,更详细的要查看文档

JUnit|Junit5中文文档

JUnit是用于编写和运行可重复的自动化测试开源测试项目框架,这样可以保证我们的代码按与其工作。JUnit可广泛用于工业和作为支架(从命令行)或IDE(如IDE)内单独的java程序。

安装

使用maven安装

<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
</dependency>

JUnit5注解

以下是一些JUnit5中常用的注解

注解 描述
@Test 表示方法是测试方法。
@ParameterizedTest 表示方法是参数化测试
@BeforeEach 表示被注解的方法应在当前类的每个@Test,@RepeatedTest,@ParameterizedTest或@TestFactory方法之前执行。
@AfterEach 表示被注解的方法应在当前类的每个@Test,@RepeatedTest,@ParameterizedTest或@TestFactory方法之后执行。
@BeforeAll 表示被注解的方法应该在当前类的所有@Test,@RepeatedTest,@ParameterizedTest和@TestFactory方法之前执行。与@BeforeEach不同的是每个测试类只执行一次,且只能注释静态方法
@AfterAll 表示被注解的方法应该在当前类的所有@Test,@RepeatedTest,@ParameterizedTest和@TestFactory方法之后执行。与@BeforeEach不同的是每个测试类只执行一次,且只能注释静态方法
@Disabled 用于禁用测试类或测试方法
@DisplayName 声明测试类或测试方法的自定义显示名称。

参数化测试

​ 参数化测试可以用不同的参数多次运行测试。它们和普通的@Test方法一样声明,但是使用@ParameterizedTest注解。另外,您必须声明至少一个将为每次调用提供参数的来源(source)

@ParameterizedTest
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
void palindromes(String candidate) {
    assertTrue(isPalindrome(candidate));
}

测试类

测试类一般都在test文件夹下,而且包结构与被测试类相同

import static org.junit.jupiter.api.Assertions.fail;

import ...
//@SpringBootTest
//如果要使用在SpringBoot中托管,就要加上这个注解
class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

其中,测试类方法中经常使用断言来判断测试结果

断言

JUnit提供了许多断言方法,是org.junit.jupiter.Assertions中的静态方法。以下列举一些常用方法

方法名 作用
assertEquals 比较期望和实际是否相同
assertTrue 判断结果是否为真
assertFalse 判断结果是否为假
assertAll 在assertAll中可以加入多个断言,还可以输出提示信息,类似于断言分组。与使用多个assertEquals不同,在前面的断言失败后,之后的断言仍然会被执行,而且全部断言的结果都会被打印出来

Mockito|Mockito 中文文档

Mockito库能够Mock对象、验证结果以及打桩(stubbing)。Mockito经常与JUnit搭配使用。

Mock的作用

​ Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。

如果我想对A进行单元测试,那A依赖于BC,而C又有自己的依赖链,如果要构建A,就需要吧BCDE都构建出来,这样就失去了单元测试的意义

使用JUnit + Mockito进行单元测试

而mock可以将这些依赖解耦,实现真正的单元测试

使用JUnit + Mockito进行单元测试

创建Mock

Mock

可以用mock(class)方法创建一个mock对象

Spy

可以用spy(class)方法创建一个监控对象。当你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。

即由spy创建的对象,在其方法被插桩之前,都是调用原函数的方法。

注解创建Mock

@Mock

同mock方法

@Spy

同spy方法

@InjectMocks

可以把mockspy注入被注解的对象的@Autowired成员中。因为在单元测试中,无法把mockspy的对象进行注入,所以可以使用@InjectMocks进行注入。使用方法见实例

验证行为

verify(对象[, times(执行次数)]).方法(参数...)

一旦mock对象被创建了,mock对象会记住所有的交互。

测试桩

测试桩有两种风格

when...then

when(对象.方法(...)).[then,thenAnwser,thenReturn,thenThrow](返回)

// 你可以mock具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);

// 测试桩
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
when(mockedList.show(anyString)).then(new Answer() {
			@Override
			public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
				return "mock show: "+invocationOnMock.getArgument(0);
			});

// 输出“first”
System.out.println(mockedList.get(0));

// 抛出异常
System.out.println(mockedList.get(1));
    
// 返回"mock show: test"
System.out.println(mockedList.show("test");

// 因为get(999) 没有打桩,因此输出null
System.out.println(mockedList.get(999));

// 验证get(0)被调用的次数
verify(mockedList).get(0);

do...when

[doReturn,doThrow,doAnswer,doNothing,doCallRealMethod](返回).when(对象).方法(参数)

方法 作用
doReturn(Object) 返回指定的值
doThrow(Throwable...) 抛出一个异常
doAnswer(Answer) 为回调做一个测试桩
doNothing() 什么也不会返回
doCallRealMethod() 会调用mock对象的真实方法,类似于spy的未被插桩的方法

区别

如果是spy对象,要使用do...when来插桩,如果使用when...then,会调用原对象的方法,而do...when则是直接返回,不会调用原方法。

参数匹配器

Mockito以自然的java风格来验证参数值: 使用equals()函数。

当为mock对象做测试桩时,可以为有参数的方法指定参数类型而不仅仅是一个值。

// 使用内置的anyInt()参数匹配器
when(mockedList.get(anyInt())).thenReturn("element");

// 使用自定义的参数匹配器( 在isValid()函数中返回你自己的匹配器实现 )
//ArgumentMatcher<T>实现了isValid()接口
when(mockedList.contains(argThat(isValid()))).thenReturn("element");

// 输出element
System.out.println(mockedList.get(999));

// 你也可以验证参数匹配器
verify(mockedList).get(anyInt());

实例

package com.demo.JUnit;

public class User {
    @Autowired
    public Phone phone;

    private Long id;
    private String username;
    private String password;

    public String callPhoneNum(String num){
        return this.phone.call(num);
    }
    public String usePhone(){
        return this.phone.use();
    }
    
    public String callPhoneNum(String num){
        return this.phone.call(num);
    }
    /**Getter and Setter**/
}
package com.demo.JUnit;

public class Phone {
    
	public String use(){
		return "use phone.";
	}
    
	public String call(String num){
		return "call: "+num;
	}
    
	public String show(Phone phone){
		return phone.toString();
	}

}
package com.demo.JUnit;

inport ...

@SpringBootTest
@RunWith(SpringRunner.class)
class UserTest {

	@Spy
	Phone phone;

	@InjectMocks
	User user;

	@BeforeEach
	public void init(){
		user.setId(1L);
        
        //对方法进行回调插桩,回调函数为一个实现了Anwser的类
		doAnswer(new Answer() {
			@Override
			public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
				return "mock call: "+invocationOnMock.getArgument(0);
			}
		}).when(phone).call(anyString());
        
        //自定义参数类型
        doReturn("mock show").when(phone).show(argThat(new ArgumentMatcher<Phone>() {
			@Override
			public boolean matches(Phone phone) {
                //返回true就返回插桩的方法,返回false就使用原类的方法或返回null
				return true;
			}
		}));
        
		//调用一次usePhone()
		user.usePhone();
	}
	@Test
	@DisplayName("
上一篇:编码


下一篇:原码、反码、补码