1. O que é um teste simulado
1.png
O teste simulado é usar um objeto virtual (objeto simulado) para criar um método de teste para testar alguns dados/cenários mais complexos que não são fáceis de construir ou obter na atividade de teste .
2. Por que testar simulado
Mock é resolver o problema que é difícil de desenvolver e testar devido ao acoplamento entre diferentes unidades. Portanto, o Mock pode aparecer não apenas em testes de unidade , mas também em testes de integração e testes de sistema .
A maior função do Mock é te ajudar a decompor o acoplamento dos testes de unidade.Se seu código possui dependências de outra classe ou interface, ele pode te ajudar a simular essas dependências e te ajudar a verificar o comportamento das dependências chamadas.
3. Cenários de aplicativos simulados
1. É necessário separar a unidade atual em teste de seus módulos dependentes, construir um ambiente de teste independente e não dar atenção aos objetos dependentes da unidade em teste, mas apenas focar na lógica funcional da unidade em teste.
2. O módulo do qual a unidade em teste depende não foi desenvolvido A, e a unidade em teste precisa contar com o valor de retorno do módulo para processamento subsequente.
3. Nos projetos front-end e back-end, antes da conclusão do desenvolvimento da interface back-end, a depuração conjunta da interface
4. A interface do projeto upstream dependente ainda não foi desenvolvida e o teste de depuração conjunta da interface é necessário
5. Os objetos dos quais a unidade em teste depende são difíceis de simular ou possuem estruturas complexas
Por exemplo: Existem muitas condições anormais no negócio de pagamentos, mas simular tais condições anormais é muito complicado ou impossível de simular.
4. Exemplo de código
Novo projeto de teste
package com.echo.mockito;
public class demo {
//新建一个测试方法
public int add(int a, int b){
return a + b;
}
}
Crie um método de teste simulado
Selecione a classe de teste, clique com o botão direito e selecione gerar
2.png
teste de clique
3.png
Após clicar em ok, o método de teste correspondente será gerado no diretório de teste, correspondente ao diretório real
4.png
5. Descrição do método de parâmetro
@BeforeEach
É usado para preparar antes do teste. Muitas configurações de ambiente ou configurações básicas serão construídas antes do teste, que podem ser definidas aqui.
@Após cada
Para configuração pós-teste.
@Zombar
As anotações podem ser entendidas como um substituto para o método simulado, ao invés de seguir o método real e simular o comportamento do método real. Ao usar essa anotação, use o método MockitoAnnotations.openMocks(this) para fazer a anotação entrar em vigor.
@Espião
1. O objeto espião seguirá o método real, mas o objeto fictício não.
2. O parâmetro do método spy é a instância do objeto, e o parâmetro do mock é a classe.
@InjectMocks
Usado para injetar variáveis simuladas marcadas com @Mock em classes de teste.
MockitoAnnotations.openMocks(this)
Ligue o mock e teste-o com as duas anotações acima. Geralmente é colocado em @BeforeEach e ativado antes do teste, para que não precise ser ativado em todos os métodos.
Mockito.when(demo.add(1,2)).thenReturn(3): empilhamento
O núcleo simulado pode definir o resultado do método a ser testado, de modo que o resultado da execução do método real seja ignorado e os testes subsequentes sejam executados com base no resultado do empilhamento.
Mockito.when(demo.add(1,2)).thenThrow(new RuntimeException());
Usado para simular exceções.
Assertions.assertEquals(3,demo.add(1,2)): Asserções
O principal meio de teste, no qual os resultados são julgados. (valor esperado, valor real).
6. Teste simples
O método de configuração do teste de empilhamento retorna 4, mas a execução real é 3 e o teste falha.
5.png
Nenhum teste de empilhamento Por ser um método de espionagem, o método real será usado e o teste será aprovado.
6.png
Se for um método simulado, se não houver empilhamento, haverá um valor padrão e o teste falhará. Você pode tentar.
7.png
7. Descrição do método de teste
Veja o código para chamadas detalhadas, geralmente da seguinte forma:
-
Teste de empilhamento
-
teste de exceção
-
chamada de método real
package com.echo.mockito;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
class demoTest {
@Mock
demo demo;
//测试前开启mock
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void add() {
//mock 打桩,就是不管真实的方法如何执行,我们可以自行假设该方法执行的结果
//后续的测试都是基于打桩结果来走
// Mockito.when(demo.add(1,2)).thenReturn(4);
// Assertions.assertEquals(3,demo.add(1,2));
//当测试方法出现异常,测试方法 如果有try{}catch{} 则可以测试异常是否正常
//Mockito.when(demo.add(1,1)).thenThrow(new RuntimeException());
//调用真实的方法
Mockito.when(demo.add(1,1)).thenCallRealMethod();
Assertions.assertEquals(2,demo.add(1,1));
}
@AfterEach
void after(){
System.out.println("测试结束");
}
}
8. Método estático simulado
Na versão anterior, não era permitido simular e testar métodos estáticos.Se você precisar testar métodos estáticos, precisará substituir as novas dependências fictícias e comentar as dependências atuais, pois haverá conflitos.
O teste de método estático deve usar a classe MockedStatic do mock para construir o método de teste.
<!-- <dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.6.1</version>
</dependency>
-->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
package com.echo.mockito.Util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
class StaticUtilsTest {
@BeforeEach
void setUp() {
}
// 有参静态方法构建
@Test
void range() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
}
// 无参静态方法构建
@Test
void name() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(StaticUtils::name).thenReturn("dhmw");
Assertions.assertEquals("dhmw",StaticUtils.name());
}
}
Problema: Não há problema na execução de um único método, mas ao executarmos todos os métodos da classe, verificamos que é reportado um erro.
A simulação estática já está registrada no thread atual Para criar uma nova simulação, o registro da simulação estática existente deve ser cancelado
Isso significa que cada método precisa ter seu próprio objeto fictício estático, que não pode ser compartilhado. Quando executados juntos, o primeiro método ocupa o objeto e o segundo método não tem como ocupá-lo.
8.png
Solução: Feche o objeto simulado demo.close() logo após a execução de cada método. Equivalente a um singleton. Solte-o após o uso e o próximo método poderá ser usado.
@Test
void range() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(()->StaticUtils.range(2,6)).thenReturn(Arrays.asList(10,11,12));
Assertions.assertTrue(StaticUtils.range(2,6).contains(11));
//关闭
demo.close();
}
// 无参静态方法构建
@Test
void name() {
MockedStatic demo = Mockito.mockStatic(StaticUtils.class);
//打桩
demo.when(StaticUtils::name).thenReturn("dhmw");
Assertions.assertEquals("dhmw",StaticUtils.name());
//关闭
demo.close();
}
9. Melhore a cobertura do teste
Caso: No sistema de estatísticas de dados, o pusher local insere o nome do cliente e o número do celular e, finalmente, constrói um objeto de usuário e o armazena na tabela de dados.
O código do negócio é o seguinte:
package com.echo.mockito.service.impl;
import com.echo.mockito.dao.UserDao;
import com.echo.mockito.service.RegistrationService;
import com.echo.mockito.vo.User;
import org.springframework.beans.factory.annotation.Autowired;
import javax.xml.bind.ValidationException;
import java.sql.SQLException;
public class RegistrationServiceImpl implements RegistrationService {
@Autowired
UserDao userDao;
@Override
public User register(String name, String phone) throws Exception {
if (name == null || name.length() == 0){
throw new ValidationException("name 不能为空");
}
if (phone == null || phone.length() ==0 ){
throw new ValidationException("phone 不能为空");
}
User user;
try {
user = userDao.save(name,phone);
}catch (Exception e){
throw new Exception("SqlException thrown" + e.getMessage());
}
return user;
}
}
package com.echo.mockito.dao;
import com.echo.mockito.vo.User;
public class UserDao {
public User save(String name,String phnoe){
User user = new User();
user.setName(name);
return user;
}
}
Para gerar o código de teste correspondente, há várias questões a serem consideradas neste momento.
1. A classe a ser testada é RegistrationServiceImpl, mas como o userDao nela é injetado na classe de teste?
2. Devido à necessidade de cobertura de teste, é necessário considerar que existem várias situações na classe de teste que precisam ser testadas.
(1): Dois ifs lançam duas exceções, e há um total de 2 situações a serem testadas.
(2): Salvar o banco de dados é dividido em salvamento normal e salvamento anormal, um total de 2 casos de teste.
Em resumo, o teste para esses quatro casos deve ser feito para cobrir todo o código.
Da mesma forma, geramos casos de teste.Existem instruções detalhadas no código e existem casos de teste para cada caso.
package com.echo.mockito.service.impl;
import com.echo.mockito.dao.UserDao;
import com.echo.mockito.vo.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
import javax.xml.bind.ValidationException;
import java.sql.SQLException;
import static org.junit.jupiter.api.Assertions.*;
class RegistrationServiceImplTest {
@InjectMocks //RegistrationServiceImpl 实例中注入@Mock标记的类,此处是注入userDao
@Spy
private RegistrationServiceImpl registrationService;
@Mock
private UserDao userDao;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void register() throws Exception {
// ------------------ 第一种 name 异常情况 测试 start ------------------------
String name = null;
String phone = "1234";
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof ValidationException);
}
// ------------------ name 异常情况 测试 end ------------------------
// ------------------ 第二种 phone 异常情况 测试 start ------------------------
name = "111";
phone = null;
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof ValidationException);
}
// ------------------ phone 异常情况 测试 start ------------------------
// ------------------ 第三种 userDao.save 正常情况 测试 start ------------------------
name = "111";
phone = "111";
//正常保存测试 打桩 走真实的方法
Mockito.when(userDao.save(name,phone)).thenCallRealMethod();
User user = registrationService.register(name,phone);
Assertions.assertEquals("111",user.getName());
// ------------------ userDao.save 正常情况 测试 end ------------------------
// ------------------ 第四种 userDao.save 异常情况 测试 start ------------------------
//异常保存测试 打桩 通过thenThrow 抛出异常 测试异常是否被捕获
Mockito.when(userDao.save(name,phone)).thenThrow(new RuntimeException());
try {
registrationService.register(name,phone);
}catch (Exception e){
Assertions.assertTrue(e instanceof Exception);
}
// ------------------ userDao.save 异常情况 测试 end ------------------------
}
}
Conforme mostrado acima: A primeira questão mencionada acima, como injetar variáveis membro na classe de teste, pode ser feita através da anotação @InjectMocks.
O método de cobertura de teste é o seguinte:
9.png
Os resultados do teste são os seguintes:
1. A parte direita exibirá a cobertura do teste.
2. A cor verde do código real representa o teste coberto, e o vermelho representa o teste não coberto.
11.png
Se todos os casos de teste forem 100% cobertos, o resultado será o seguinte:
12.png
Em resumo, o método de teste de cobertura é resumido da seguinte forma:
1. De acordo com o código empresarial, analise todas as situações que precisam ser testadas.
2. Escreva códigos de teste específicos de acordo com diferentes situações de teste.
3. Para cada situação, você pode escrever códigos de teste específicos e, em seguida, esgotar todas as situações de inventário empilhando, afirmando, etc.
pergunta:
1. Se o nível do método de código real lançar uma exceção, da mesma forma, o método de teste também deve lançar exceções no nível do método, caso contrário, o teste relatará um erro.
@Test
void register() throws Exception {
2. O banco de dados salvo é dividido em normal e anormal. Em seguida, teste primeiro a ramificação normal e, em seguida, teste a ramificação anormal. Se a ordem for invertida, o teste lançará uma exceção primeiro e a ramificação normal não será executada, o que levará a uma cobertura de teste incompleta.
A seguir estão as informações de suporte. Para amigos que fazem [teste de software], deve ser o warehouse de preparação mais abrangente e completo. Este warehouse também me acompanhou na jornada mais difícil. Espero que possa ajudar você também!
Applet de entrevista de teste de software
O banco de questões de teste de software está esgotado por milhões de pessoas! ! ! Quem é quem sabe! ! ! O miniprograma de quiz mais completo de toda a rede, você pode usar seu celular para fazer os quizzes, no metrô ou no ônibus, enrola!
As seguintes seções de perguntas da entrevista são abordadas:
1. Teoria básica de teste de software, 2. web, app, teste de função de interface, 3. rede, 4. banco de dados, 5. linux
6. web, aplicativo, automação de interface, 7. teste de desempenho, 8. noções básicas de programação, 9. perguntas de entrevista de hr, 10. perguntas de teste aberto, 11. teste de segurança, 12. noções básicas de computador
Método de aquisição de informações: