Menos de un par. Otra forma de reducir la cantidad de pruebas

Cualquier QA conoce un método para minimizar los casos de prueba como las pruebas por pares: pruebas por pares. El método es excelente, bastante simple y probado por muchos equipos. Pero, ¿y si después de usarlo quedan demasiados casos?



Esto es exactamente lo que sucedió en mi proyecto, y hoy les diré cómo pueden reducir aún más el número de casos de prueba sin perder calidad.



imagen



Objeto de prueba



Primero, te contaré un poco sobre el producto. En Tinkoff, nuestro equipo desarrolló bloques: estos son componentes de React que consisten en implementación y configuración. La implementación es el propio componente, que hemos desarrollado y que el usuario ve en el navegador. La configuración es JSON que establece los parámetros y el contenido de este objeto.



La tarea principal de los bloques es ser bellos y mostrarse de la misma manera para diferentes usuarios. Al mismo tiempo, el bloque puede cambiar de manera muy significativa la configuración y el contenido.

Por ejemplo, un bloque puede ser así - sin fondo, con un botón y una imagen a la derecha:



imagen



O así - con un fondo, sin un botón y con una imagen a la izquierda:



imagen



O, en general, así - con un enlace en lugar de un botón y sin una lista en el texto:



imagen



Todos los ejemplos anteriores son el mismo bloque que tiene una versión de la configuración (una estructura JSON que este componente React en particular puede manejar), pero su contenido es diferente.



El circuito en sí:



