pensar la práctica (junit5 + + jmockit testcontainer) prueba de la unidad

fondo

Antes de terminar un artículo, basado en (+ SpringCloud Junit5 + + Mockito DataMocker ) marco de clasificación pruebas unitarias. En ese momento el proyecto es una capa de orquestación de servicios, por lo que no hay ningún problema implica complejas bases de datos u otro middleware. Y el proyecto es el comienzo, no el código complejo entorno, la arquitectura era básicamente capaz de satisfacer la demanda.
Recientemente, en un proyecto mayor, ahora esperamos fortalecer la calidad del proyecto de código, por lo que comenzó a introducir marco de pruebas de unidad. Desde que primero introducido de acuerdo con el diseño original de todo el junit5 marco, mientras que la introducción de la base de datos h2 para la simulación y burlarse de la RabbitMQ servicio. Este proyecto utiliza marco SpringCloud Alibaba, registro de servicios y gestión de la configuración utilizando nacos, no muchos otros lugares especiales. Sin embargo, el proceso de escritura, hemos encontrado algunos problemas:

  • usos marco Mock Mockito y PowerMock, los desarrolladores tienen que utilizar los dos marcos.
  • base de datos de H2 y la base de datos Mysql real, hay algunas diferencias en comparación con la situación no puede apoyar este tipo de funciones, etc.
  • prueba de la unidad de preparación de datos es relativamente compleja, que afecta a lo bien diferente aislamiento unidad de pruebas es un problema.
  • Prueba de la unidad es tener cobertura o para asegurar la calidad de la fuerza, la forma de mejorar el personal de I + D de calidad de prueba en la unidad.

diseño

En respuesta a estos problemas, tenemos que resolver uno por uno.
La primera es para marco Mock que puede ser seleccionado marco Jmockit, los métodos comunes y métodos estáticos puede satisfacer directamente, pero la sintaxis es relativamente bueno como Mockito curva de aprendizaje natural es relativamente alta después de la inspección. Pero finalmente decidió tratar de hacer un marco unificado, reducir la complejidad de la arquitectura.
Seguido de problemas de bases de datos, hay dos opciones, una es la mejora de la base de datos de H2, se puede utilizar una función personalizada características para apoyar a los desaparecidos, pero el inconveniente es también muy clara, H2 no es siempre verdad base de datos MySQL. Encontrado segunda realización TestContainer, que es una operación de Java biblioteca acoplable, se puede utilizar código Java imagen generada directamente recipiente acoplable y en funcionamiento, por lo que no es una forma directa para iniciar medios contenedores MySQL para probar directamente después de completamente destruido. La desventaja de este enfoque es que los temas ambientales, toda necesidad de ejecutar el entorno de prueba de unidad de necesidad para instalar el soporte del estibador, contienen su propio departamento de I + D y CI medio ambiente. Pero la ventaja es que un programa de simulación de middleware universal posterior Redis, MQ son totalmente u otro middleware puede ser utilizado para simular un esquema de este tipo.
preparación de datos, el problema que creó dos formas de preparación de datos. La primera parte es para inicializar la base de datos, importar guión básico, el guión contiene esta parte de la estructura y los datos es la base del contenido de datos de toda la unidad común pruebas necesitan depender, como empresa, el departamento, el personal, los roles, permisos y así sucesivamente. La segunda parte es una única prueba de la unidad de inicialización de clase, la introducción de los datos de secuencia de comandos, los datos de sólo una única clase de prueba unidad / método, el método se deshará después de ejecutar, sin afectar el funcionamiento de otras pruebas de unidad.
Por último, la fuerza de la prueba de la unidad, sobre todo de algunas especificaciones, estas pruebas requieren que todas las unidades deben haber afirmado, y afirma la condición de que el contenido del campo de datos se autentica razonable. Se puede hacer referencia a una escritura valiosa prueba de la unidad .
Por lo que finalmente será el marco es Junit5 + + Jmockit TestContainer.

directrices para las pruebas unitarias

Antes de la estructura subyacente configurado, podemos hablar de la forma de escribir el valor real de la unidad de pruebas, en lugar de simplemente de la evolución de la cobertura de prueba de la unidad con el fin?
Antes del período mencionado en la escritura valiosa prueba de la unidad y el código de Java Ali mencionado en el estatuto en algún momento

