Qué son las pruebas

Cuando escribes software, puedes realizar pruebas para confirmar que funciona correctamente. En términos generales, las pruebas se pueden definir como el proceso de ejecutar software de maneras específicas para garantizar que se comporte según lo previsto.

Las pruebas exitosas pueden darte la confianza de que, a medida que agregues código o funciones nuevos, o incluso actualices tus dependencias, el software que ya escribiste seguirá funcionando de la manera esperada. Las pruebas también pueden ayudar a proteger el software contra situaciones improbables o entradas inesperadas.

Estos son algunos ejemplos de comportamiento en la Web que quizás desees probar:

  • Garantizar que la función de un sitio web funcione correctamente cuando se hace clic en un botón
  • Confirmar que una función compleja produce los resultados correctos
  • Completar una acción que requiere el acceso del usuario.
  • Verificar que un formulario informe correctamente un error cuando se ingresan datos con errores de formato
  • Asegurarse de que una app web compleja siga funcionando cuando un usuario tenga un ancho de banda muy bajo o se desconecte

Comparación entre las pruebas automáticas y manuales

Puedes probar tu software de dos maneras generales: pruebas automatizadas y pruebas manuales.

Las pruebas manuales implican que las personas ejecuten software directamente, como cargar un sitio web en su navegador y confirmar que se comporta según lo esperado. Las pruebas manuales son fáciles de crear o definir. Por ejemplo, ¿puede tu sitio cargar? ¿Puedes realizar estas acciones? Pero cada repaso cuesta una enorme cantidad de tiempo para una persona. Si bien las personas son muy creativas, lo que puede permitir un tipo de pruebas conocidas como pruebas de exploración, de todos modos podemos notar fallas o incoherencias, en especial cuando se realiza la misma tarea muchas veces.

Las pruebas automatizadas son cualquier proceso que permite que una computadora codifique y ejecute pruebas de forma repetida para confirmar el comportamiento previsto de tu software sin que una persona realice pasos repetidos, como la configuración o la verificación de los resultados. Es importante destacar que, una vez que se configuran las pruebas automatizadas, se pueden ejecutar con frecuencia. Esta es una definición muy amplia y vale la pena señalar que las pruebas automatizadas toman todo tipo de formas. La mayor parte de este curso aborda las pruebas automatizadas como práctica.

Las pruebas manuales tienen su lugar, a menudo como un precursor de la escritura de pruebas automatizadas, pero también cuando estas se vuelven demasiado poco confiables, amplios o difíciles de escribir.

Los aspectos básicos a través de un ejemplo

Para nosotros, como desarrolladores web que escriben código JavaScript o lenguajes relacionados, una prueba automatizada y concisa podría ser una secuencia de comandos como esta que ejecutas todos los días, quizás a través de Node o la carga en un navegador:

import { fibonacci } from "../src/math.js";

if (fibonacci(0) !== 0) {
  throw new Error("Invalid 0th fibonacci result");
}
const fib13 = fibonacci(13);
if (fib13 !== 233) {
  throw new Error("Invalid 13th fibonacci result, was=${fib13} wanted=233");
}

Este es un ejemplo simplificado que proporciona las siguientes estadísticas:

  • Esta es una prueba porque ejecuta algún software (la función Fibonacci) y garantiza que su comportamiento funcione de la manera prevista mediante la verificación de sus resultados con los valores esperados. Si el comportamiento no es correcto, se genera un error que JavaScript expresa con el lanzamiento de una Error.

  • Aunque puedes ejecutar esta secuencia de comandos de forma manual en tu terminal o en un navegador, esta sigue siendo una prueba automatizada, ya que se puede ejecutar repetidamente sin tener que realizar ningún paso individual. En la siguiente página, donde se ejecutan las pruebas, se explica más información.

  • Aunque esta prueba no usa ninguna biblioteca (es JavaScript que se puede ejecutar en cualquier lugar), sigue siendo una prueba. Existen muchas herramientas que pueden ayudarte a escribir pruebas, incluidas las que se analizarán más adelante en este curso, pero todas siguen funcionando según el principio fundamental de generar un error si algo sale mal.

Cómo probar bibliotecas en la práctica

La mayoría de las bibliotecas o los frameworks de prueba integrados proporcionan dos primitivas principales que facilitan la escritura de las pruebas: aserciones y una forma de definir pruebas independientes. Se tratarán en detalle en la siguiente sección, sobre aserciones y otras primitivas. Sin embargo, en términos generales, es importante recordar que casi todas las pruebas que veas o escribas terminarán usando estos tipos de primitivas.

Las aserciones son una forma de combinar la verificación de un resultado y causar un error si algo sale mal. Por ejemplo, puedes hacer que la prueba anterior sea más concisa si presentas assert:

import { fibonacci } from "../src/math.js";
import { assert } from "a-made-up-testing-library";

assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");

Puedes mejorar aún más esta prueba si defines pruebas independientes, que se agrupan de forma opcional en paquetes. El siguiente paquete prueba de forma independiente la función de Fibonacci y la función Catalán:

