单元测试案例

打印 上一主题 下一主题

主题 531|帖子 531|积分 1593

在日常的springboot项目开发中,总会需要写一些单元测试用例,一些单元测试的方法用的比较少,编写时又需要去查询,因此在此总结一些测试案例
Junit是目前主流的单元测试框架,我,常用的为Junit4,以下测试案例是基于Junit4来编写
 
单元测试的目的与好处

  1、单元测试能有效地帮你发现代码中的 bug
    单元测试往往需要走通方法中的各条路径,通过单元测试常常会发现代码中的很多考虑不全面的地方
  2、写单元测试能帮你发现代码设计上的问题
    对于一段代码,如果很难为其编写单元测试,或者单元测试写起来很吃力,需要依靠单元测试框架里很高级的特性才能完成,那往往就意味着代码设计得不够合理
  3、单元测试是对集成测试的有力补充
    对于一些复杂系统来说,集成测试也无法覆盖得很全面。复杂系统往往有很多模块。每个模块都有各种输入、输出、异常情况,组合起来,整个系统就有无数测试场景需要模拟,无数的测试用例需要设计,再强大的测试团队也无法穷举完备
  4、写单元测试的过程本身就是代码重构的过程
    设计和实现代码的时候,我们很难把所有的问题都想清楚。而编写单元测试就相当于对代码的一次自我 Code Review,在这个过程中,我们可以发现一些设计上的问题(比如代码设计的不可测试)以及代码编写方面的问题(比如一些边界条件处理不当)等,然后针对性的进行重构。
 
所需依赖
  1.         <dependency>
  2.             <groupId>org.springframework.boot</groupId>
  3.             <artifactId>spring-boot-starter-test</artifactId>
  4.             <exclusions>
  5.                 <exclusion>
  6.                     <groupId>org.junit.jupiter</groupId>
  7.                     <artifactId>junit-jupiter-api</artifactId>
  8.                 </exclusion>
  9.                 <exclusion>
  10.                     <groupId>org.junit.jupiter</groupId>
  11.                     <artifactId>junit-jupiter</artifactId>
  12.                 </exclusion>
  13.                 <exclusion>
  14.                     <groupId>org.junit.vintage</groupId>
  15.                     <artifactId>junit-vintage-engine</artifactId>
  16.                 </exclusion>
  17.             </exclusions>
  18.             <scope>test</scope>
  19.         </dependency>
  20.         
  21.         <dependency>
  22.             <groupId>junit</groupId>
  23.             <artifactId>junit</artifactId>
  24.             <scope>test</scope>
  25.         </dependency>
  26.         
  27.         <dependency>
  28.             <groupId>org.powermock</groupId>
  29.             <artifactId>powermock-module-junit4</artifactId>
  30.             <version>2.0.9</version>
  31.             <scope>test</scope>
  32.         </dependency>
  33.         <dependency>
  34.             <groupId>org.powermock</groupId>
  35.             <artifactId>powermock-api-mockito2</artifactId>
  36.             <version>2.0.9</version>
  37.             <scope>test</scope>
  38.         </dependency>
  39.         <dependency>
  40.             <groupId>org.mockito</groupId>
  41.             <artifactId>mockito-core</artifactId>
  42.             <version>3.12.4</version>
  43.             <scope>test</scope>
  44.         </dependency>
复制代码
 
 常见注解
  1. <strong>  @Before</strong>:初始化方法,在任何一个测试方法执行之前,必须执行的代码
复制代码
  1. <strong>  @BeforeClass</strong>:针对所有测试,也就是整个测试类中,在所有测试方法执行前,都会先执行由它注解的方法,<strong>而且只执行一次</strong>,修饰符必须是 public static void
复制代码
  1. <strong>  @After</strong>:释放资源,在任何一个测试方法执行之后,需要进行的收尾工作
复制代码
  1. <strong>  @AfterClass</strong>:针对所有测试,也就是整个测试类中,在所有测试方法都执行完之后,才会执行由它注解的方法,<strong>而且只执行一次</strong>。修饰符必须是 public static void
复制代码
  1. <strong>  @Test</strong>:测试方法,表明这是一个测试方法。在 JUnit 中将会自动被执行。对与方法的声明也有如下要求:名字可以随便取,没有任何限制,<strong>但是返回值必须为 void ,而且不能有任何参数<br></strong>
