Diretório de artigos
prefácio
Há uma aula de teste de software neste semestre, e o curso é escrever relatórios experimentais sobre algumas ferramentas de teste. Esta é realmente uma boa oportunidade. Afinal, como engenheiro full-stack, como você pode nem saber sobre testes (risos e lágrimas).
Este artigo descreve brevemente o teste de unidade e a ferramenta de teste única do Java, bem como o uso simples do framework mokito. Além disso, também incluirei outros artigos relacionados a testes nesta série.
1. Teste de unidade
1. O que é teste de unidade?
Para emprestar as palavras da Enciclopédia Baidu,
O teste de unidade refere-se à inspeção e verificação da menor unidade testável de software. Para o significado de uma unidade em testes unitários, em geral, é necessário determinar seu significado específico de acordo com a situação real. Por exemplo, uma unidade em linguagem C refere-se a uma função, em Java, uma unidade refere-se a uma classe, e o software gráfico pode se referir a uma janela ou a um menu. Em geral, uma unidade é o menor módulo funcional definido por humanos em teste. O teste de unidade é o nível mais baixo de atividade de teste a ser realizado durante o desenvolvimento de software, onde unidades individuais de software são testadas isoladamente do resto do programa.
Aqui estão alguns pontos-chave:
- As unidades são feitas pelo homem
- Os testes unitários são unidades independentes, separadas de outras partes.
2. Por que você precisa de testes unitários?
Deixe-me falar sobre meus próprios sentimentos aqui. No que me diz respeito, não tenho o hábito de fazer testes unitários. Sinto que os testes unitários serão muito demorados. Ao mesmo tempo, acho que não vale a pena o tempo gasto .Todos são projetos simples e crus.
Mas à medida que os projetos que desenvolvo se tornam maiores e os requisitos se tornam mais complexos, gradualmente percebo que a qualidade dos projetos que faço está se tornando cada vez mais instável, e muitas vezes há alguns bugs estranhos. Quando ocorre um bug, muitas vezes temos que localizar o problema. Por exemplo, eu fiz uma plataforma geral de IoT há algum tempo. Ao gravar um vídeo de demonstração, digitei uma fórmula de correção (suportando quatro operações aritméticas), e funcionou normalmente em outros dispositivos naquele momento, mas houve uma anormalidade naquele momento. Felizmente, todo o projeto foi feito por mim e eu sabia alguns detalhes de implementação. Depois de depurar por um tempo, descobri que era um problema de algoritmo. Naquela época, lembrei de repente que, quando o escrevi, percebi que o algoritmo implementação não suporta números negativos. A operação de número negativo torna-se a forma de "(0-x)", e os dados carregados por esse dispositivo passam a ser um número negativo, então há um problema.
Felizmente, é desenvolvimento full-stack, e tudo é feito por mim mesmo, se este projeto for desenvolvido por uma equipe, estimo que o tempo de localização de bugs aumentará exponencialmente.
Porque em testes de grande escala, como testes de integração, demora muito para localizar bugs, então precisamos de testes de unidade para garantir a correção de cada módulo pequeno. Embora leve mais tempo, vale a pena em comparação com o tempo gasto em bugs e correções de bugs.
No processo de desenvolvimento de um projeto, muitas vezes é para resolver o legado de bugs anteriores.
Eu vi resumos relevantes na Internet, e eles são muito bem escritos para compartilhar - o que exatamente é teste de unidade? O que deveria ser feito?
- O teste unitário é muito importante para a qualidade do nosso produto.
- O teste unitário é o tipo de teste mais baixo em todos os testes. É a primeira e mais importante parte. É o único teste que garante 100% de cobertura de código. É a base e premissa de todo o processo de teste de software. , O teste de unidade evita que o desenvolvimento tardio fique fora de controle devido a muitos bugs, e o desempenho de custo do teste de unidade é o melhor.
- De acordo com as estatísticas, cerca de 80% dos erros são introduzidos na fase de projeto do software, e o custo de correção de um erro de software aumentará com o progresso do ciclo de vida do software. Quanto mais tarde um bug é descoberto, mais caro é corrigi-lo e aumenta exponencialmente.
- Como codificador e principal executor de testes unitários, ele é o único que pode produzir programas livres de defeitos, ninguém mais pode fazer isso: especificação de código, otimização e testabilidade.
- Refatore com confiança
- Automatize a execução três mil vezes
2. Junho
1. O que é junho
JUnit é uma estrutura de teste de unidade para a linguagem Java. Fundada por Kent Beck e Erich Gamma, tornou-se a mais bem-sucedida da família xUnit de sUnits derivada de Kent Beck. JUnit tem seu próprio ecossistema de extensões JUnit.
Atualmente, o junit evoluiu para o junit5, que mudou muito em relação ao junit4. JUnit5 consiste em vários módulos diferentes de três subprojetos diferentes.
JUnit 5=Plataforma JUnit+Junit Jupiter+Junit Vintage
Veja o site oficial para detalhes
Nota: junit é basicamente o mainstream dos testes de unidade Java, e a maioria dos projetos Java hoje tem junit
2. Conceito de junho - afirmação
Os alunos que acabaram de ser expostos a testes de unidade definitivamente se perguntarão o que significa o método assert e o que é uma afirmação ao aprender junit. Quando entrei em contato pela primeira vez, foi assim, perguntando para que servia a afirmação.
Na verdade, asserções são algumas funções auxiliares, que são usadas para nos ajudar a determinar se o método em teste funciona como esperado.Geralmente, essas funções auxiliares são chamadas de asserções.
3. Uso simples do Junit
A demonstração a seguir é um projeto maven
①Importar dependências
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<!-- ... -->
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
②Escrever casos de teste
Aqui está um exemplo do site oficial
import static org.junit.jupiter.api.Assertions.assertEquals;
import example.util.Calculator;
import org.junit.jupiter.api.Test;
class MyFirstJUnitJupiterTests {
private final Calculator calculator = new Calculator();
@Test
void addition() {
assertEquals(2, calculator.add(1, 1));
}
}
O exemplo acima afirma que o valor de retorno de calculator.add(1, 1) será igual a 2.
Será muito conveniente executar o teste na ideia, basta clicar no ícone executar,
caso não esteja na ideia, basta adicionar uma função mian para executá-la.
Se a asserção em execução estiver correta, o programa será o seguinte:
Se a asserção estiver errada, o junit lançará uma exceção AssertionFailedError e informará o que deu errado
4. Uso de junit no ambiente SpringBoot
É claro que, no desenvolvimento real, como no ambiente SpringBoot, muitas classes de código de negócios são injetadas no contêiner Spring e existem dependências entre outras classes injetadas. Obviamente, não é realista criar um objeto de teste como antes. . Então, qual é a solução para este problema?
Deixe-me apresentar o uso de junit no projeto SpringBoot+SSM.
①Importar dependências
Integra dependências no SpringBoot.Se precisarmos testar dependências relacionadas, basta introduzir os módulos de teste correspondentes.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
②Escrever casos de teste
classe de objeto de teste
package com.example.demo.service;
import org.springframework.stereotype.Component;
@Component
public class Junit5Test {
public int add(int i,int j){
System.out.println("-----------add被执行了---------------");
return i+j;
}
public int doAdd(int i,int j){
System.out.println("------------doAdd被执行了--------------");
//被mock的函数会先执行,且只会执行一次
System.out.println(add(i,j));
return add(i,j);
}
}
caso de teste
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
@Autowired
Junit5Test junit5Test;
//会初始化一次
@BeforeAll
static void init(){
System.out.println("init");
}
//所有测试方法前都会执行一遍
@BeforeEach
void each(){
System.out.println("each");
}
@Test
void getDeviceStatistic() {
Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
}
}
Se você precisa do contexto SpringBoot, você só precisa adicionar uma anotação @SpringBootTest a ele. Claro, em projetos antigos podemos ver @RunWith(SpringRunner.class). O primeiro é o método de escrita de junit5, e o último é o método de escrita de junit4.
Quando precisamos do objeto de teste no container spring, apenas o injetamos normalmente.
@Autowired
Junit5Test junit5Test;
3. Dados simulados - o uso do framework mockito
1.simular
No desenvolvimento real e no teste único, nosso objeto de teste pode precisar solicitar dados de rede ou alterar o banco de dados, mas não queremos que isso mude. Neste momento, podemos usar o framework mockito para simular os dados.
O chamado mock significa que se o código que escrevemos depende de alguns objetos, e esses objetos são difíceis de criar manualmente (ou seja, não sabem inicializar, etc., como HttpRequest e outros objetos), então use um virtual objeto a testar. Como ele passa em um arquivo de classe, o bloco de código estático ainda será executado, mas os blocos de código construtor e de instância não serão executados .
2. Toco de empilhamento
O chamado stub Stub é usado para fornecer os dados de teste necessários para o teste. Por ser um objeto simulado, alguns métodos podem não saber o valor de retorno, portanto, precisamos assumir o valor de retorno. As respostas correspondentes podem ser definidas para várias interações, ou seja, para definir o valor de retorno de uma chamada de método, usando when(…).thenReturn(…) e doReturn(…).when(…).
por exemplo:
//You can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
- doReturn().when() não tem efeitos colaterais. O método não é executado durante o empilhamento.
- when().thenReturn() tem efeitos colaterais, o que significa que o método será executado primeiro durante o empilhamento, o que pode causar certos efeitos colaterais.
3. @MockBean e @SpyBean
Claro, no ambiente SpringBoot, você também pode usar diretamente as anotações @SpyBean e @MockBean para substituir o objeto injetado de @Autowired, para que haja um objeto virtual.
@MockBean
Se você usar apenas @MockBean, o objeto modificado será zombado, de modo que o método add() do Junit5Test não executará mais os detalhes específicos, mas o MockBean zombará de todos os métodos do objeto de destino, portanto, o teste não pode ser realmente executado. , ele não pode ser testado.
@SpyBean
e em alguns casos precisamos executar métodos reais, queremos apenas zombar de alguns métodos, então podemos usar @SpyBean.
O spyJunit5Test decorado com @SpyBean é um objeto real, somente quando(spyJunit5Test.add(1,1)).thenReturn(2);, o método add é stub e outros métodos ainda são chamados.
Aqui está um exemplo
package com.example.demo.service;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
//初始化一个spring的上下文,使其可以使用一些注入(junit5)。junit4会用runwith
@SpringBootTest
class Junit5TestTest {
// @Autowired
// Junit5Test junit5Test;
//介于@Autowired和@MockBean之间的注解,当配置了when时使用mock,没有进行打桩则走正常方法
@SpyBean
Junit5Test spyJunit5Test;
//完全使用mock方法,方法都不会去真正执行。当调用mockBean修饰的方法时,不会去真正执行该方法,只会返回打桩后的值,如果没有打桩时会返回默认值,比如int就返回0。
// @MockBean
// Junit5Test mockJunit5Test;
//会初始化一次
@BeforeAll
static void init(){
System.out.println("init");
}
//所有测试方法前都会执行一遍
@BeforeEach
void each(){
System.out.println("each");
}
@Test
void getDeviceStatistic() {
Assertions.assertEquals(1,1);
}
@Test
void testDeviceStatisticByMock() {
//配置mock
when(spyJunit5Test.add(1,1)).thenReturn(2);
Assertions.assertEquals(2,spyJunit5Test.doAdd(1,1));
}
}
Por fim, desejo a todos um Feliz Dia do Programador!