Ali citó el estatuto:

  1. [Obligado] una buena unidad de pruebas deben cumplir con el principio de AIR.
    Descripción: Al ejecutar las pruebas de unidad en línea, se siente como el aire (AIR) como no existe, pero en las pruebas de control de calidad,
    pero es muy crítico. En una buena prueba de la unidad nivel macro, con automatizado, independencia, las características se pueden repetir. A: automática (Automation) I: Independiente (Independencia) R: repetible (repetición)
  2. las pruebas unitarias [Fuerza] deben realizarse totalmente automático y no interactiva. Y generalmente se realiza sobre una base regular, el Ejecutivo
    proceso de línea debe ser completamente automatizado tiene sentido. La prueba de salida requiere una inspección manual no es una prueba de unidad buena. Unidad de
    prueba utilizando System.out permitió comprobar la carne humana, debe valer para verificar.
  3. [Obligado] para mantener la independencia de las pruebas unitarias. Para garantizar la estabilidad y la unidad de pruebas fiables y de fácil mantenimiento entre unidad de prueba
    no debe llamar a los demás, no podemos confiar en la orden de ejecución.
    Anti Ejemplo: la realización de necesidad method2 que confiar en el method1, method2 como entrada el resultado de la ejecución.
  4. pruebas unitarias [obligatorio] se pueden realizar varias veces, no puede verse afectada por el entorno externo.
    Descripción: Prueba de la unidad por lo general se coloca en la integración continua, cada vez que se realiza la comprobación de código en las pruebas unitarias. Como
    si una sola medición con una dependencia sobre el medio ambiente externo (servicios de red, middleware, etc.), no conduce fácilmente a un mecanismo de integración continua.
    Ejemplo positivo: Con el fin de influencia del ambiente externo, requiere que el diseñador para poner el código en la inyección de dependencia SUT, cuando la prueba de primavera
    de inyección de un local (memoria) de bastidor DI tales implementaciones o Mock implementado.
  5. [Fuerza] para la unidad de pruebas, la prueba para asegurar un tamaño de partícula suficientemente pequeño para ayudar a identificar el problema. La medición de un tamaño de partícula de como máximo un solo nivel de clase
    no, el método es generalmente nivel.
    Descripción: Sólo prueba de tamaño pequeño para localizar el error en la ubicación del error tan pronto como sea posible. medición individual no es responsable de comprobar clase cruz o entre sistemas
    lógica de interacción, que es una zona de pruebas de integración.

Algunas de estas ideas determinará la aplicación específica en la prueba de la unidad de código. Luego intentamos, hay dos implementaciones diferentes de acuerdo con las pautas anteriores.

  • Single-aislamiento
  • la penetración interna

A continuación explicamos de dos maneras.

Single-aislamiento

Código se dividirá en controlador normal de capas, el servicio, DAO, etc., en una sola capa en el aislamiento de pensamiento, de cada unidad de prueba se hace para el código de cada capa no penetra hacia abajo. Esta redacción es principalmente para asegurar la lógica de negocio único y bien curada.
En la práctica, por ejemplo, la unidad comprueba el código de la clase controlador de escritura capa del controlador correspondiente al archivo externo toda la maqueta todas las llamadas, incluyendo el servicio interno / externo correspondiente. Otras capas de código.

La ventaja de hacer esto:

  • código de prueba de unidad es extremadamente ligero, rápido. Dado que sólo una única lógica interna para asegurar que la clase correcta, todos los demás simulacro, simulacro de middleware para que pueda darse por vencido, incluso la inyección de primavera puede renunciar, se centran en escribir pruebas de unidad de verificación de la lógica. Tal operación se completa el código de prueba unidad de paquete también debe cronógrafo segundos rueda, relativamente completa contenedor del resorte de inicialización puede necesitar 20 segundos.
  • En línea con el principio de la unidad de pruebas verdaderas se puede ejecutar en el caso de la red de apagado. lógica de una sola capa puede proteger el registro de servicio y gestión de la configuración, el impacto de varios middleware.
  • Significa una mayor calidad de la prueba. Para verificación de la lógica de una sola capa y la afirmación de ser más clara, si se quiere cubrir un multi-capa, puede ignorar el eslabón perdido entre los diversos autenticación, si se combina con la condición puede ser del tamaño de un producto cartesiano es demasiado complejo.

También hay un inconveniente:

  • Más grande que el código de prueba de unidad, ya que cada unidad de escritura individual de las pruebas, sino también fuera de la maqueta es más dependiente del exterior.
  • La curva de aprendizaje es relativamente alta, ya que el programador es habitual que la validación de entrada de salida de prueba célula dada. Así que no hay salida lógica subyacente de un simple proceso de verificación hay una transición en un pensamiento.
  • Para los proyectos de baja complejidad relativamente poco amigable. Si su proyecto es sobre todo después de un CRUD jerárquica simple, hecho verificable que las pruebas de unidad no es demasiado material. Sin embargo, si el código que se ejecuta una lógica compleja, tales redacción será capaz de jugar un mejor control de calidad.

