Buscar..


Introducción

Las pruebas unitarias son una parte integral del desarrollo impulsado por pruebas, y una característica importante para construir cualquier aplicación robusta. En Java, las pruebas unitarias se realizan casi exclusivamente utilizando bibliotecas y marcos externos, la mayoría de los cuales tienen su propia etiqueta de documentación. Este talón sirve como medio para presentar al lector las herramientas disponibles y su documentación respectiva.

Observaciones

Marcos de prueba unitaria

Hay numerosos marcos disponibles para pruebas de unidad dentro de Java. La opción más popular por el momento es JUnit. Está documentado bajo lo siguiente:

JUIT

JUnit4 - Etiqueta propuesta para las características de JUnit4; aún no implementado .

Existen otros marcos de prueba de unidad, y tienen documentación disponible:

Prueba

Herramientas de prueba unitaria

Hay varias otras herramientas utilizadas para la prueba de la unidad:

Mockito - marco burlón ; Permite que los objetos sean imitados. Útil para imitar el comportamiento esperado de una unidad externa dentro de la prueba de una unidad dada, para no vincular el comportamiento de la unidad externa a las pruebas de la unidad dada.

JBehave - BDD Framework. Permite vincular las pruebas a los comportamientos de los usuarios (lo que permite la validación de requisitos / escenarios) No hay etiquetas de documentos disponibles en el momento de la escritura; Aquí hay un enlace externo .

¿Qué es la prueba unitaria?

Esto es un poco de una cartilla. En su mayoría se incluye porque la documentación se ve obligada a tener un ejemplo, incluso si está pensado como un artículo de código auxiliar. Si ya conoce los conceptos básicos de la prueba de unidad, siéntase libre de saltar a los comentarios, donde se mencionan los marcos específicos.

La prueba de unidad es asegurar que un módulo dado se comporte como se espera. En aplicaciones a gran escala, garantizar la ejecución adecuada de los módulos en el vacío es una parte integral para garantizar la fidelidad de la aplicación.

Considere el siguiente pseudo-ejemplo (trivial):

public class Example {
  public static void main (String args[]) {
    new Example();
  }

  // Application-level test.
  public Example() {
    Consumer c = new Consumer();
    System.out.println("VALUE = " + c.getVal());
  }

  // Your Module.
  class Consumer {
    private Capitalizer c;
  
    public Consumer() {
      c = new Capitalizer();
    }

    public String getVal() {
      return c.getVal();
    }
  }

  // Another team's module.
  class Capitalizer {
    private DataReader dr;
  
    public Capitalizer() {
      dr = new DataReader();
    }

    public String getVal() {
      return dr.readVal().toUpperCase();
    }
  }

  // Another team's module.
  class DataReader {
    public String readVal() {
      // Refers to a file somewhere in your application deployment, or
      // perhaps retrieved over a deployment-specific network.
      File f; 
      String s = "data";
      // ... Read data from f into s ...
      return s;
    }
  }
}

Entonces este ejemplo es trivial; DataReader obtiene los datos de un archivo, los pasa al Capitalizer , que convierte todos los caracteres a mayúsculas, que luego pasa al Consumer . Pero el DataReader está fuertemente vinculado a nuestro entorno de aplicación, por lo que aplazamos las pruebas de esta cadena hasta que estemos listos para implementar una versión de prueba.

Ahora, supongamos que en algún momento del lanzamiento de una versión, por razones desconocidas, el método getVal() en Capitalizer cambió de devolver una cadena toUpperCase() a una toLowerCase() :

  // Another team's module.
  class Capitalizer {
    ...

    public String getVal() {
      return dr.readVal().toLowerCase();
    }
  }

