ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

SpringBoot测试迷你系列 —— 一 单元测试

2021-10-11 09:34:26  阅读:182  来源: 互联网

标签:SpringBoot 迷你 Spring orderRepository 单元测试 private 测试 class


Spring Boot 单元测试

原文Spring Boot Unit Testing

非逐句翻译

目录

  1. 单元测试
  2. 使用@WebMvcTest进行测试
  3. 使用@DataJpa进行持久层测试
  4. 使用@JsonTest测试序列化
  5. 使用MockWebServer测试Spring WebClient Rest调用
  6. 使用@SpringBootTest进行SpringBoot集成测试

什么是单元测试

当一个测试满足下面任意一点时,测试就不是单元测试(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操作符在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,迷你,Spring,orderRepository,单元测试,private,测试,class
来源: https://www.cnblogs.com/lilpig/p/15391885.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有