En este proyecto, no adoptar este método, en lugar de utilizar una manera penetrante. Escena del personal del proyecto, la complejidad de la situación, creo que de esta manera no es muy apropiado.

la penetración interna

La penetración, la naturaleza es siempre llamar desde la parte superior a la parte inferior. ¿Por qué más interna de las palabras? Además del método que puede penetrar dentro del proyecto, el proyecto todavía tiene que depender de salida simulada externa.
Practice, es escribir pruebas unitarias para capa del controlador, pero se completa servicio de llamadas, DAO, los resultados finales se verifican en el suelo.

ventajas:

  • la cantidad de código es relativamente pequeño, ya que está cubierto con capas múltiples de penetración del código requiere sólo una unidad de prueba para verificar desde la parte superior.
  • baja curva de aprendizaje, penetrando prefieren unidad de negro pruebas de caja, las condiciones de entrada desarrollador configurados, y los resultados de los asistentes (almacenamiento, tales como una base de datos) para verificar los resultados esperados.

desventajas:

  • En general más pesados, iniciar contenedor de primavera, simulacro de middleware, se espera de ejecución global de prueba de unidad para que levante acta se requiere nivel. Tan básico que se va a ejecutar cuando el IC.

tecnología

Después de finalizar el programa técnico que podemos lograr, que es un proyecto Java, utilice la gestión de la dependencia Maven. A continuación nos dividimos en tres partes introducción:

  • gestión de la dependencia
  • infraestructura
  • Los ejemplos de realización

gestión de la dependencia

Gestión se basa primer punto a la nota, debido a la actual Junit4 también ocupan más mercado, tenemos que tratar de excluir algunas pruebas relacionadas con la dependencia contiene una referencia a la 4.
He publicado la primera parte del archivo siguiente Pom y pruebas unitarias asociadas

        <!-- Jmockit -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.49</version>
            <scope>test</scope>
        </dependency>

        <!-- junit5 框架 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.6.1</version>
            <scope>test</scope>
        </dependency>

        <!--  Spring Boot 测试框架 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <!-- exclude junit 4 -->
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--  公司内部封装的一个数据自动Mock框架,来源于Jmockdata -->
        <dependency>
            <groupId>cn.vv.service.unittest</groupId>
            <artifactId>vv-data-mocker</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>

        <!--  testcontainers对于mysql的封装包,当然也可以将mysql替换为testcontainers,这样直接引入底层容器包 -->
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>mysql</artifactId>
            <version>1.12.0</version>
            <scope>test</scope>
        </dependency>

        <!--  testcontainers 容器对于junit5的支持 -->
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>1.12.0</version>
            <scope>test</scope>
        </dependency>

La introducción de estas depende de la base, que también tiene que ser señalado que la segura plugin de configuración

<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M4</version>
                <configuration>
                    <argLine>-javaagent:${settings.localRepository}/org/jmockit/jmockit/1.49/jmockit-1.49.jar
                        -Dfile.encoding=UTF-8 -Xmx1024m
                    </argLine>
                    <enableAssertions>true</enableAssertions>
                    <!-- <useSystemClassLoader>true</useSystemClassLoader>-->
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.apache.maven.surefire</groupId>
                        <artifactId>surefire-api</artifactId>
                        <version>3.0.0-M4</version>
                    </dependency>
                </dependencies>
            </plugin>

Tenga en cuenta el punto aquí es Jmockit necesidad javaagent para inicializar los parámetros de JVM.

infraestructura

Parte de la infraestructura, creo que se divide en tres términos:

  • grupo de prueba de unidad, un paquete de algunos objetos y elementos básicos Mock métodos comunes usados
  • configuración de prueba Unidad relacionados
  • paquete TestContainer

De hecho, después de estos tres puntos son, hablando sus propias implementaciones independientes asociados con la clase base de pruebas de unidad, con el tiempo se dará el código completo.

Paquete Junit5 y Jmockit

En primer lugar, la sección de notas de anotación 5 Junit4 para ajustar y cambiar, y nuestro proyecto se basa SpringCloud, por lo que la clase de base de las pruebas finales BaseTest utiliza tres notas