复制代码
  1. @RunWith(MockitoJUnitRunner.class)
  2. public class AnnotationTest {
  3.     public static final Logger log = LoggerFactory.getLogger(AnnotationTest.class);
  4.     @Before
  5.     public void init(){
  6.         log.info("@Before call");
  7.     }
  8.     @BeforeClass
  9.     public static void beforeClass(){
  10.         log.info("@BeforeClass call");
  11.     }
  12.     @After
  13.     public void after(){
  14.         log.info("@After call");
  15.     }
  16.     @AfterClass
  17.     public static void afterClass(){
  18.         log.info("@AfterClass call");
  19.     }
  20.     @Test
  21.     public void test01(){
  22.         log.info("test01 call");
  23.     }
  24.     @Test
  25.     public void test02(){
  26.         log.info("test02 call");
  27.     }
  28. }
复制代码
方法执行结果如下所示,两个测试方法,@Before和@After都执行了两次,而@BeforeClass和@AfterClass都只执行了一次;执行顺序:@BeforeClass --> @Before  --> @Test  --> @After  --> @AfterClass

断言

Junit提供的断言主要有如下几种类型:
  Assert.assertTrue():验证条件是否为真
  Assert.assertFalse():验证条件是否为假
  Assert.assertEquals():验证两个值是否相等
  Assert.assertNotNull():验证对象是否为空
  Assert.assertThrows():验证执行代码是否抛出了指定类型的异常
verify:
  1. @Test
  2.     public void test(){
  3.         List list = Mockito.mock(List.class);
  4.         list.add("a");
  5.         //Mockito.times()不写默认指调用1次
  6.         Mockito.verify(list).add("a");
  7.         Mockito.verify(list,Mockito.times(1)).add("a");
  8.         //判读list.add方法被调用2次
  9.         list.add("a");
  10.         Mockito.verify(list,Mockito.times(2)).add("a");
  11.     }
复制代码
 
@Mock与@InjectMocks的区别

  @Mock: 创建一个Mock.
  @InjectMocks: 创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock注解创建的mock将被注入到用该实例中。
Mockito的初始化

当我们要使用注解(比如@Mock)来mock对象的使用,就要初始化Mockito,这样用@Mock标注的对象才会被实例化,否则直接使用会报Null指针异常。其有两种初始化的方法:
  1、使用MockitoAnnotations.initMocks方法
  1. public class InitMockA {
  2.     public static final Logger log = LoggerFactory.getLogger(InitMockA.class);
  3.    
  4.     @Before
  5.     public void init(){
  6.         MockitoAnnotations.initMocks(this);
  7.     }
  8.    
  9.     @Test
  10.     public void test01(){
  11.         log.info("run test01");
  12.     }
  13. }
复制代码
  2、类上使用@RunWith(MockitoJUnitRunner.class)
  1. @RunWith(MockitoJUnitRunner.class)
  2. public class InitMockB {
  3.     public static final Logger log = LoggerFactory.getLogger(InitMockB.class);
  4.     @Test
  5.     public void test01(){
  6.         log.info("run test01");
  7.     }
  8. }
复制代码
 
 
案例

  1、常见简单测试案例
  1. @Service
  2. public class AComponent {
  3.     @Value("${test-case.key}")
  4.     private String key;
  5.     @Autowired
  6.     private UserInfoMapper userInfoMapper;
  7.     @Autowired
  8.     private BComponent bComponent;
  9.     public UserInfo normalMethod(Integer id){
  10.         UserInfo userInfo = userInfoMapper.getById(id);
  11.         System.out.println(userInfo.getSex());
  12.         return userInfo;
  13.     }
  14.     public boolean compareUser(Integer originId,Integer targetId){
  15.         UserInfo originUser = userInfoMapper.getById(originId);
  16.         UserInfo targetUser = userInfoMapper.getById(targetId);
  17.         return originUser.getSex().equals(targetUser.getSex());
  18.     }
  19.     public void complicatedService(ServiceEntity serviceEntity, String name){
  20.         //...
  21.         bComponent.complicatedMethod(serviceEntity,name);
  22.         //...
  23.     }
  24.     public UserInfo exceptionService(Integer id){
  25.         UserInfo userInfo = null;
  26.         try {
  27.             userInfo = bComponent.exceptionMethod(id);
  28.         }catch (Exception e){
  29.             return null;
  30.         }
  31.         return userInfo;
  32.     }
  33.     public void updateUserInfo(UserInfo userInfo){
  34.         userInfoMapper.updateUserInfo(userInfo);
  35.     }
  36.     public String getKey(){
  37.         return key;
  38.     }
  39. }
