关闭

单元测试之简单Mock实战

发表于:2024-5-15 09:54

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:佚名    来源:51CTO博客

  这里就写一些关键代码。
  service是最需要关注的地方,重要的逻辑一般都在这里;同时又有诸多的外部依赖,用这一层做实际mock的实例是最合适的。
  /**
   * @author zhangpeng34
   * Created on 2019/1/18 下午9:15
  **/
  @Service
  public class UserServiceImpl implements UserService {
      @Autowired
      UserDao userDao;
      @Override
      public User findUserById(Long id) {
          return userDao.findById(id).orElse(null);
      }
      @Override
      public User addUser(String name) {
          if(StringUtils.isEmpty(name)){
              return null;
          }
          User user = new User();
          user.setName(name);
          return userDao.save(user);
      }
  }
  这个service很简单,这里针对里面的addUser方法写一些对应的单测:
  /**
   *
   * 单元测试,测试的目的是对java代码逻辑进行测试。
   * 单纯的逻辑测试,不应该加载外部依赖,所有的外部依赖应该mock掉,只关注本身逻辑。
   * 例如,需要测试service层时,所依赖的dao等,应提前mock掉,设置好测试需要的输入和输出即可。
   * dao层的逻辑应由dao层的测试保证,service层默认dao层是正确的。
  **/
  @RunWith(MockitoJUnitRunner.class)
  public class UserServiceTests {
      //mock注解创建一个被mock的实例
      @Mock
      UserDao userDao;
      //InjectMocks代表创建一个实例,其他带mock注解的示例将被注入到该实例用。
      //可以用该注解创建要被测试的实例,将实例所需的依赖用mock注解创建,即可mock掉依赖
      @InjectMocks
      UserServiceImpl UserServiceImpl;
      String addUserName = "testAddUser";
      /**
       * 初始化时设置一些需要mock的方法和返回值
       * 这里的设置表示碰到userDao的save方法,且参数为任一User类的实例时,返回提前预设的值
       */
      @Before
      public void init(){
          User user =new User();
          user.setId(1L);
          user.setName(addUserName);
          Mockito.when(userDao.save(any(User.class)))
                  .thenReturn(user);
      }
      //正向流程
      @Test
      public void testAddUser(){
          User user = UserServiceImpl.addUser(addUserName);
          Assert.assertEquals(addUserName,user.getName());
          Assert.assertEquals(1L,user.getId().longValue());
      }
      //异常分支,name为null
      @Test
      public void testAddUserNull(){
          User user = UserServiceImpl.addUser(null);
          Assert.assertEquals(null,user);
      }
      //将各个分支都写出test和assert
      //............
  }
  集成测试
  上面所说的都是单元测试,但是实际开发中,我们往往不光需要单元测试(甚至不需要单元测试。。。。),还需要有集成测试,来测试我们程序的整体运行情况。
  集成测试并不是联调,集成测试用依然可以mock第三方依赖。
  在我们的工程里,一般只要实际启动整个spring容器的测试代码,都是集成测试。
  Controller层是较为适合集成测试的地方,这里用Controller层来做集成测试的示例。
  @RestController
  public class UserController {
      @Autowired
      UserService userService;
      @PostMapping(value = "/user")
      public Object register(String name) {
          return userService.addUser(name);
      }
      @GetMapping(value = "/user/{userId}")
      public Object getUserInfo(@PathVariable Long userId) {
          User user = userService.findUserById(userId);
          if (user != null) {
              return user;
          }
          return "fail";
      }
      @PostMapping(value = "/user/login")
      public Object login(Long id, String pwd) {
          User user =  userService.findUserById(id);
          if(user!=null){
              return user;
          }
          return "fail";
      }
  }
  下面写一个这次集成测试用的spring测试文件,位置如下:
  测试代码:
  配置文件如下:
  spring.profiles=it
  server.port=9898
  spring.h2.console.enabled=true
  spring.h2.console.path=/h2
  spring.datasource.driver-class-name=org.h2.Driver
  spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1
  spring.datasource.username=sa
  spring.datasource.password=sa
  spring.datasource.max-wait=10000
  spring.datasource.max-active=5
  spring.datasource.test-on-borrow=true
  spring.datasource.test-while-idle = true
  spring.datasource.validation-query = SELECT 1
  # jpa
  spring.jpa.hibernate.ddl-auto=update
  #spring.jpa.hibernate.ddl-auto= create-drop
  spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
  spring.jpa.show-sql=true
  spring.jpa.generate-ddl=true
  这个配置文件的主要作用就是将程序连接的DB换成一个内存数据库h2,这样就不用在测试时受DB的掣肘了。
  @RunWith(SpringRunner.class)
  @SpringBootTest
  @ActiveProfiles("it")
  @AutoConfigureMockMvc
  public class UserControllerTests {
      @Autowired
      private MockMvc mvc;
      @Autowired
      UserDao userDao;
      Long userId;
      @Before
      public void init(){
          User user = new User();
          user.setName("111");
          userId = userDao.save(user).getId();
          System.out.println(userId);
      }
      /**
       * 测试/user/{userId}
       * @throws Exception
       */
      @Test
      public void testGetUser() throws Exception {
          //success
          this.mvc.perform(get("/user/"+userId)).andExpect(status().isOk())
                  .andExpect(content().json("{ \"id\": "+userId+", \"name\": \"111\" }"));
          //fail
          this.mvc.perform(get("/user/"+(userId+100))).andExpect(status().isOk())
                  .andExpect(content().string("fail"));
      }
      /**
       * 测试login
       * @throws Exception
       */
      @Test
      public void exampleTest2() throws Exception {
          //success
          this.mvc.perform(post("/user/login").param("id",userId.toString()).param("pwd","11"))
                  .andExpect(status().isOk())
                  .andExpect(content().json("{ \"id\": "+userId+", \"name\": \"111\" }"));
          //fail
          this.mvc.perform(post("/user/login").param("id",userId.toString()+"11").param("pwd","11"))
                  .andExpect(status().isOk())
                  .andExpect(content().string("fail"));
      }
  }
  @ActiveProfiles("it") 这个注解就是制定本测试代码加载的配置文件,”it“指 文件名 application-XX.properties 中间的xx,spring会自动根据名称去加载对应的配置文件。
  init()?方法就是在内存数据库中构造自己需要的数据,这是集成测试最常见的步骤。
  后面的测试代码就不需要解释太多了。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号