@SpringBootTest(classes = {OaApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
@Slf4j

La cabeza de la clase es Junit5 no necesita ninguna anotación, y sobre todo con la primavera, que utiliza SpringBootTest proporcionar anotación de la prueba de inicio, especificando el inicio de entrada de la clase, con el fin de incluir el archivo de configuración, obtener nacos configuración.
Transacción método de anotación de datos es permitir que la operación se puede deshacer, no afecta a otras pruebas unitarias.
El paso final es notas del registro de Lombok.

El siguiente paso es BeforeAll, después de todo, beforeeach, AfterEach algunas notas.
La idea aquí es utilizar Jmockit, el tratamiento de los mecanismos subyacentes dentro de la empresa sistema de prueba unificada tratamiento simulado, tales como la cabeza de solicitud de información o sesión. Aquí está el código que pude y que cada proyecto más diferencias, sólo un pensamiento. Mock Jmockit utilizar métodos estáticos conseguir algunos de nuestros objetos, el objeto devuelve directamente el resultado de nuestro diseño.


    @BeforeAll
    protected static void beforeAll() {

        new MockUp<ShiroUtils>(ShiroUtils.class) {
            @Mock
            public EmployeeVO getEmployee() {
                EmployeeVO employeeVO = new EmployeeVO();
                employeeVO.setUserName("mock.UserName");
                employeeVO.setUserNo("mock.UserNo");
                employeeVO.setCompanyName("mock.CompanyName");
                employeeVO.setDepartmentName("mock.DepartmentName");
                return employeeVO;
            }
        };
        new MockUp<LogAspect>(LogAspect.class) {
            @Mock
            public String getIp() {
                return "mock.ip";
            }
        };
    }

    @AfterAll
    protected static void destroy() {
    }

    @BeforeEach
    protected void beforeEach() {

        new MockUp<WebUtil>(WebUtil.class) {
            @Mock
            public HttpServletRequest getRequest() {
                return getRequest;
            }

            @Mock
            public VvCurrentAccount getCurrentAccount(Boolean isMustLogin) {
                VvCurrentAccount vvCurrentAccount = new VvCurrentAccount();
                vvCurrentAccount.setUserCode("mock.userCode");
                return vvCurrentAccount;
            }
        };
        new MockUp<ServletUtils>(ServletUtils.class) {
            @Mock
            public HttpServletRequest getRequest() {
                return getRequest;
            }
        };


        if (StringUtil.isNotBlank(this.getDbScript())) {
            try {
                ScriptRunner runner = new ScriptRunner(dataSource.getConnection());
                runner.setErrorLogWriter(null);
                runner.setLogWriter(null);
                runner.runScript(new FileReader(this.getClass().getResource(this.getDbScript()).getPath()));
            } catch (Exception e) {
                log.error("ScriptRunner error!", e);
            }
        }
    }

    @AfterEach
    protected void afterEach() {
    }


    protected String getDbScript() {
        return "";
    }

Hay un punto de diseño se puede discutir, beforeeach llama getDbScript, los datos utilizados para construir una sola clase de prueba de unidad requerida antes de que el método de prueba de la unidad. Y debido a que los hereda de la clase la reversión de transacción por defecto, por lo que esta operación se complete los datos se deshacen después de que el final del proceso, por lo que el impacto de los datos al mínimo.
Cada unidad de las clases de prueba, siempre y cuando el método getDbScript reescritura, proporcionan su propio script de base de datos. Tal método de ensayo una diseñada para aislar el nivel de la unidad de datos.

Configuración de la unidad de prueba

Desde el marco de este proyecto utiliza Nacos, y su espacio de direcciones se encuentra en el archivo de configuración de Pom, especificar el perfil en tiempo de ejecución para invocar configurar diferentes entornos. Durante el uso normal, direcciones de acceso a la información, contraseñas de usuario y otra middleware se guarda en Nacos, debido a la necesidad de ejecutar la unidad de pruebas simuladas cierto middleware, por lo que todas las necesidades de información que ser reemplazado.
La primera versión es el uso de sus propias características Nacos, cabeza de prueba Unidad utilizada @ActiveProfile("")más tarde, se lee en el archivo de configuración correspondiente a las propiedades para reemplazar el marcador de posición, tales como el original está escrito en nuestra configuración que vv-oa.yml Especifica ActiveProfile ( "test"), será el de archivo de carga vv-oa-test.properties, que se utiliza para reemplazar la configuración yml.
Por este método para lograr el propósito de reemplazar sólo pruebas de unidad de conexión intermedia.
Sin embargo, debido a la utilización de middleware Mock método TestContainer, de hecho, puede no directamente recipiente dirección es fija, por lo que el programa no es muy apropiado. Mediante el uso de la forma (configuración automática) configurada localmente, crear un nuevo perfil basado en el paquete de prueba de unidad.


@Configuration
@EnableTransactionManagement
public class JunitDataSource {

    @Bean
    public DataSource dataSource() throws Exception {
        Properties properties = new Properties();
        properties.setProperty("driverClassName", System.getProperty("spring.datasource.driver-class-name"));
        properties.setProperty("url", System.getProperty("spring.datasource.url"));
        properties.setProperty("username", System.getProperty("spring.datasource.username"));
        properties.setProperty("password", System.getProperty("spring.datasource.password"));
        return DruidDataSourceFactory.createDataSource(properties);
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws Exception {
        return new DataSourceTransactionManager(dataSource());
    }

}

Otro middleware también se puede utilizar de la misma manera.

paquete TestContainer

En primer lugar, para ofrecerle el sitio web oficial y su librería de muestras de código Github , una gran cantidad de uso es una referencia a la fuente. En este trabajo, un recipiente como ejemplos de MySQL le dará una breve introducción a utilizar.

El programa oficial

En el documento oficial de la base de datos de contenedores sección, se describen dos maneras de utilizar el contenedor de base de datos:

  • Inicio código de contenedor
  • contenedor de inicio mediante JDBC URL
   @Rule
    public MySQLContainer mysql = new MySQLContainer();

Iniciar el código es tan simple, se inicia un simple contenedor MySQL, la información de configuración por defecto es la siguiente:

    public static final String NAME = "mysql";
    public static final String IMAGE = "mysql";
    public static final String DEFAULT_TAG = "5.7.22";
    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = "TC_MY_CNF";
    public static final Integer MYSQL_PORT = 3306;
    private String databaseName = "test";
    private String username = "test";
    private String password = "test";
    private static final String MYSQL_ROOT_USER = "root";

Luego, en la llamada BeforeAll mysql.start(), se iniciará el contenedor.

JDBC de manera más simple, sin ningún tipo de código en la configuración de la unidad directa y especifica a url

spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.url=jdbc:tc:mysql:5.7.22:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql&TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction

Aquí hay varios puntos a la nota

  • El conductor debe utilizar el TC suministrado
  • url en MySQL con el tiempo después del número de versión, la correspondencia puede ser entendido como dockerhub en la versión mysql reflejado es en realidad la versión actual de MySQL.
  • tc, entonces la base de datos inicial proporciona dos formas, especificando directamente guión TC_INITSCRIPT , o el código de inicialización de la clase especificada TC_INITFUNCTION , estos dos métodos pueden existir simultáneamente .

El programa real

Cuando se utiliza en el proyecto son dos formas anteriores en realidad no es muy buena, MySQLContainer después de un relativamente pequeño paquete de contenido personalizado, forma JDBC es el mismo problema como la configuración de puertos, etc., no se pueden establecer.
Para una mayor flexibilidad, se utiliza la clase de contenedor de base más primitiva para construir usted mismo un recipiente de MySQL. En primer lugar dada directamente código.


    @ClassRule
    public static GenericContainer mysql = new VvFixedHostPortGenericContainer(
            new ImageFromDockerfile("mysql-vv-gms")
                    .withDockerfileFromBuilder(dockerfileBuilder -> {
                        dockerfileBuilder.from("mysql:8.0.0")
                                .env("MYSQL_ROOT_PASSWORD", "test")
                                .env("MYSQL_DATABASE", "test")
                                .env("MYSQL_USER", "test")
                                .env("MYSQL_PASSWORD", "test")
                                .add("my.cnf", "/etc/mysql/conf.d")
                                .add("db-schema.sql", "/docker-entrypoint-initdb.d")
                        ;
                    })
                    .withFileFromClasspath("my.cnf", "my.cnf")
                    .withFileFromClasspath("db-schema.sql", "db-schema.sql")
    )
            .withFixedExposedPort(3307, 3306)
            .waitingFor(Wait.forListeningPort());
package cn.vv.oa.init;

import lombok.NonNull;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.InternetProtocol;

import java.util.concurrent.Future;

public class VvFixedHostPortGenericContainer<SELF extends VvFixedHostPortGenericContainer<SELF>> extends GenericContainer<SELF> {

    public VvFixedHostPortGenericContainer(@NonNull final Future<String> image) {
        super(image);
    }

    /**
     * Bind a fixed TCP port on the docker host to a container port
     *
     * @param hostPort a port on the docker host, which must be available
     * @param containerPort a port in the container
     * @return this container
     */
    public SELF withFixedExposedPort(int hostPort, int containerPort) {

        return withFixedExposedPort(hostPort, containerPort, InternetProtocol.TCP);
    }

    /**
     * Bind a fixed port on the docker host to a container port
     *
     * @param hostPort a port on the docker host, which must be available
     * @param containerPort a port in the container
     * @param protocol an internet protocol (tcp or udp)
     * @return this container
     */
    public SELF withFixedExposedPort(int hostPort, int containerPort, InternetProtocol protocol) {

        super.addFixedExposedPort(hostPort, containerPort, protocol);

        return self();
    }
}

El segundo hecho puede no VvFixedHostPortGenericContainer preocupación particular, el puerto designado clase se expone sólo a los métodos de la clase base del recipiente, y mediante la construcción de imagen Dockerfile constructor generado. La clave está en ver parte del primer párrafo del contenedor comunicado MySQL.
withDockerfileFromBuilder este método, ya que no se especifica el constructor Dockerfile, el método puede exponer comando Dockerfile son capaces de escribir, si usted sabe que es bueno ventana acoplable manera personalizada. Añadir archivos de comandos que se pueden añadir, tenemos que volver con el mapa withFileFromClasspath.
Comenzará los dos puertos 3306 y 33060 después de la exposición para especificar el puerto, mysql8 por el método withFixedExposedPort, sólo tenemos que estar expuestos a 3306.
Añadir los dos archivos que aquí también tenemos que averiguarlo.
my.cnf archivo para anular la configuración por defecto de MySQL, la base de datos de codificación puede resolver el problema de fondo se establece que señalar que la ruta del archivo para añadir comandos para agregar /etc/mysql/conf.d camino a la configuración inicial.
db-schem.sql es inicializar los scripts de base, añadiendo en el contenedor /docker-entrypoint-initdb.d ruta se realiza de forma automática, pero tenga en cuenta que sólo se puede añadir un guión.
Por cierto, también es my.cnf Tieshanglai que podría afectar a la base de datos china ilegible

[mysqld]
user = mysql
datadir = /var/lib/mysql
port = 3306
#socket = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 16K
max_allowed_packet = 1M
table_open_cache = 4
sort_buffer_size = 64K
read_buffer_size = 256K
read_rnd_buffer_size = 256K
net_buffer_length = 2K
skip-host-cache
skip-name-resolve
character-set-server = utf8
collation-server = utf8_general_ci

# Don't listen on a TCP/IP port at all. This can be a security enhancement,
# if all processes that need to connect to mysqld run on the same host.
# All interaction with mysqld must be made via Unix sockets or named pipes.
# Note that using this option without enabling named pipes on Windows
# (using the "enable-named-pipe" option) will render mysqld useless!
#
#skip-networking
#server-id = 1

# Uncomment the following if you want to log updates
#log-bin=mysql-bin

# binary logging format - mixed recommended
#binlog_format=mixed

# Causes updates to non-transactional engines using statement format to be
# written directly to binary log. Before using this option make sure that
# there are no dependencies between transactional and non-transactional
# tables such as in the statement INSERT INTO t_myisam SELECT * FROM
# t_innodb; otherwise, slaves may diverge from the master.
#binlog_direct_non_transactional_updates=TRUE

# Uncomment the following if you are using InnoDB tables
innodb_data_file_path = ibdata1:10M:autoextend
# You can set .._buffer_pool_size up to 50 - 80 %
# of RAM but beware of setting memory usage too high
innodb_buffer_pool_size = 16M
#innodb_additional_mem_pool_size = 2M
# Set .._log_file_size to 25 % of buffer pool size
innodb_log_file_size = 5M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50

[mysql.server]
default-character-set=utf8
[mysql_safe]
default-character-set=utf8
[client]
default-character-set=utf8

código completo de clases

package cn.vv.oa;

import cn.vv.OaApplication;
import cn.vv.fw.common.api.VvCurrentAccount;
import cn.vv.fw.common.utils.StringUtil;
import cn.vv.fw.common.utils.WebUtil;
import cn.vv.oa.api.org.vo.EmployeeVO;
import cn.vv.oa.common.aspectj.LogAspect;
import cn.vv.oa.common.filter.TokenAuthorFilters;
import cn.vv.oa.common.shiro.ShiroUtils;
import cn.vv.oa.common.utils.ServletUtils;
import cn.vv.oa.init.VvFixedHostPortGenericContainer;
import lombok.extern.slf4j.Slf4j;
import mockit.Mock;
import mockit.MockUp;
import mockit.Mocked;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.shiro.authz.aop.PermissionAnnotationHandler;
import org.junit.ClassRule;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.FileReader;

@SpringBootTest(classes = {OaApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
@Slf4j
public class BaseTest {

    @ClassRule
    public static GenericContainer mysql = new VvFixedHostPortGenericContainer(
            new ImageFromDockerfile("mysql-vv-gms")
                    .withDockerfileFromBuilder(dockerfileBuilder -> {
                        dockerfileBuilder.from("mysql:8.0.0")
                                .env("MYSQL_ROOT_PASSWORD", "test")
                                .env("MYSQL_DATABASE", "test")
                                .env("MYSQL_USER", "test")
                                .env("MYSQL_PASSWORD", "test")
                                .add("my.cnf", "/etc/mysql/conf.d")
                                .add("db-schema.sql", "/docker-entrypoint-initdb.d")
                        ;
                    })
                    .withFileFromClasspath("my.cnf", "my.cnf")
                    .withFileFromClasspath("db-schema.sql", "db-schema.sql")
    )
            .withFixedExposedPort(3307, 3306)
            .waitingFor(Wait.forListeningPort());

    @Resource
    protected DataSource dataSource;

    @Mocked
    PermissionAnnotationHandler permissionAnnotationHandler;
    @Mocked
    cn.vv.fw.boot.logger.RequestLogAspect RequestLogAspect;
    @Mocked
    TokenAuthorFilters tokenAuthorFilters;
    @Mocked
    HttpServletRequest getRequest;

    @BeforeAll
    protected static void beforeAll() {
        mysql.start();

        System.setProperty("spring.datasource.url", "jdbc:mysql://" + mysql.getContainerIpAddress() + ":3307/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8");
        System.setProperty("spring.datasource.driver-class-name", "com.mysql.cj.jdbc.Driver");
        System.setProperty("spring.datasource.username", "test");
        System.setProperty("spring.datasource.password", "test");

        new MockUp<ShiroUtils>(ShiroUtils.class) {
            @Mock
            public EmployeeVO getEmployee() {
                EmployeeVO employeeVO = new EmployeeVO();
                employeeVO.setUserName("mock.UserName");
                employeeVO.setUserNo("mock.UserNo");
                employeeVO.setCompanyName("mock.CompanyName");
                employeeVO.setDepartmentName("mock.DepartmentName");
                return employeeVO;
            }
        };
        new MockUp<LogAspect>(LogAspect.class) {
            @Mock
            public String getIp() {
                return "mock.ip";
            }
        };
    }

    @AfterAll
    protected static void destroy() {
        mysql.stop();
    }

    @BeforeEach
    protected void beforeEach() {

        new MockUp<WebUtil>(WebUtil.class) {
            @Mock
            public HttpServletRequest getRequest() {
                return getRequest;
            }

            @Mock
            public VvCurrentAccount getCurrentAccount(Boolean isMustLogin) {
                VvCurrentAccount vvCurrentAccount = new VvCurrentAccount();
                vvCurrentAccount.setUserCode("mock.userCode");
                return vvCurrentAccount;
            }
        };
        new MockUp<ServletUtils>(ServletUtils.class) {
            @Mock
            public HttpServletRequest getRequest() {
                return getRequest;
            }
        };


        if (StringUtil.isNotBlank(this.getDbScript())) {
            try {
                ScriptRunner runner = new ScriptRunner(dataSource.getConnection());
                runner.setErrorLogWriter(null);
                runner.setLogWriter(null);
                runner.runScript(new FileReader(this.getClass().getResource(this.getDbScript()).getPath()));
            } catch (Exception e) {
                log.error("ScriptRunner error!", e);
            }
        }
    }

    @AfterEach
    protected void afterEach() {
    }

    protected String getDbScript() {
        return "";
    }

}

Los ejemplos de realización

interfaz real de la empresa como un ejemplo, nuestra entrada unidad de prueba desde el método Controller.

package cn.vv.oa.module.org.controller;

import cn.vv.fw.common.api.R;
import cn.vv.oa.BaseTest;
import cn.vv.oa.api.org.dto.CompanyDTO;
import cn.vv.oa.module.org.entity.Company;
import cn.vv.oa.module.org.repository.mapper.CompanyMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.junit.jupiter.api.Test;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class CompanyControllerTest extends BaseTest {

    @Resource
    CompanyController companyController;

    @Resource
    CompanyMapper companyMapper;

    @Test
    public void getList() throws Exception {
        List dtos = companyController.getList("100", "").getData();
        assertEquals(((Map) (dtos.get(0))).get("companyName"), "VV科技集团");
    }

    @Test
    void getAllList() {
        List<Company> list = companyMapper.selectList(new LambdaQueryWrapper<Company>());
        assertEquals(list.size(), 3);
    }


    @Test
    void saveOrUpdate() throws Exception {
        CompanyDTO companyDTO = CompanyDTO.builder()
                .companyName("VV日本公司")
                .parentId(new BigInteger("100"))
                .companyEmail("[email protected]")
                .companyArea(Arrays.asList("Japan"))
                .regTime(LocalDate.now())
                .build();

        R r = companyController.saveOrUpdate(companyDTO);

        List<Company> list = companyMapper.selectList(new LambdaQueryWrapper<Company>());
        assertEquals(list.size(), 4);

    }
}

Esta unidad pone a prueba el controlador capas de cobertura de código, servicio, de DAO. La primavera puede ser visto por el uso responsable de forma inyectada u original.
Se hace notar aquí que el punto a ensayar después de la llamada a un método de pruebas de unidad, ya que tenemos que verificar los datos a través del suelo, por lo que necesita una inyección correspondiente Mapper directamente en la búsqueda de bases de datos. Este punto será una cierta vuelta o no directamente.
Este es un ejemplo de una penetración. Veamos un ejemplo aislado.


    @Test
    void save() {
        R<AccountSimpleVO> r = new R<>();
        AccountSimpleVO accountSimpleVO = new AccountSimpleVO();
        accountSimpleVO.setUserCode("usercode");
        r.setCode(ResultCode.SUCCESS.getCode());
        r.setData(accountSimpleVO);

        new Expectations() {{
            userMapper.selectList((Wrapper<User>) any);
            result = null;

            userClient.getUserInfo((AccountDTO) any);
            result = null;

            userClient.registered((AccountDTO) any);
            result = r;

            companyMapper.selectOne((Wrapper<Company>) any);
            Company company = new Company();
            company.setCompanyArea("中国");
            result = company;
        }};

        new MockUp<DictUtil>(DictUtil.class) {
            @Mock
            public Map<String, DictDTO> getDictNameMap(String code) {
                Map<String, DictDTO> r1 = new HashMap<>();
                DictDTO dictDTO = new DictDTO();
                dictDTO.setRemark("30");
                r1.put("美国", dictDTO);
                return r1;
            }

            @Mock
            public Map<String, DictDTO> getDictMap(String code) {
                Map<String, DictDTO> r2 = new HashMap<>();
                DictDTO dictDTO = new DictDTO();
                dictDTO.setRemark("86");
                r2.put("中国", dictDTO);
                return r2;
            }

        };

        Assertions.assertThrows(NullPointerException.class, () -> {
            employeeService.save(new EmployeeDTO());
        });
    }

Este ejemplo es un servicio método de ensayo específico, se puede ver burlan de una gran cantidad de servicios internos y externos, incluyendo el asignador subyacente se burla de los medios de contenido que los datos se han leído completamente devueltos en cuarentena.

resumen

Las pruebas unitarias, tenemos un consenso es uno de los medios más importantes de la calidad del código, pero necesitamos realmente " valiosas pruebas de unidad". Verdaderamente valioso medio para mantener la calidad de los proyectos, la investigación y el desarrollo se puede hacer una verdadera disposición a gastar energía para escribir y mantener los casos de prueba. Si nos fijamos en la cobertura de las pruebas unidad de empresa, es realmente muy bueno tonto, y esto se convierte en la cara y no tiene valor. I + D para las pruebas de unidad de escritura única de rendimiento, alta cobertura, no contribuye a mejorar la calidad de los proyectos.
Si usted está leyendo este artículo usted es un líder, debe ser una batalla personal para llevar al equipo a poner en práctica en serio, guiar al equipo a entender realmente el valor de escribir las pruebas unitarias.
Nuestro equipo también está todavía va a tratar, en nuestras pruebas, la generación de la unidad de pruebas de valor, la cantidad de código es 2-3 veces el código de negocio real. Y cuando el negocio es inestable, el código de servicio de mantenimiento también dar lugar a la modificación de código de prueba de unidad, cambie la eficiencia del código es escribir la mitad de la eficiencia del código, el costo es muy alto.

Supongo que te gusta

Origin www.cnblogs.com/pluto4596/p/12610333.html
Recomendado
Clasificación