复制代码
  测试方法:
  1. @RunWith(MockitoJUnitRunner.class)
  2. public class SimpleTest {
  3.     @Mock
  4.     private UserInfoMapper userInfoMapper;
  5.     @Mock
  6.     private BComponent bComponent;
  7.     /**
  8.      * 创建一个实例,简单的说是这个Mock可以调用真实代码的方法,其余用@Mock注解创建的mock将被注入到用该实例中
  9.      */
  10.     @InjectMocks
  11.     AComponent aComponent;
  12.     @Before
  13.     public void intit(){
  14.         // 为aComponent注入对象
  15.         ReflectionTestUtils.setField(aComponent,"key","abcdefg");
  16.     }
  17.     /**
  18.      * 最常见的测试用例,mock返回值
  19.      */
  20.     @Test
  21.     public void normalTest(){
  22.         Integer id = 1;
  23.         Mockito.when(userInfoMapper.getById(id)).thenReturn(getManUserInfo());
  24.         aComponent.normalMethod(id);
  25.         Mockito.verify(userInfoMapper).getById(id);
  26.     }
  27.     /**
  28.      * 测试同一个方法,入参不同的,返回值也不相同
  29.      */
  30.     @Test
  31.     public void differentParamTest(){
  32.         Integer user1 = 1;
  33.         Integer user2 = 2;
  34.         Mockito.when(userInfoMapper.getById(user1)).thenReturn(getManUserInfo());
  35.         Mockito.when(userInfoMapper.getById(user2)).thenReturn(getFemaleUserInfo());
  36.         boolean result = aComponent.compareUser(user1,user2);
  37.         Assert.assertFalse(result);
  38.     }
  39.     /**
  40.      * 入参比较复杂的时候可以使用Mockito.any,入参也是可以mock的
  41.      * Mockito.any()可以有多种类型,比如:
  42.      *      Mockito.any(ServiceEntity.class);
  43.      *      Mockito.anyString();
  44.      *      Mockito.anyCollection();
  45.      *      Mockito.anyList();
  46.      */
  47.     @Test
  48.     public void paramComplicated(){
  49.         aComponent.complicatedService(Mockito.any(),Mockito.anyString());
  50.         Mockito.verify(bComponent).complicatedMethod(Mockito.any(),Mockito.anyString());
  51.     }
  52.     /**
  53.      * 当方法中出现异常的时候,可以使用doThrow方法自己制造异常
  54.      */
  55.     @Test
  56.     public void exceptionTest(){
  57.         Integer id = 1;
  58.         Mockito.doThrow(new IllegalArgumentException()).when(bComponent).exceptionMethod(id);
  59.         UserInfo userInfo = aComponent.exceptionService(id);
  60.         Assert.assertTrue(userInfo == null);
  61.     }
  62.     @Test
  63.     public void keyTest(){
  64.         String key = aComponent.getKey();
  65.         Assert.assertTrue("abcdefg".endsWith(key));
  66.     }
  67.     private UserInfo getManUserInfo(){
  68.         UserInfo userInfo = new UserInfo();
  69.         userInfo.setId(1);
  70.         userInfo.setUserName("zhansan");
  71.         userInfo.setAge(12);
  72.         userInfo.setSex("M");
  73.         return userInfo;
  74.     }
  75.     private UserInfo getFemaleUserInfo(){
  76.         UserInfo userInfo = new UserInfo();
  77.         userInfo.setId(2);
  78.         userInfo.setUserName("李四");
  79.         userInfo.setAge(12);
  80.         userInfo.setSex("F");
  81.         return userInfo;
  82.     }
  83. }
复制代码
 
  2、Mock静态方法:
  1. @Service
  2. public class StaticComponent {
  3.     /**
  4.      * 这里为是shiro登录时,存放登录对象信息的位置
  5.      * @return
  6.      */
  7.     public String getUserId(){
  8.         Subject localSubject = ThreadContext.getSubject();
  9.         String userId = (String) localSubject.getPrincipals().getPrimaryPrincipal();
  10.         return userId;
  11.     }
  12. }
