工作3年,还不会写单元测试?新技能get(三)

发表于:2022-1-30 09:07

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

 作者:Java码农之路    来源:今日头条

  Mock框架简介
  工欲善其事必先利其器,选择一个合适的Mock框架与手动实现Stub比,往往能够让我们的单测事半功倍。
  需要说明的是,Mock框架并不是必须的。正如上文所说,我们可以实现Stub代码来隔离依赖,当需要使用到Mock对象时,我们只需要对Stub的实现稍作修改即可。
  市面上有许多Mock框架可供选择,如常见的Mockito,PowerMock,Spock,EasyMock,JMock等。如何选择合适的框架呢?
  如果你想半个小时就能上手,不妨试试Mockito,绝对如丝般顺滑!当然,如果有时间并且对Groovy语言感兴趣,不妨花半天时间了解下Spock,能让测试代码更加精简。
  以下是几种常用的Mock框架对比,不知道怎么选时,不妨根据现状,需要注意的是,大部分Mock框架都不支持Mock静态方法。
  单测实战
  写单测一般包括3个部分,即Given(Mock外部依赖&准备Fake数据),When(调用被测方法)以及Then(断言执行结果),这种写法和Spock的语法结构也是一致的。
  为了更好的理解单元测试,笔者将针对如下代码,分别使用Mockito和Spock写一个简单的示例,让大家感受一下两者的各自的特点和不同。
  Copy@Service
  @AllArgsConstructor
  @NoArgsConstructor
  public class UserServiceImpl implements UserService {
      @Autowired
      private UserDao userDao;
      @Autowired
      private EmailService emailService;
      public ResultDTO<Long> addUser(AddUserRequest request) {
          // 1. 校验参数
          ResultDTO<Void> validateResult = validateAddUserParam(request);
          if (!validateResult.isSuccess()) {
              return ResultDTO.paramError(validateResult.getMsg());
          }
          // 2. 添加用户
          UserDO userDO = request.buildUserDO();
          long id = userDao.insert(userDO);
          // 3. 添加成功,返回验证激活邮件
          if (id > 0) {
              emailService.sendVerifyEmail(request.getEmail());
              return ResultDTO.success(id);
          }
          return ResultDTO.internalError("添加用户失败,请稍后重试");
      }
      /**
       * 校验添加用户参数
       */
      private ResultDTO<Void> validateAddUserParam(AddUserRequest request) {
          if (Objects.isNull(request)) {
              return ResultDTO.paramError("添加用户参数不能为空");
          }
          if (StringUtils.isBlank(request.getUserName())) {
              return ResultDTO.paramError("用户名不能为空");
          }
          if (!EmailValidator.validate(request.getEmail())) {
              return ResultDTO.paramError("邮箱格式错误");
          }
          return ResultDTO.success();
      }
  }
  基于Mockito的单测示例如下,需要注意的下面是纯java代码,没有对象显示调用的方法都是已经静态导入过的。
  Copy@RunWith(MockitoJUnitRunner.class)
  public class UserServiceImplTest {
      // Fake:需要提前构造的假数据
      AddUserRequest fakeAddUserRequest;
      // Mock: mock外部依赖
      @InjectMocks
      private UserServiceImpl userService;
      @Mock
      private UserDao userDao;
      @Mock
      private EmailService emailService;
      @Before
      public void init() {
          fakeAddUserRequest = new AddUserRequest("zhangsan", "zhangsan@163.com");
          when(userDao.insert(any())).thenReturn(1L);
          when(emailService.sendVerifyEmail(anyString())).thenReturn(true);
      }
      @Test
      public void testAddUser4NullParam() {
          // GIVEN
          fakeAddUserRequest = null;
          // WHEN
          ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
          // THEN
          assertEquals(addResult.getMsg(), "添加用户参数不能为空");
      }
      @Test
      public void testAddUser4BadEmail() {
          // GIVEN
          fakeAddUserRequest.setEmail(null);
          // WHEN
          ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
          // THEN
          assertEquals(addResult.getMsg(), "邮箱格式错误");
      }
      @Test
      public void testAddUser4BadUserName() {
          // GIVEN
          fakeAddUserRequest.setUserName(null);
          // WHEN
          ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
          // THEN
          assertEquals(addResult.getMsg(), "用户名不能为空");
      }
      @Test
      public void testAddUser4DbError() {
          // GIVEN
          when(userDao.insert(any())).thenReturn(-1L);
          // WHEN
          ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
          // THEN
          assertEquals(addResult.getMsg(), "添加用户失败,请善后重试");
      }
      @Test
      public void testAddUser4SendEmail() {
          // GIVEN
          // WHEN
          ResultDTO<Long> addResult = userService.addUser(fakeAddUserRequest);
          // THEN
          assertTrue(addResult.isSuccess());
          verify(emailService, times(1)).sendVerifyEmail(any());
          verify(emailService).sendVerifyEmail(fakeAddUserRequest.getEmail());
      }
  }
  正如上文提到的,Spock能够让代码更加精简,尤其是在代码逻辑分支比较多的场景下。下面是基于Spock的单测。
  Copyclass UserServiceImplSpec extends Specification {
      UserServiceImpl userService = new UserServiceImpl();
      AddUserRequest fakeAddUserRequest;
      def userDao = Mock(UserDao)
      def emailService = Mock(EmailService)
      def setup() {
          // Fake数据创建
          fakeAddUserRequest = new AddUserRequest(userName: "zhangsan", email: "zhangsan@163.com")
          // 注入Mock对象
          userService.userDao = userDao
          userService.emailService = emailService
      }
      def "testAddUser4BadParam"() {
          given:
          if (Objects.isNull(userName) || Objects.is(email)) {
              fakeAddUserRequest = null
          } else {
              fakeAddUserRequest.setUserName(userName)
              fakeAddUserRequest.setEmail(email)
          }
          when:
          def result = userService.addUser(fakeAddUserRequest)
          then:
          Objects.equals(result.getMsg(), resultMsg)
          where:
          userName   | email              | resultMsg
          null       | null               | "添加用户参数不能为空"
          "Java填坑笔记" | null               | "邮箱格式错误"
          null       | "javaTKBJ@163.com" | "用户名不能为空"
      }
      def "testAddUser4DbError"() {
          given:
          _ * userDao.insert(_) >> -1L
          when:
          def result = userService.addUser(fakeAddUserRequest)
          then:
          Objects.equals(result.getMsg(), "添加用户失败,请稍后重试")
      }
      def "testAddUser4SendEmail"() {
          given:
          _ * userDao.insert() >> 1
          when:
          def result = userService.addUser(fakeAddUserRequest)
          then:
          result.isSuccess()
          1 * emailService.sendVerifyEmail(fakeAddUserRequest.getEmail())
      }
  }
  思考总结
  在验证商业模式之前,时刻要想考虑投入产出比。时间和商业成本太高不利于产品快速推向市场,所以什么时候推广单测,需要更高阶的人决策。
  测试不可能犯错误,单测也不例外。单测只测试程序单元自身的功能。因此,它不能发现集成错误、性能、或者其他系统级别的问题。
  单测能够提高代码质量,驱动代码设计,帮助我们更早发现问题,保障持续优化和重构,是工程师的一项必备技能。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号