Por favor, escreva um teste de unidade elegante para SpringBoot?

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):

  1. comunicar com o banco de dados
  2. comunicar com a rede
  3. Comunicação com o sistema de arquivos
  4. Não pode ser executado ao mesmo tempo que outros testes de unidade
  5. 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, @SpringBootTesta 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 newtestada 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é @Autowiredinjetado 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 newpassar 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 mockfornecer 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;
    
    // ...
}

Acho que você gosta

Origin blog.csdn.net/agonie201218/article/details/131766260
Recomendado
Clasificación