复制代码
  测试方法:
  1. /**
  2. * 静态Mock需要使用PowerMockRunner
  3. * 并使用PrepareForTest,该测试代表不会实际执行ThreadContext这个类
  4. */
  5. @RunWith(PowerMockRunner.class)
  6. @PrepareForTest({ThreadContext.class})
  7. public class StaticComponentTest {
  8.     @InjectMocks
  9.     StaticComponent staticComponent;
  10.     @Test
  11.     public void getUserId(){
  12.         String userId = "12345";
  13.         PowerMockito.mockStatic(ThreadContext.class);
  14.         Subject localSubject = PowerMockito.mock(Subject.class);
  15.         when(ThreadContext.getSubject()).thenReturn(localSubject);
  16.         PrincipalCollection principalCollection = PowerMockito.mock(PrincipalCollection.class);
  17.         when(localSubject.getPrincipals()).thenReturn(principalCollection);
  18.         when(principalCollection.getPrimaryPrincipal()).thenReturn("12345");
  19.         String resultUserId = staticComponent.getUserId();
  20.         Assert.assertTrue(userId.equals(resultUserId));
  21.     }
  22. }
复制代码
  
  3、方法内部有new对象的测试
  1. @Service
  2. public class CreateComponent {
  3.     @Autowired
  4.     private RestTemplate restTemplate;
  5.     public MethodResult addUser(UserInfo user)throws Exception{
  6.         Map<String,String> map = new HashMap<>();
  7.         map.put("name",String.valueOf(user.getId()));
  8.         map.put("email",user.getEmail());
  9.         map.put("nickname",user.getNickname());
  10.         MultiValueMap<String, String> header = new LinkedMultiValueMap();
  11.         header.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
  12.         HttpEntity request = new HttpEntity(JSONObject.toJSONString(map), header);
  13.         String url = "http://127.0.0.1:8088/add/user";
  14.         try{
  15.             ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);
  16.             MethodResult result = JSONObject.parseObject(response.getBody(), MethodResult.class);
  17.             return result;
  18.         }catch (Exception e){
  19.             e.printStackTrace();
  20.             return null;
  21.         }
  22.     }
  23. }
复制代码
  测试方法
  1. @RunWith(PowerMockRunner.class)
  2. public class CreateComponentTest {
  3.     @Mock
  4.     private  RestTemplate restTemplate;
  5.     @InjectMocks
  6.     private CreateComponent component;
  7.     @Test
  8.     public void createTest()throws Exception{
  9.         UserInfo param = new UserInfo();
  10.         param.setNickname("zhangsan");
  11.         param.setEmail("zhangsan@supermap.com");
  12.         param.setId(123);
  13.         Map<String,String> map = new HashMap<>();
  14.         map.put("name",String.valueOf(param.getId()));
  15.         map.put("email",param.getEmail());
  16.         map.put("nickname",param.getNickname());
  17.         MultiValueMap<String, String> header = new LinkedMultiValueMap();
  18.         header.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
  19.         HttpEntity request = new HttpEntity(JSONObject.toJSONString(map), header);
  20.         PowerMockito.whenNew(HttpEntity.class).withAnyArguments().thenReturn(request);
  21.         ResponseEntity responseEntity = PowerMockito.mock(ResponseEntity.class);
  22.         Mockito.when(restTemplate.postForEntity("http://127.0.0.1:8088/add/user",request,String.class)).thenReturn(responseEntity);
  23.         MethodResult result = new MethodResult();
  24.         result.setSucceed(true);
  25.         PowerMockito.when(responseEntity.getBody()).thenReturn(JSONObject.toJSONString(result));
  26.         MethodResult methodResult = component.addUser(param);
  27.         Assert.assertTrue(methodResult.isSucceed());
  28.     }
  29. }
复制代码
 
  4:方法过于复杂跳过内部私有方法,再单独测试私有方法
  1. @Service
  2. public class PrivateComponent {
  3.     public Integer entranceMethod(Integer i){
  4.         methodA(i);
  5.         System.out.println("call methodA end");
  6.         i = methodB(i);
  7.         System.out.println("call methodB end");
  8.         i = methodC(i);
  9.         System.out.println("call methodC end");
  10.         return i;
  11.     }
  12.     private void methodA(Integer i){
  13.         System.out.println("do methodA i = " + i);
  14.         methodA2(i);
  15.     }
  16.     private void methodA2(Integer i){
  17.         System.out.println("do methodA2 i = " + i);
  18.     }
  19.     private Integer methodB(Integer i){
  20.         ++i;
  21.         System.out.println("do methodB");
  22.         return i;
  23.     }
  24.     private Integer methodC(Integer i){
  25.         ++i;
  26.         System.out.println("do methodC");
  27.         return i;
  28.     }
  29. }