{
  components: {
    background: color,

    panel: {
      panelProps: {
        color: {
          style: ['outline', 'color', 'shadow', 'custom'],

          background: color
        },

        size: ['s', 'm', 'l'],

        imagePosition: ['left', 'right']
      },

      title: {
        text: text,

        size: ['s', 'l'],

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      description: {
        text: html,

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      image: {
        alt: text,

        title: text,

        image: {
          src: image,

          srcset: [{
            src: image,

            condition: ['2x', '3x']
          }],

          webpSrcset: [{
            src: image,

            condition: ['1x', '2x', '3x']
          }]
        },

        imageAlign: ['top', 'center', 'bottom']
      },

      button: {
        active: boolean,

        text: text,

        color: {
          style: ['primary', 'secondary', 'outline', 'outlineDark', 'outlineLight', 'textLink', 'custom'],

          backgroundColor: color
        },

        onClick: {
          action: ['goToLink', 'goToBlock', 'showBlock', 'crossSale', 'callFormEvent'],

          nofollow: boolean,

          url: url,

          targetBlank: boolean,

          title: text,

          noindex: boolean,

          guid: guid,

          guidList: [{
            guid: guid
          }],

          formId: guid,

          crossSaleUrl: url,

          eventName: text
        },

        htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
      },

      href: url
    }
  }
}


En este caso, el bloque con la imagen de la derecha tendrá un valor components.panel.imagePosition = right. Y el bloque con la imagen de la izquierda tiene components.panel.imagePosition = left. Para un bloque con un botón, components.button.active = truey así sucesivamente. Espero que el principio sea claro. Así es como se configuran todos los parámetros del bloque.



Casos de una combinación de parámetros



En este artículo, no abordaré los problemas del control de versiones de los diagramas de bloques, las reglas de llenado de contenido o de dónde provienen los datos. Todos estos son temas separados que no afectan la compilación de un conjunto de casos de prueba. Lo principal a saber: tenemos muchos parámetros que afectan a nuestro componente y cada uno de ellos puede tomar su propio conjunto de valores.



imagen



Para el ejemplo anterior, elegí un bloque con una configuración bastante simple. Pero incluso en él, verificar todas las combinaciones de valores para todos los parámetros llevará un tiempo prohibitivo, especialmente si tiene que tener en cuenta la compatibilidad entre navegadores. Por lo general, las pruebas por pares vienen al rescate aquí, o las pruebas por pares. Ya se han escrito toneladas de artículos sobre él e incluso hay formación . Si de repente no se encuentra, asegúrese de leer.



Estimemos cuántos casos de prueba obtendremos al aplicarlo. Tenemos más de 25 parámetros y algunos de ellos toman hasta 7 y 9 variantes de valores. Sí, puede descuidar algo: por ejemplo, si está comprobando el diseño, la guía no es importante para usted. Pero al usar la prueba por pares, aún obtendrá más de 80 casos de prueba. Y esto, como ya escribí, no es para el bloque más complejo y sin tener en cuenta la compatibilidad entre navegadores. Ahora tenemos más de 150 bloques, y su número está creciendo, por lo que no podemos permitirnos tantos casos si queremos mantener la velocidad de las pruebas y el lanzamiento de nuevas versiones.



Casos de un parámetro



El método de prueba por pares se basa en la afirmación de que la mayoría de los defectos son causados ​​por la interacción de no más de dos factores. Es decir, la mayoría de los errores se manifiestan en un valor de un parámetro o en una combinación de los valores de dos parámetros. Decidimos ignorar la segunda parte de esta declaración y asumimos que al verificar un parámetro, aún se encontrarían la mayoría de los errores.



Entonces resulta que para las pruebas, necesitamos verificar cada valor de cada parámetro al menos una vez. Pero al mismo tiempo, cada bloque lleva la configuración completa. Luego, en cada nuevo caso, puede verificar el máximo de valores aún no verificados para minimizar el número de casos.



Analicemos el algoritmo para construir casos usando un ejemplo simplificado. Tomemos el componente de botón de nuestro esquema y compongamos casos de prueba para él:



 button: {
        active: boolean,

        text: text,

        color: {
          style: ['primary', 'secondary', 'outline', 'custom'],

          backgroundColor: color
        }


Para simplificar el ejemplo, reduje la longitud de la lista a button.color.style.



Paso 1. Redacte las opciones de contenido para cada campo



Todo aquí es como en las pruebas por pares: debe comprender qué valores puede tomar cada uno de los campos. Por ejemplo, button.activeen nuestro caso, puede haber solo dos valores: trueo false. En teoría, pueden surgir más opciones, por ejemplo, la undefinedausencia de la clave en sí.



Aquí, en mi opinión, es importante definir muy claramente los límites y la funcionalidad de su sistema y no verificar cosas innecesarias. Es decir, si la verificación de claves obligatorias o la validación de un valor se implementa en un sistema de terceros, esta funcionalidad debe verificarse en un sistema de terceros. Y solo deberíamos usar datos "correctos" como casos.



En general, el mismo principio se utiliza en la pirámide de pruebas. Si lo desea, se pueden agregar las pruebas de integración más críticas, por ejemplo, para verificar el procesamiento de una clave que no ha llegado. Pero debería haber un número mínimo de tales pruebas. Otro enfoque es la búsqueda de pruebas exhaustivas, lo que, como todos saben, es imposible.



Entonces, identificamos las opciones de contenido para cada campo y elaboramos la siguiente tabla:



imagen



Esta tabla incluye cada clase de equivalencia de cada parámetro, pero solo una vez.



Estas son las clases de valor en nuestro caso, las clases:



  • text_s - cadena corta;
  • text_m - cadena más larga;
  • no_color - sin color;
  • rnd_color es cualquier color.


Paso 2. Enriquecimiento de la tabla con datos



Dado que cada bloque tiene la configuración completa, necesitamos agregar algunos datos relevantes a las celdas en blanco:



imagen



ahora cada columna es un caso.



Al mismo tiempo, dado que nosotros mismos seleccionamos los datos que faltan, podemos generar casos en función de la prioridad. Por ejemplo, si sabemos que el texto corto se usa en un botón con más frecuencia que el texto de longitud media, entonces vale la pena verificarlo con más frecuencia.



En el ejemplo anterior, también puede prestar atención a los casos "descartados", casos en los que algún parámetro no se comprueba en absoluto, aunque está presente en la tabla. En este caso, button.color.style: secondaryno se comprobará su apariencia, porque no importa qué estilo tenga el botón desactivado.



Para evitar que los casos "descartados" produzcan errores, solíamos analizar los conjuntos de valores resultantes. El análisis se realizó una vez al generar casos de prueba, y todos los casos "descartados" se agregaron manualmente al caso de prueba final. Esta solución al problema es bastante torpe, pero barata (a menos que, por supuesto, rara vez cambie la configuración de los objetos probados).



Una solución más general es dividir todos los valores en dos grupos:



  1. valores inseguros (aquellos que pueden conducir a la "pérdida" de casos);
  2. seguro (que no puede provocar una "caída").


Cada valor inseguro se verifica en su propio caso de prueba, puede enriquecer el caso con cualquier dato seguro. Para valores seguros, se compila una tabla de acuerdo con las instrucciones anteriores.



Paso 3. Aclarando los valores



Ahora todo lo que queda es generar valores concretos en lugar de clases de equivalencia.



imagen



Aquí, cada proyecto deberá elegir sus propias opciones de valor, en función de las características del objeto probado. Algunos valores son muy fáciles de generar. Por ejemplo, puede simplemente tomar cualquier color para la mayoría de los campos. Para algunos bloques, al verificar el color, debe agregar un degradado, pero se mueve a una clase de equivalencia separada.



Con texto, es un poco más complicado: si genera una cadena a partir de caracteres aleatorios, no se probarán los guiones, las listas, las etiquetas y los espacios que no se rompen. Generamos cadenas cortas y medianas a partir de texto real, recortándolas hasta el número deseado de caracteres. Y en el texto largo comprobamos:



  • etiqueta html (cualquiera);
  • enlace;
  • lista sin numerar.


Este conjunto de casos se deriva directamente de nuestra implementación de bloque. Por ejemplo, todas las etiquetas html se incluyen juntas, por lo que no tiene sentido probar cada una. En este caso, el enlace y la lista se verifican por separado, porque tienen un procesamiento visual separado (resaltado al pasar el mouse y los tiroteos).



Resulta que para cada proyecto necesitas componer tu propio conjunto de contenido real basado en la implementación del objeto probado.



Algoritmo



Por supuesto, a primera vista puede parecer que el algoritmo es complejo y no merece la pena el esfuerzo. Pero si omite todos los detalles y excepciones que traté de describir en cada párrafo anterior, resulta bastante simple.



Paso 1. Agregue todos los valores posibles a la tabla de parámetros:



imagen



Paso 2. Duplique los valores en celdas vacías:



imagen



Paso 3. Convierta los valores abstractos en concretos y obtenga casos:



imagen



Cada columna de la tabla es un caso.



Ventajas del enfoque



Este método de generar casos de prueba tiene varias ventajas importantes.



imagen



Menos casos



En primer lugar, hay muchos menos casos que en las pruebas por pares. Si tomamos un ejemplo simplificado con un botón, obtenemos 4 casos en lugar de 8 en las pruebas por pares.



Cuantos más parámetros haya en el objeto probado, mayor será el ahorro en los casos. Por ejemplo, para el bloque completo presentado al principio del artículo, obtenemos 11 casos y, con la ayuda de pares, 260.



El número de casos no aumenta con la complicación de la funcionalidad.



La segunda ventaja es que cuando se tienen en cuenta nuevos parámetros durante las pruebas, el número de casos no siempre aumenta.



Por ejemplo, supongamos un parámetro button.color.textColorcon clases de equivalencia de valores no_colory se agrega a nuestro botón rnd_color. Entonces quedarán 4 casos, solo se agregará un parámetro más a cada uno de ellos: El



imagen



número de casos aumentará solo si algún parámetro tiene más valores que casos.



Puedes comprobar lo importante con más frecuencia



Al enriquecer los valores (paso 2 del algoritmo), los valores de mayor prioridad o más riesgosos pueden comprobarse con mayor frecuencia.



Por ejemplo, si sabemos que los usuarios anteriores usaban texto más corto con más frecuencia, y ahora usan texto más largo, podemos enriquecer los casos con texto más largo y entrar más a menudo en casos de usuarios reales.



Puede ser automatizado



El algoritmo anterior es bastante susceptible de automatización. Por supuesto, los casos generados por el algoritmo se parecerán menos a los reales que a los generados por humanos. Al menos haciendo coincidir colores y recortando texto.



Pero por otro lado, ya en el proceso de desarrollo sin la participación de un tester, aparecen casos, lo que reduce en gran medida el ciclo de retroalimentación.



imagen



desventajas



Naturalmente, esta generación de casos está lejos de ser una fórmula mágica y tiene sus inconvenientes.



Dificultad para analizar el resultado.



Creo que notó que en el proceso de generación de casos, los datos de prueba se mezclan entre sí. Debido a esto, cuando el caso cae, se vuelve más difícil identificar la causa de la caída. Después de todo, algunos de los parámetros utilizados en el caso no afectan de ninguna manera el resultado de la prueba.



Esto realmente dificulta analizar los resultados de la prueba, por un lado. Pero, por otro lado, si el objeto bajo prueba requiere una gran cantidad de parámetros requeridos, esto también dificulta encontrar la causa del error.



Los errores pueden perderse



Volviendo al principio del artículo: al utilizar este método, permitimos la posibilidad de omitir errores causados ​​por una combinación de dos o más parámetros. Pero ganamos en velocidad, por lo que depende de usted decidir qué es más importante para cada proyecto específico.



Para no perder los errores dos veces, presentamos la Política de error cero y comenzamos a cerrar cada error perdido con un caso de prueba adicional, que ya no se genera automáticamente, sino que se escribe a mano. Esto dio excelentes resultados: ahora tenemos más de 150 bloques (objetos probados), varios lanzamientos por día y de 0 a 3 errores no críticos perdidos por mes.



conclusiones



Si su objeto probado tiene una amplia gama de parámetros de entrada y desea intentar reducir la cantidad de casos y, como resultado, el tiempo de prueba, le recomiendo probar el método anterior para generar casos usando un parámetro.



En mi opinión, es ideal para componentes de front-end: puede reducir el tiempo en más de tres veces, por ejemplo, para verificar la apariencia a través de pruebas de captura de pantalla. Y el desarrollo irá más rápido debido a la aparición de casos en las primeras etapas.



Por supuesto, si está probando el piloto automático del nuevo Tesla, no debe descuidarse ni siquiera la pequeña probabilidad de perder un error. Pero en la mayoría de los casos, no olvide que la velocidad en el mundo moderno es un criterio de calidad muy importante. Y el aumento de velocidad da resultados más positivos que un par de problemas menores encontrados.



imagen



Y para los más responsables, en el próximo artículo les diré cómo pueden protegerse adicionalmente de errores engañosos causados ​​por una combinación de parámetros usando casos personalizados y StoryBook.



All Articles