单元测试—参数化测试


在写单元测试的时候经常会遇到一种情况,针对某个方法使用多组入参进行测试,这时可以每组入参写一个测试方法,但代码重复率高不优雅,而 junit 从 4.0 开始提供了一种叫做参数化测试的方式专门处理这样情况。

单元测试 – 参数化测试

普通的参数化测试

package com.myz.util;

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class JunitParameterTest {
    
    /**
     * 1.更改默认的运行器为@RunWith(Parameterized.class)
     * 2.声明变量来存放预期值和结果值
     * 3.声明一个返回值为collection的公共静态方法,并使用@Parameters进行修饰
     * 4.为测试类声明一个带参数的公共构造函数,并在其中为之声明变量赋值
     */
    
    int expected=0;
    int input1=0;
    int input2=0;
    
    @Parameters
    public static Collection<Object[]> t(){//保存参数
        return Arrays.asList(new Object[][]{
            {3,1,2},
            {4,2,2}
        });
    }

    public JunitParameterTest(int expected, int input1, int input2) {
        this.expected = expected;
        this.input1 = input1;
        this.input2 = input2;
    }
    
    @Test
    public void testAdd(){//将参数传入,测试
        assertEquals(expected,new Calculate().add(input1, input2));
    }
}

Springboot中的参数化测试

注意 JUnit4 不支持多个 Runner,用了 @RunWith(Parameterized.class) 之后就没法再用 @RunWith(SpringRunner.class),但是可以通过 @Before 中的 TestContextManager 来实现 SpringRunner 同样的效果

// 我们需要测试的方法
@PostMapping("/recall_message")
public Response recallMessage(@RequestBody RecallMessageDTO recallMessageDTO) {
    log.info("[聊天室][单条消息撤回]请求参数:[recallMessageDTO{}]", recallMessageDTO);
    recallMessageDTO.valid();

    sendMessageService.recallMessage(recallMessageDTO);
    return Response.ok();
}

RecallMessageDTO中定义了一个valid方法

public void valid() {
    if (StringUtils.isEmpty(fromUserId)) {
    	throw new BusinessException("消息发送人ID不能为空");
    }

    if (StringUtils.isEmpty(targetId)) {
    	throw new BusinessException("消息接收人ID不能为空");
    }
    ...

我们在测试valid()方法的时候,如果每种情况都写一个测试方法的话很不优雅,因为RecallMessageDTO中的valid()方法有很多的属性需要检验。这个时候我们就可以使用参数化测试了。

@RunWith(Parameterized.class)
@SpringBootTest(classes = Application .class)
public class TimlineServiceTest {
    @InjectMocks
    private SendMessageService sendMessageService;
 
    private TestContextManager testContextManager;
 
    private RecallMessageDTO recallMessageDTO;
 
    private String message;
 
    @Rule
    public ExpectedException thrown= ExpectedException.none();
 
    private ObjectMapper objectMapper;
 
    @InjectMocks
    private MessageController controller;
 
    private MockMvc mockMvc;
 
    //参数数组,数组中每个元素将会被用来构造一个入参实例,每个入参实例对应一个测试用例,
    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        Object[][] objects = {
                {"", "", "消息发送人ID不能为空"},
                {"fb_1904", "", "消息接收人ID不能为空"}
        };
        return Arrays.asList(objects);
    }
 
    //构造函数,使用上面的参数数组初始化入参
    public TimlineServiceTest(String fromUserId, String targetId, String message) {
        recallMessageDTO = new RecallMessageDTO();
        recallMessageDTO.setFromUserId(fromUserId);
        recallMessageDTO.setTargetId(targetId);
        this.message = message;
    }
 
    //功能相当于 @RunWith(SpringRunner.class) ,否则无法注入bean,这里同时还可以给入参初始化一些固定值
    @Before
    public void setUp() throws Exception {
        // equals to @RunWith(SpringRunner.class) in case that JUnit4 doesn’t accept multiple runners
        this.testContextManager = new TestContextManager(getClass());
        //this.testContextManager.prepareTestInstance(this);
 
        MockitoAnnotations.initMocks(this);
        objectMapper = new ObjectMapper();
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        MappingJackson2HttpMessageConverter convert = new MappingJackson2HttpMessageConverter(objectMapper);
        this.mockMvc = MockMvcBuilders
                .standaloneSetup(controller)
                //.setControllerAdvice(new GlobalExceptionResolver())
                .setMessageConverters(convert)
                .build();
    }
 
    //单元测试方法体
    @Test
    public void tst() throws Exception {
        thrown.expect(NestedServletException.class);
        thrown.expectMessage(message);
 
        MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders
                .post("/v1/recall_message")
                .accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(recallMessageDTO));
 
        MvcResult mvcResult = mockMvc.perform(requestBuilder)
                .andDo(print()) //打印输出发出请求的详细信息
                .andExpect(status().isOk())
                .andReturn();
    }
}

文章作者: WangQingLei
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WangQingLei !
  目录