[Teste de software] Teste de unidade Junit

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.
insira a descrição da imagem aqui

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,
insira a descrição da imagem aqui
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:
insira a descrição da imagem aqui
Se a asserção estiver errada, o junit lançará uma exceção AssertionFailedError e informará o que deu errado
insira a descrição da imagem aqui

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!

Acho que você gosta

Origin blog.csdn.net/qq_46101869/article/details/120942569
Recomendado
Clasificación