请给 SpringBoot 写一个优雅的单元测试吧?

发表于:2023-8-23 09:55

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

 作者:Young丶    来源:CSDN

  什么是单元测试
  当一个测试满足下面任意一点时,测试就不是单元测试 (by Michael Feathers in 2005):
  1. 与数据库交流
  2. 与网络交流
  3. 与文件系统交流
  4. 不能与其他单元测试在同一时间运行
  5. 不得不为运行它而作一些特别的事
  如果一个测试做了上面的任何一条,那么它就是一个集成测试。
  不要用 Spring 编写单元测试
  @SpringBootTest
  class OrderServiceTests {
      @Autowired
      private OrderRepository orderRepository;
      @Autowired
      private OrderService orderService;
      @Test
      void payOrder() {
          Order order = new Order(1L, false);
          orderRepository.save(order);
          Payment payment = orderService.pay(1L, "4532756279624064");
          assertThat(payment.getOrder().isPaid()).isTrue();
          assertThat(payment.getCreditCardNumber()).isEqualTo("4532756279624064");
      }
  }
  这是一个单元测试吗?首先@SpringBootTest注解加载了整个应用上下文,而仅仅是为了注入两个 Bean。
  另一个问题是我们需要读取和写入订单到数据库,这也是集成测试的范畴。
  Spring Framework 文档对于单元测试的描述:
  真正的单元测试运行的非常快,因为不需要运行时去装配基础设施。强调将真正的单元测试作为开发方法的一部分可以提高你的生产力。
  编写 “可单元测试” 的 Service
  Spring Framework 文档对于单元测试的另一描述:
  依赖注入可以让你的代码减少依赖。POJO 可以让你的应用可以通过new操作符在 JUnitTestNG 上进行测试,不需要任何的 Spring 和其他容器
  考虑如果编写这样的 Service,它方便进行单元测试吗!?
  @Service
  public class BookService {
      @Autowired
      private BookRepository repository;
      // ... service methods
  }
  不方便,因为BookRepository通过@Autowired被注入到 Service 中,并且repository是一个私有变量,这就限定了外界只能通过 Spring 或其它依赖注入容器(或反射)设置这个值,那么单元测试如果不想加载整个 Spring 容器,那么它就无法使用这个 Service。
  而如果这样写,使用构造方法注入,外界也可以通过new去自行传递Repository,这样即使没有 Spring,外界也能进行快速的测试。这可能也是 Spring 不推荐属性注入的原因。
  @Service
  public class BookService {
      private BookRepository repository;
      @Autowired
      public BookService(BookRepository repository) {
          this.repository = repository;
      }
  }
  编写单元测试
  Mockito 介绍
  前面的知识表明,单元测试就是对一个系统中的某个最小单元的逻辑正确性的测试,通常是对一个方法来进行测试,因为只测试逻辑正确性,所以这个测试是独立的,不与任何外界环境相关,比如不需要连接数据库,不访问网络和文件系统,不依赖其他单元测试。但是现实的业务逻辑中往往有很多复杂错综的依赖关系,比如你想对 Service 进行单元测试,那么它要依赖一个数据库持久层的 Repository 对象,这时候就难办了,若创建了一个 Repository 便连接了数据库,连接了数据库便不是一个独立的单元测试。
  Mockito 是一个用来在单元测试中快速模拟那些需要与外界环境沟通的对象,以便我们快速的、方便的进行单元测试而不用启动整个系统。
  下面的代码就是 Mockito 的一个基础使用,Mock 意为伪造。
  // 通过mock方法伪造一个orderRepository的实现,这个实现目前什么都不会做
  orderRepository = mock(OrderRepository.class);
  // 通过mock方法伪造一个paymentRepository的实现,这个实现目前什么都不会做
  paymentRepository = mock(PaymentRepository.class)
  // 创建一个Order对象以便一会儿使用
  Order order = new Order(1L, false);
  // 使用when方法,定义当orderRepository.findById(1L)被调用时的行为,直接返回刚刚创建的order对象
  when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
  // 使用when方法,定义当paymentRepository.save(任何参数)被调用时的行为,直接返回传入的参数。
  when(paymentRepository.save(any())).then(returnsFirstArg());
  编写单元测试
  class OrderServiceTests {
      private OrderRepository orderRepository;
      private PaymentRepository paymentRepository;
      private OrderService orderService;
      @BeforeEach
      void setupService() {
          orderRepository = mock(OrderRepository.class);
          paymentRepository = mock(PaymentRepository.class);
          orderService = new OrderService(orderRepository, paymentRepository);
      }
      
      @Test
      void payOrder() {
          Order order = new Order(1L, false);
          when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
          when(paymentRepository.save(any())).then(returnsFirstArg());
          Payment payment = orderService.pay(1L, "4532756279624064");
          assertThat(payment.getOrder().isPaid()).isTrue();
          assertThat(payment.getCreditCardNumber()).isEqualTo("4532756279624064");
      }
  }
  现在我们即使不想连接数据库,也可以通过mock来给定一个 Repository 的其他实现,这样这个方法可以在毫秒内完成。
  也可以使用Mockito:
  @ExtendWith(MockitoExtension.class)
  class OrderServiceTests {
      @Mock
      private OrderRepository orderRepository;
      @Mock
      private PaymentRepository paymentRepository;
      @InjectMocks
      private OrderService orderService;
      
      // ...
  }
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号