复制代码
  测试方法:
  1. @RunWith(PowerMockRunner.class)
  2. @PrepareForTest(PrivateComponent.class)
  3. public class PrivateComponentTest {
  4.     @InjectMocks
  5.     private PrivateComponent privateComponent;
  6.     /**
  7.      * 测试复杂的方法,跳过方法内部的私有方法:1、该私有方法没有返回值
  8.      * @throws Exception
  9.      */
  10.     @Test
  11.     public void jumpPrivateMethodTest()throws Exception{
  12.         PrivateComponent component = PowerMockito.spy(privateComponent);
  13.         PowerMockito.doNothing().when(component,"methodA",1);
  14.         Integer i = component.entranceMethod(1);
  15.         System.out.println(i);
  16.         Assert.assertTrue(i == 3);
  17.     }
  18.     /**
  19.      * 测试复杂的方法,跳过方法内部的私有方法:2、该私有方法有返回值
  20.      * @throws Exception
  21.      */
  22.     @Test
  23.     public void jumpPrivateMethodTest2()throws Exception{
  24.         PrivateComponent component = PowerMockito.spy(privateComponent);
  25.         PowerMockito.doReturn(5).when(component,"methodB", Mockito.any());
  26.         Integer i = component.entranceMethod(1);
  27.         System.out.println(i);
  28.         Assert.assertTrue(i == 6);
  29.     }
  30.     /**
  31.      * 测试复杂方法,单独测试方法内部的私有方法
  32.      * @throws Exception
  33.      */
  34.     @Test
  35.     public void privateMethodTest()throws Exception{
  36.         PrivateComponent component = PowerMockito.spy(privateComponent);
  37.         Method method = PowerMockito.method(PrivateComponent.class,"methodB",Integer.class);
  38.         Integer i = (Integer) method.invoke(component,1);
  39.         System.out.println("result i = " + i);
  40.         Assert.assertTrue(i == 2);
  41.     }
  42. }
复制代码
 
  5、对controller进行测试
  1. @RestController
  2. @RequestMapping("/api/user")
  3. public class AController {
  4.     @Autowired
  5.     private AComponent aComponent;
  6.     @GetMapping(value = "/info")
  7.     public UserInfo testA1(Integer id){
  8.         UserInfo userInfo = aComponent.normalMethod(id);
  9.         return userInfo;
  10.     }
  11.     @PostMapping(value = "/update")
  12.     public String updateUserInfo(@RequestBody UserInfo userInfo){
  13.         aComponent.updateUserInfo(userInfo);
  14.         return "success";
  15.     }
  16. }
复制代码
  测试方法:
  1. @RunWith(MockitoJUnitRunner.class)
  2. public class ControllerTest {
  3.     @InjectMocks
  4.     private AController aController;
  5.     @Mock
  6.     private AComponent aComponent;
  7.     private MockMvc mockMvc;
  8.     @Before
  9.     public void setUp() {
  10.         mockMvc = MockMvcBuilders.standaloneSetup(aController).build();
  11.     }
  12.     /**
  13.      * 使用http GET方法调用的方式来测试controller
  14.      * @throws Exception
  15.      */
  16.     @Test
  17.     public void getControllerMvcTest() throws Exception {
  18.         Integer id = 1;
  19.         Mockito.when(aComponent.normalMethod(id)).thenReturn(getManUserInfo());
  20.         MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/user/info?id="+id))
  21.                 .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
  22.         String content = mvcResult.getResponse().getContentAsString();
  23.         Assert.assertNotNull(content);
  24.     }
  25.     /**
  26.      * 使用http POST方法调用的方式来测试controller
  27.      * @throws Exception
  28.      */
  29.     @Test
  30.     public void postControllerMvcTest() throws Exception {
  31.         UserInfo userInfo = getManUserInfo();
  32.         MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/api/user/update")
  33.                 .contentType(MediaType.APPLICATION_JSON).content(JSON.toJSONString(userInfo)))
  34.                 .andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
  35.         String content = mvcResult.getResponse().getContentAsString();
  36.         Assert.assertTrue("success".equals(content));
  37.     }
  38.     private UserInfo getManUserInfo(){
  39.         UserInfo userInfo = new UserInfo();
  40.         userInfo.setId(1);
  41.         userInfo.setUserName("zhansan");
  42.         userInfo.setAge(12);
  43.         userInfo.setSex("M");
  44.         return userInfo;
  45.     }
  46. }
复制代码
 代码地址:https://github.com/x104859/test-case

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表