O que é teste de unidade
Um teste não é um teste de unidade quando qualquer um dos seguintes é verdadeiro para um teste (por Michael Feathers em 2005):
- comunicar com o banco de dados
- comunicar com a rede
- Comunicação com o sistema de arquivos
- Não pode ser executado ao mesmo tempo que outros testes de unidade
- teve que fazer algo especial para executá-lo
Se um teste faz qualquer um dos itens acima, então é um teste de integração.
Não escreva testes de unidade com 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");
}
}
Isso é um teste de unidade? Primeiro, @SpringBootTest
a anotação carrega todo o contexto do aplicativo, apenas para injetar dois beans.
Outro problema é que precisamos ler e gravar pedidos no banco de dados, que também é o escopo do teste de integração.
A documentação do Spring Framework descreve o teste de unidade
Os verdadeiros testes de unidade são executados muito rapidamente porque não há necessidade de tempo de execução para conectar a infraestrutura. Enfatizar o verdadeiro teste de unidade como parte de sua metodologia de desenvolvimento pode aumentar sua produtividade.
Escreva um serviço "testável por unidade"
Outra descrição do teste de unidade na documentação do Spring Framework
A injeção de dependência pode tornar seu código menos dependente. POJO permite que sua aplicação seja
new
testada em JUnit ou TestNG através de operadores, sem a necessidade de Spring e outros containers
Considere se tal serviço foi escrito, é conveniente para testes de unidade! ?
@Service
public class BookService {
@Autowired
private BookRepository repository;
// ... service methods
}
Inconveniente, porque BookRepository
é @Autowired
injetado no Service e repository
é uma variável privada, o que limita o mundo externo a definir esse valor apenas por meio do Spring ou outros contêineres de injeção de dependência (ou reflexão). Se o teste de unidade não quiser carregar todo o contêiner do Spring , então ele não pode usar este serviço.
E se for escrito assim, usando injeção de construtor, o mundo externo também pode new
passar por si mesmo Repository
, de forma que mesmo sem o Spring, o mundo externo pode realizar testes rápidos. Também pode ser por isso que o Spring não recomenda a injeção de atributos.
@Service
public class BookService {
private BookRepository repository;
@Autowired
public BookService(BookRepository repository) {
this.repository = repository;
}
}
Escrever testes de unidade
Introdução ao Mockito
O conhecimento prévio mostra que um teste de unidade é um teste da correção lógica de uma determinada menor unidade em um sistema, geralmente um método é testado, pois apenas a correção lógica é testada, portanto esse teste é independente e não está relacionado a nenhuma. relacionados ao ambiente externo, como não precisar se conectar ao banco de dados, não ter acesso à rede e ao sistema de arquivos e não depender de outros testes de unidade. No entanto, geralmente existem muitas dependências complexas e intrincadas na lógica de negócios real. Por exemplo, se você deseja testar a unidade de um serviço, ele depende de um objeto Repositório da camada de persistência do banco de dados. Isso é difícil. Se você criar um repositório, precisará pode conectar Conectar-se a um banco de dados não é um teste de unidade independente.
O Mockito é usado para simular rapidamente objetos que precisam se comunicar com o ambiente externo em testes de unidade, para que possamos realizar testes de unidade de maneira rápida e conveniente sem iniciar todo o sistema.
O código a seguir é um uso básico de Mockito, Mock significa falso.
// 通过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());
Escrever testes de unidade
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");
}
}
Agora, mesmo que não desejemos nos conectar ao banco de dados, também podemos mock
fornecer uma outra implementação do Repository, para que esse método seja concluído em milissegundos.
também pode usarMockito
@ExtendWith(MockitoExtension.class)
class OrderServiceTests {
@Mock
private OrderRepository orderRepository;
@Mock
private PaymentRepository paymentRepository;
@InjectMocks
private OrderService orderService;
// ...
}