import { fibonacci, catalan } from "../src/math.js";
import { assert, test, suite } from "a-made-up-testing-library";

suite("math tests", () => {
  test("fibonacci function", () => {
    assert.equal(fibonacci(0), 0, "Invalid 0th fibonacci result");
    assert.equal(fibonacci(13), 233, "Invalid 13th fibonacci result");
  });
  test("relationship between sequences", () => {
    const numberToCheck = 4;
    const fib = fibonacci(numberToCheck);
    const cat = catalan(numberToCheck);
    assert.isAbove(fib, cat);
  });
});

En este contexto de pruebas de software, prueba como sustantivo se refiere a un caso de prueba: una situación única, independiente y abordable, como el caso de prueba de "relación entre secuencias" en el ejemplo anterior.

Las pruebas con nombres individuales son útiles para las siguientes tareas, entre otras:

  • Determinar cómo una prueba tiene éxito o falla con el tiempo
  • Destaca un error o una situación por su nombre para que puedas probar más fácilmente que se haya resuelto.
  • Ejecución de algunas pruebas de forma independiente de otras, por ejemplo, a través de un filtro glob.

Una forma de analizar los casos de prueba es usar las "tres A" de la prueba de unidades: organizar, actuar y confirmar. En esencia, cada caso de prueba hará lo siguiente:

  • Organiza algunos valores o estados (podrían ser datos de entrada hard-coded).
  • Realizar una acción, como llamar a un método
  • Confirma los valores de salida o el estado actualizado (con assert).

La escala de las pruebas

En las muestras de código de la sección anterior, se describe una prueba de unidades, ya que prueban partes menores del software (a menudo, se enfocan en un solo archivo y, en este caso, solo el resultado de una sola función). La complejidad de las pruebas aumenta a medida que consideras el código de varios archivos, componentes o incluso diferentes sistemas interconectados (a veces fuera de tu control, como un servicio de red o el comportamiento de una dependencia externa). Debido a esto, los tipos de prueba a menudo se nombran según su alcance o escala.

Además de las pruebas de unidades, algunos ejemplos de otros tipos de pruebas incluyen las pruebas de componentes, las pruebas visuales y las pruebas de integración. Ninguno de estos nombres tiene definiciones rigurosas y puede tener significados diferentes según la base de código, así que recuerda usarlos como guía y crea definiciones que te sirvan. Por ejemplo, ¿qué es un componente que se está probando en tu sistema? Para los desarrolladores de React, esto puede asignarse literalmente a un "componente de React", pero podría tener un significado diferente para los desarrolladores en otros contextos.

La escala de una prueba individual puede colocarla dentro de un concepto que a menudo se conoce como "pirámide de prueba", que puede ser una buena regla general sobre lo que una prueba verifica y cómo se ejecuta.

Pirámide de pruebas,
 con pruebas de extremo a extremo (E2E) en la parte superior, pruebas de integración en el medio y
 pruebas de unidades en la parte inferior.
La pirámide de prueba

Se iteró esta idea y se popularizaron otras formas, como el diamante de prueba o el cono de hielo de prueba. Tus prioridades de escritura de pruebas probablemente sean exclusivas de tu base de código. Sin embargo, una función común es que las pruebas más simples, como las pruebas de unidades, suelen ser más rápidas de ejecutarse, más fáciles de escribir (para que tengas más) y probar con un alcance limitado, mientras que las pruebas complejas, como las pruebas de extremo a extremo, son difíciles de escribir, pero pueden probar un alcance más amplio. De hecho, la capa superior de muchas "formas" de prueba suele ser una prueba manual, porque alguna interacción del usuario es demasiado compleja para codificarla en una prueba automatizada.

Estos tipos se ampliarán en tipos de pruebas automatizadas.

Verifica tus conocimientos

¿Qué primitivas proporcionan la mayoría de las bibliotecas y los frameworks de prueba?

Un servicio de ejecutor que usa un proveedor de servicios en la nube.
Algunos ejecutores basados en el navegador ofrecen una forma de externalizar tus pruebas, pero no es una función normal de las bibliotecas de prueba.
Aserciones que causan excepciones si no se cumplen.
Si bien puedes arrojar un error para que una prueba falle, se suelen incluir assert() y sus variaciones, ya que facilitan la escritura de las verificaciones.
Una forma de categorizar las pruebas en la pirámide de pruebas.
No existe una forma estándar de hacerlo. Puedes agregar prefijos a los nombres de tus pruebas o colocarlos en archivos diferentes, pero la categorización no está realmente integrada en la mayoría de los frameworks de pruebas.
Es la capacidad de definir pruebas independientes por función.
El método test() se incluye en casi todos los ejecutores de pruebas. Es importante porque el código de prueba no se ejecuta en el nivel superior de un archivo, lo que permite que el ejecutor de pruebas trate cada caso de prueba como una unidad independiente.