使用SpringBoot编写一个优雅的单元测试

 更新时间:2023年07月21日 17:03:20   作者:Young丶  
这篇文章主要为大家详细介绍了如何使用SpringBoot编写一个优雅的单元测试,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下

什么是单元测试

当一个测试满足下面任意一点时,测试就不是单元测试 (by Michael Feathers in 2005):

  • 与数据库交流
  • 与网络交流
  • 与文件系统交流
  • 不能与其他单元测试在同一时间运行
  • 不得不为运行它而作一些特别的事

如果一个测试做了上面的任何一条,那么它就是一个集成测试。

不要用 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操作符在 JUnit 或 TestNG 上进行测试,不需要任何的 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;
    // ...
}

以上就是使用SpringBoot编写一个优雅的单元测试的详细内容,更多关于SpringBoot单元测试的资料请关注脚本之家其它相关文章!

相关文章

  • 解决SpringBoot自定义拦截器和跨域配置冲突的问题

    解决SpringBoot自定义拦截器和跨域配置冲突的问题

    这篇文章主要介绍了解决SpringBoot自定义拦截器和跨域配置冲突的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java虚拟机使用jvisualvm工具远程监控tomcat内存

    Java虚拟机使用jvisualvm工具远程监控tomcat内存

    这篇文章主要介绍了Java虚拟机使用jvisualvm工具远程监控tomcat内存,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • java操作cookie示例(删除cookie)

    java操作cookie示例(删除cookie)

    这篇文章主要介绍了java操作cookie示例,包括设置Cookie、读取Cookie、删除Cookie,需要的朋友可以参考下
    2014-02-02
  • mybatis insert foreach循环插入方式

    mybatis insert foreach循环插入方式

    这篇文章主要介绍了mybatis insert foreach循环插入方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Struts2相关的面试题整理分享

    Struts2相关的面试题整理分享

    这篇文章主要给大家总结整理了关于Struts2相关的面试题,文中先详细介绍了关于struts2的工作原理、工作流程、拦截器和过滤器的区别以及什么要使用Struts2,然后分享了总结的一些关于Struts2面试的一些问题,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-11-11
  • Flink作业Task运行源码解析

    Flink作业Task运行源码解析

    这篇文章主要为大家介绍了Flink作业Task运行源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 详解java数组进行翻转的方法有哪些

    详解java数组进行翻转的方法有哪些

    这篇文章主要介绍了详解java数组进行翻转的方法有哪些,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Messges Queue消息队列详解

    Messges Queue消息队列详解

    这篇文章主要介绍了Messges Queue消息队列详解,消息队列一般简称为 MQ,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器,需要的朋友可以参考下
    2023-07-07
  • 如何两步解决maven依赖导入失败的问题

    如何两步解决maven依赖导入失败的问题

    这篇文章主要介绍了如何两步解决maven依赖导入失败的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 谈谈HttpClient使用详解

    谈谈HttpClient使用详解

    这篇文章给大家介绍HttpClient使用,httpClient是一个客户端的http通信实现库,HttpClient的目标是发送和接收HTTP报文。本文讲解的非常详细,对HttpClient使用感兴趣的朋友可以参考下
    2015-10-10

最新评论