Claramente, esto rompe el comportamiento esperado. Pero, debido a los arduos procesos involucrados en la ejecución del DataReader , no lo notaremos hasta nuestra próxima implementación de prueba. Entonces, los días / semanas / meses pasan con este error en nuestro sistema, y ​​luego el gerente de producto ve esto y se dirige instantáneamente a usted, el líder del equipo asociado con el Consumer . "¿Por qué está sucediendo esto? ¿Qué cambiaron?" Obviamente, no tienes ni idea. No tienes idea de lo que está pasando. No cambiaste ningún código que debería estar tocando esto; ¿Por qué se rompe de repente?

Finalmente, después de la discusión entre los equipos y la colaboración, el problema se rastrea y el problema se resuelve. Pero, plantea la pregunta; ¿Cómo pudo haberse evitado esto?

Hay dos cosas obvias:

Las pruebas deben ser automatizadas

Nuestra confianza en las pruebas manuales hace que este error pase inadvertido durante demasiado tiempo. Necesitamos una forma de automatizar el proceso bajo el cual los errores se introducen instantáneamente . No dentro de 5 semanas. No dentro de 5 días No dentro de 5 minutos. Ahora mismo.

Hay que apreciar que, en este ejemplo, he expresado un error muy trivial que se introdujo y pasó desapercibido. En una aplicación industrial, con docenas de módulos que se actualizan constantemente, estos pueden arrastrarse por todas partes. Arregla algo con un módulo, solo para darte cuenta de que el mismo comportamiento que "arreglaste" se confió de alguna manera en otra parte (ya sea interna o externamente).

Sin una validación rigurosa, las cosas entrarán en el sistema. Es posible que, si se descuida lo suficiente, esto resulte en mucho trabajo adicional al tratar de arreglar los cambios (y luego corregir esos arreglos, etc.), que un producto aumente en el trabajo restante a medida que se pone esfuerzo en ello. No quieres estar en esta situación.

Las pruebas deben ser de grano fino

El segundo problema observado en nuestro ejemplo anterior es la cantidad de tiempo que llevó rastrear el error. El gerente de producto le hizo un ping cuando los evaluadores lo notaron, usted investigó y encontró que el Capitalizer estaba devolviendo datos aparentemente malos, hizo un ping al equipo de Capitalizer con sus hallazgos, investigaron, etc., etc., etc.

El mismo punto que mencioné anteriormente sobre la cantidad y la dificultad de este ejemplo trivial se sostiene aquí. Obviamente, cualquier persona razonablemente versada en Java podría encontrar el problema introducido rápidamente. Pero a menudo es mucho, mucho más difícil rastrear y comunicar problemas. Tal vez el equipo de Capitalizer proporcionó un JAR sin ninguna fuente. Tal vez estén ubicados en el otro lado del mundo, y las horas de comunicación son muy limitadas (tal vez a correos electrónicos que se envían una vez al día). Puede dar lugar a errores que tardan semanas o más en rastrearse (y, nuevamente, podría haber varios de estos para una versión determinada).

Para mitigar esto, queremos que las pruebas rigurosas se realicen en el nivel más preciso posible (también desea que las pruebas generales aseguren que los módulos interactúen correctamente, pero ese no es nuestro punto focal aquí). Queremos especificar rigurosamente cómo funciona toda la funcionalidad orientada hacia el exterior (como mínimo), y probar esa funcionalidad.

Entrar en la prueba de la unidad

Imagínese si tuviéramos una prueba, asegurando específicamente que el método getVal() de Capitalizer devolvió una cadena en mayúscula para una cadena de entrada determinada. Además, imagine que la prueba se ejecutó incluso antes de que cometiéramos ningún código. El error introducido en el sistema (es decir, toUpperCase() se reemplaza con toLowerCase() ) no causaría problemas porque el error nunca se introduciría en el sistema. Lo atraparíamos en una prueba, el desarrollador (con suerte) se daría cuenta de su error, y se alcanzaría una solución alternativa en cuanto a cómo introducir el efecto deseado.

Aquí se hacen algunas omisiones en cuanto a cómo implementar estas pruebas, pero están cubiertas en la documentación específica del marco (enlazada en los comentarios). Con suerte, esto sirve como un ejemplo de por qué las pruebas unitarias son importantes.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow