[Pruebas de software] Pruebas unitarias de Junit

prefacio

Hay una clase de prueba de software este semestre, y el trabajo del curso es escribir informes experimentales sobre algunas herramientas de prueba. Esta es sin duda una buena oportunidad. Después de todo, como ingeniero full-stack, ¿cómo es posible que ni siquiera sepas sobre pruebas (risas y lágrimas).

Este artículo describe brevemente las pruebas unitarias y la herramienta de prueba única de Java, junit, así como el uso simple del marco mokito. Además, también incluiré otros artículos relacionados con las pruebas en esta serie.

1. Pruebas unitarias

1. ¿Qué son las pruebas unitarias?

Tomando prestadas las palabras de la Enciclopedia Baidu,

La prueba unitaria se refiere a la inspección y verificación de la unidad de software comprobable más pequeña. Para el significado de una unidad en pruebas unitarias, en términos generales, es necesario determinar su significado específico de acuerdo con la situación real, por ejemplo, una unidad en lenguaje C se refiere a una función, en Java, una unidad se refiere a una clase, y el software gráfico puede hacer referencia a una ventana o un menú. En general, una unidad es el módulo funcional definido por humanos más pequeño bajo prueba. La prueba unitaria es el nivel más bajo de actividad de prueba que se realiza durante el desarrollo de software, donde las unidades individuales de software se prueban de forma aislada del resto del programa.

Aquí hay algunos puntos clave:

  • Las unidades son hechas por el hombre
  • Las pruebas unitarias son unidades independientes, separadas de otras partes.

2. ¿Por qué necesita pruebas unitarias?

Permítanme hablar sobre mis propios sentimientos aquí. En lo que a mí respecta, no tengo la costumbre de realizar pruebas unitarias. Siento que las pruebas unitarias llevarán mucho tiempo. Al mismo tiempo, creo que no vale la pena el tiempo invertido. Todos son simples proyectos crudos.
Pero a medida que los proyectos que desarrollo se vuelven más grandes y los requisitos se vuelven más complejos, gradualmente descubro que la calidad de los proyectos que hago se vuelve cada vez más inestable y, a menudo, hay algunos errores extraños. Cuando ocurre un error, a menudo tenemos que localizar el problema. Por ejemplo, hice una plataforma IoT general hace algún tiempo. Al grabar un video de demostración, ingresé una fórmula de corrección (admitiendo cuatro operaciones aritméticas), y funcionó normalmente en otros dispositivos en ese momento, pero hubo una anomalía en ese momento. Afortunadamente, todo el proyecto fue hecho por mí mismo y conocía algunos detalles de implementación. Después de depurar por un tiempo, descubrí que era un problema de algoritmo. En ese momento, de repente recordé que cuando lo escribí, me di cuenta de que el algoritmo La implementación no admite números negativos. La operación de número negativo se convierte en la forma de "(0-x)", y los datos cargados por ese dispositivo resultan ser un número negativo, por lo que hay un problema.

Afortunadamente es un desarrollo full-stack y todo lo hago yo mismo, si este proyecto es desarrollado por un equipo estimo que el tiempo de localización de bugs aumentará exponencialmente.

Debido a que en las pruebas a gran escala, como las pruebas de integración, se tarda demasiado en localizar errores, por lo que necesitamos pruebas unitarias para garantizar la corrección de cada módulo pequeño. Aunque tomará más tiempo, vale la pena en comparación con el tiempo dedicado a errores y correcciones de errores.

En el proceso de desarrollo de un proyecto, muchas veces se trata de solucionar el legado de bugs anteriores.
inserte la descripción de la imagen aquí

He visto resúmenes relevantes en Internet y están muy bien escritos para compartir: ¿qué son exactamente las pruebas unitarias? ¿Lo que debe hacerse?

  • Las pruebas unitarias son muy importantes para la calidad de nuestros productos.
  • Las pruebas unitarias son el tipo de prueba más bajo en todas las pruebas. Es la primera y más importante parte. Es la única prueba que garantiza lograr una cobertura de código del 100 %. Es la base y la premisa de todo el proceso de prueba de software. , Las pruebas unitarias evitan que el desarrollo tardío se salga de control debido a demasiados errores, y el rendimiento de costos de las pruebas unitarias es el mejor.
  • Según las estadísticas, alrededor del 80 % de los errores se introducen en la etapa de diseño del software, y el costo de corregir un error de software aumentará con el progreso del ciclo de vida del software. Cuanto más tarde se descubre un error, más caro es arreglarlo y aumenta exponencialmente.
  • Como codificador y principal ejecutor de pruebas unitarias, es el único que puede producir programas libres de defectos. Nadie más puede hacer esto. Especificación de código, optimización y capacidad de prueba.
  • Refactorice con confianza
  • Automatice la ejecución tres mil veces

2. Junit

1. ¿Qué es junit?

JUnit es un marco de pruebas unitarias para el lenguaje Java. Fundada por Kent Beck y Erich Gamma, se ha convertido en la más exitosa de la familia xUnit de sUnits derivada de Kent Beck. JUnit tiene su propio ecosistema de extensiones JUnit.
En la actualidad, junit se ha convertido en junit5, que ha cambiado mucho en comparación con junit4. JUnit5 consta de varios módulos diferentes de tres subproyectos diferentes.
JUnit 5 = Plataforma JUnit + JUnit Júpiter + JUnit Vintage

Ver el sitio web oficial para más detalles

Nota: junit es básicamente la corriente principal de las pruebas unitarias de Java, y la mayoría de los proyectos Java actuales tienen junit

2.Concepto Junit - afirmación

Los estudiantes que acaban de ser expuestos a las pruebas unitarias definitivamente se preguntarán qué significa el método de afirmación y qué es una afirmación cuando se aprende junit. Cuando entré en contacto por primera vez, fue así, preguntándome para qué era la afirmación.

De hecho, las aserciones son en realidad algunas funciones auxiliares, que se utilizan para ayudarnos a determinar si el método bajo prueba funciona como se esperaba. Por lo general, estas funciones auxiliares se denominan aserciones.

3. Uso sencillo de Junit

La siguiente demostración es un proyecto maven

①Importar dependencias

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

②Escribir casos de prueba

Aquí hay un ejemplo del sitio web 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));
    }

}

El ejemplo anterior afirma que el valor de retorno de calculadora.add(1, 1) será igual a 2.

Será muy conveniente ejecutar la prueba en la idea, simplemente haga clic en el icono de ejecutar,
inserte la descripción de la imagen aquí
si no está en la idea, simplemente agregue una función mian para ejecutarla.

Si la aserción en ejecución es correcta, el programa será el siguiente:
inserte la descripción de la imagen aquí
Si la aserción es incorrecta, junit le arrojará una excepción AssertionFailedError y le dirá qué salió mal.
inserte la descripción de la imagen aquí

4. Uso de junit en entorno SpringBoot

Por supuesto, en el desarrollo real, como en el entorno SpringBoot, muchas clases de código empresarial se inyectan en el contenedor Spring y existen dependencias entre otras clases inyectadas. Obviamente, no es realista crear un objeto de prueba como antes. . Entonces, ¿cuál es la solución a este problema?
Permítanme presentarles el uso de junit en el proyecto SpringBoot+SSM.

①Importar dependencias

Integra dependencias en SpringBoot, si necesitamos probar dependencias relacionadas, solo necesitamos introducir los módulos de prueba correspondientes.

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

②Escribir casos de prueba

clase de objeto de prueba

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 prueba

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));
    }
}

Si necesita el contexto SpringBoot, solo necesita agregarle una anotación @SpringBootTest. Por supuesto, en proyectos antiguos podemos ver @RunWith(SpringRunner.class). El primero es el método de escritura de junit5, y el segundo es el método de escritura de junit4.

Cuando necesitamos el objeto de prueba en el contenedor de resorte, simplemente lo inyectamos normalmente.

@Autowired
Junit5Test junit5Test;

3. Datos simulados: el uso del marco mockito

1. simulacro

En el desarrollo real y la prueba individual, nuestro objeto de prueba puede necesitar solicitar datos de red o cambiar la base de datos, pero no queremos que cambie. En este momento, podemos usar el marco de trabajo de mockito para simular los datos.

El llamado simulacro significa que si el código que escribimos depende de algunos objetos, y estos objetos son difíciles de crear manualmente (es decir, no saben cómo inicializar, etc., como HttpRequest y otros objetos), entonces use un virtual objeto a probar. Debido a que pasa en un archivo de clase, el bloque de código estático aún se ejecutará, pero los bloques de código de instancia y constructor no se ejecutarán .

2. Trozo de pilotaje

El llamado Stub de stub se utiliza para proporcionar los datos de prueba necesarios para la prueba. Debido a que es un objeto simulado, es posible que algunos métodos no conozcan el valor de retorno, por lo que debemos asumir el valor de retorno. Las respuestas correspondientes se pueden establecer para varias interacciones, es decir, para establecer el valor de retorno de una llamada de método, usando when(…).thenReturn(…) y doReturn(…).when(…).

por ejemplo:


//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() no tiene efectos secundarios. El método no se ejecuta durante el pilotaje.
  • when().thenReturn() tiene efectos secundarios, lo que significa que el método se ejecutará primero mientras se acumula, lo que puede causar ciertos efectos secundarios.

3. @MockBean y @SpyBean

Por supuesto, en el entorno SpringBoot, también puede usar directamente las anotaciones @SpyBean y @MockBean para reemplazar el objeto inyectado de @Autowired, de modo que haya un objeto virtual.

@MockBean
Si solo usa @MockBean, el objeto modificado se burlará, de modo que el método add() de Junit5Test ya no ejecutará los detalles específicos, pero MockBean se burlará de todos los métodos del objeto de destino, por lo que la prueba no se puede realmente ejecutado. , no se puede probar.

@SpyBean
y, en algunos casos, necesitamos ejecutar métodos reales, solo queremos simular algunos métodos, luego podemos usar @SpyBean.
El spyJunit5Test decorado con @SpyBean es un objeto real, solo cuando (spyJunit5Test.add(1,1)).thenReturn(2); el método add se bloquea y se sigue llamando a otros métodos.

Aquí hay un ejemplo

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));
    }
}

Finalmente, les deseo a todos un ¡Feliz Día del Programador!

Supongo que te gusta

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