在写单元测试的时候经常会遇到一种情况,针对某个方法使用多组入参进行测试,这时可以每组入参写一个测试方法,但代码重复率高不优雅,而 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();
}
}