De Wikipedia, la enciclopedia libre
Saltar a navegación Saltar a búsqueda

En programación de computadoras , las pruebas unitarias son un método de prueba de software mediante el cual se prueban unidades individuales de código fuente (conjuntos de uno o más módulos de programas de computadora junto con datos de control asociados, procedimientos de uso y procedimientos operativos) para determinar si son aptos para su uso . [1]

Descripción [ editar ]

Las pruebas unitarias son típicamente pruebas automatizadas escritas y ejecutadas por desarrolladores de software para garantizar que una sección de una aplicación (conocida como la "unidad") cumpla con su diseño y se comporte según lo previsto. [2] En la programación de procedimientos , una unidad podría ser un módulo completo, pero más comúnmente es una función o procedimiento individual. En la programación orientada a objetos , una unidad suele ser una interfaz completa, como una clase o un método individual. [3] Al escribir pruebas primero para las unidades comprobables más pequeñas, luego los comportamientos compuestos entre ellas, uno puede construir pruebas integrales para aplicaciones complejas. [2]

Para aislar los problemas que puedan surgir, cada caso de prueba debe probarse de forma independiente. Se pueden usar sustitutos tales como resguardos de métodos , objetos simulados , [4] falsificaciones y arneses de prueba para ayudar a probar un módulo de forma aislada.

Durante el desarrollo, un desarrollador de software puede codificar criterios, o resultados que se sabe que son buenos, en la prueba para verificar la exactitud de la unidad. Durante la ejecución del caso de prueba, los marcos registran las pruebas que fallan en cualquier criterio y las informan en un resumen. Para esto, el enfoque más comúnmente utilizado es prueba - función - valor esperado.

La escritura y el mantenimiento de pruebas unitarias se pueden hacer más rápido utilizando pruebas parametrizadas . Estos permiten la ejecución de una prueba varias veces con diferentes conjuntos de entrada, lo que reduce la duplicación de códigos de prueba. A diferencia de las pruebas unitarias tradicionales, que generalmente son métodos cerrados y prueban condiciones invariantes, las pruebas parametrizadas toman cualquier conjunto de parámetros. Las pruebas parametrizadas son compatibles con TestNG , JUnit y su contraparte .Net, XUnit. Los parámetros adecuados para las pruebas unitarias se pueden suministrar manualmente o, en algunos casos, el marco de pruebas los genera automáticamente. En los últimos años se agregó soporte para escribir pruebas (unitarias) más potentes, aprovechando el concepto de teorías, casos de prueba que ejecutan los mismos pasos, pero usando datos de prueba generados en tiempo de ejecución, a diferencia de las pruebas parametrizadas regulares que usan los mismos pasos de ejecución con conjuntos de entrada que están predefinidos. [5] [6] [7]

Ventajas [ editar ]

El objetivo de las pruebas unitarias es aislar cada parte del programa y mostrar que las partes individuales son correctas. [1] Una prueba unitaria proporciona un contrato escrito estricto que el fragmento de código debe cumplir. Como resultado, ofrece varios beneficios.

Las pruebas unitarias encuentran problemas al principio del ciclo de desarrollo . Esto incluye tanto errores en la implementación del programador como fallas o partes faltantes de la especificación de la unidad. El proceso de escribir un conjunto completo de pruebas obliga al autor a pensar en las entradas, salidas y condiciones de error y, por lo tanto, definir con mayor precisión el comportamiento deseado de la unidad. El costo de encontrar un error antes de que comience la codificación o cuando el código se escribe por primera vez es considerablemente menor que el costo de detectar, identificar y corregir el error más tarde. Los errores en el código publicado también pueden causar problemas costosos para los usuarios finales del software. [8] [9] [10]El código puede ser imposible o difícil de probar unitariamente si está mal escrito, por lo que las pruebas unitarias pueden obligar a los desarrolladores a estructurar funciones y objetos de mejores maneras.

En el desarrollo impulsado por pruebas (TDD), que se usa con frecuencia tanto en programación extrema como en scrum , las pruebas unitarias se crean antes de que se escriba el código en sí. Cuando pasan las pruebas, ese código se considera completo. Las mismas pruebas unitarias se ejecutan contra esa función con frecuencia a medida que se desarrolla la base de código más grande, ya sea a medida que se cambia el código o mediante un proceso automatizado con la compilación. Si las pruebas unitarias fallan, se considera un error en el código modificado o en las pruebas mismas. Luego, las pruebas unitarias permiten rastrear fácilmente la ubicación de la falla o falla. Dado que las pruebas unitarias alertan al equipo de desarrollo del problema antes de entregar el código a los probadores o clientes, los problemas potenciales se detectan al principio del proceso de desarrollo.

Las pruebas unitarias permiten al programador refactorizar el código o actualizar las bibliotecas del sistema en una fecha posterior y asegurarse de que el módulo aún funciona correctamente (por ejemplo, en las pruebas de regresión ). El procedimiento consiste en escribir casos de prueba para todas las funciones y métodos, de modo que siempre que un cambio cause una falla, se pueda identificar rápidamente. Las pruebas unitarias detectan cambios que pueden romper un contrato de diseño .

Las pruebas unitarias pueden reducir la incertidumbre en las unidades mismas y pueden usarse en un enfoque de estilo de prueba ascendente . Al probar las partes de un programa primero y luego probar la suma de sus partes, las pruebas de integración se vuelven mucho más fáciles. [ cita requerida ]

Las pruebas unitarias proporcionan una especie de documentación viva del sistema. Los desarrolladores que deseen saber qué funcionalidad proporciona una unidad y cómo usarla, pueden consultar las pruebas de la unidad para obtener una comprensión básica de la interfaz de la unidad ( API ). [ cita requerida ]

Los casos de prueba unitaria incorporan características que son críticas para el éxito de la unidad. Estas características pueden indicar un uso apropiado / inapropiado de una unidad, así como comportamientos negativos que la unidad debe atrapar. Un caso de prueba unitario, en sí mismo, documenta estas características críticas, aunque muchos entornos de desarrollo de software no dependen únicamente del código para documentar el producto en desarrollo. [ cita requerida ]

Cuando el software se desarrolla utilizando un enfoque basado en pruebas, la combinación de escribir la prueba unitaria para especificar la interfaz más las actividades de refactorización realizadas después de que la prueba ha pasado, puede reemplazar el diseño formal. Cada prueba unitaria puede verse como un elemento de diseño que especifica clases, métodos y comportamiento observable. [ cita requerida ]

Limitaciones y desventajas [ editar ]

Las pruebas no detectarán todos los errores del programa, ya que no pueden evaluar todas las rutas de ejecución en los programas más triviales. Este problema es un superconjunto del problema de la detención , que es indecidible . Lo mismo ocurre con las pruebas unitarias. Además, las pruebas unitarias, por definición, solo prueban la funcionalidad de las propias unidades. Por lo tanto, no detectará errores de integración ni errores más amplios a nivel del sistema (como funciones realizadas en varias unidades o áreas de prueba no funcionales como el rendimiento ). Las pruebas unitarias deben realizarse junto con otras pruebas de software.actividades, ya que solo pueden mostrar la presencia o ausencia de errores particulares; no pueden probar una ausencia total de errores. Para garantizar un comportamiento correcto para cada ruta de ejecución y cada entrada posible, y asegurar la ausencia de errores, se requieren otras técnicas, a saber, la aplicación de métodos formales para demostrar que un componente de software no tiene un comportamiento inesperado. [ cita requerida ]

Una jerarquía elaborada de pruebas unitarias no equivale a pruebas de integración. La integración con unidades periféricas debe incluirse en las pruebas de integración, pero no en las pruebas unitarias. [ cita requerida ] Las pruebas de integración generalmente todavía dependen en gran medida de las pruebas en humanos manualmente ; Las pruebas de alto nivel o de alcance global pueden ser difíciles de automatizar, por lo que las pruebas manuales a menudo parecen más rápidas y económicas. [ cita requerida ]

La prueba de software es un problema combinatorio. Por ejemplo, cada declaración de decisión booleana requiere al menos dos pruebas: una con un resultado de "verdadero" y otra con un resultado de "falso". Como resultado, por cada línea de código escrito, los programadores a menudo necesitan de 3 a 5 líneas de código de prueba. [11] Obviamente, esto lleva tiempo y es posible que su inversión no valga la pena. Hay problemas que no se pueden probar fácilmente en absoluto, por ejemplo, aquellos que no son deterministas o involucran múltiples subprocesos . Además, es probable que el código para una prueba unitaria tenga al menos tantos errores como el código que está probando. Fred Brooks en The Mythical Man-Month cita: "Nunca vayas al mar con dos cronómetros; toma uno o tres". [12]Es decir, si dos cronómetros se contradicen, ¿cómo saber cuál es el correcto?

Otro desafío relacionado con la redacción de las pruebas unitarias es la dificultad de configurar pruebas realistas y útiles. Es necesario crear condiciones iniciales relevantes para que la parte de la aplicación que se está probando se comporte como parte del sistema completo. Si estas condiciones iniciales no se establecen correctamente, la prueba no ejercitará el código en un contexto realista, lo que disminuye el valor y la precisión de los resultados de la prueba unitaria. [13]

Para obtener los beneficios esperados de las pruebas unitarias, se necesita una disciplina rigurosa durante todo el proceso de desarrollo de software. Es esencial mantener registros cuidadosos no solo de las pruebas que se han realizado, sino también de todos los cambios que se han realizado en el código fuente de esta o cualquier otra unidad del software. El uso de un sistema de control de versiones es esencial. Si una versión posterior de la unidad falla una prueba en particular que había pasado previamente, el software de control de versiones puede proporcionar una lista de los cambios en el código fuente (si los hay) que se han aplicado a la unidad desde ese momento. [ cita requerida ]

También es esencial implementar un proceso sostenible para garantizar que las fallas de los casos de prueba se revisen con regularidad y se aborden de inmediato. [14] Si un proceso de este tipo no se implementa y está arraigado en el flujo de trabajo del equipo, la aplicación evolucionará fuera de sincronización con el conjunto de pruebas unitarias, aumentando los falsos positivos y reduciendo la eficacia del conjunto de pruebas.

La prueba unitaria del software del sistema integrado presenta un desafío único: debido a que el software se está desarrollando en una plataforma diferente a la que eventualmente se ejecutará, no se puede ejecutar fácilmente un programa de prueba en el entorno de implementación real, como es posible con los programas de escritorio. [15]

Las pruebas unitarias tienden a ser más fáciles cuando un método tiene parámetros de entrada y alguna salida. No es tan fácil crear pruebas unitarias cuando una función principal del método es interactuar con algo externo a la aplicación. Por ejemplo, un método que funcionará con una base de datos puede requerir la creación de una maqueta de las interacciones de la base de datos, que probablemente no será tan completa como las interacciones reales de la base de datos. [16] [se necesita una mejor fuente ]

Ejemplo [ editar ]

A continuación, se muestra un conjunto de casos de prueba en Java que especifican una serie de elementos de la implementación. Primero, que debe haber una interfaz llamada Adder y una clase de implementación con un constructor de argumento cero llamado AdderImpl. Continúa afirmando que la interfaz Adder debería tener un método llamado add, con dos parámetros enteros, que devuelve otro entero. También especifica el comportamiento de este método para un pequeño rango de valores en varios métodos de prueba.

importar  org.junit.Assert.assertEquals estático ;import  org.junit.Test ;público  de clase  TestAdder  { @Test  public  void  testSumPositiveNumbersOneAndOne ()  {  Adder  sumador  =  new  AdderImpl ();  asertEquals ( 2 ,  sumador . agregar ( 1 ,  1 ));  } // ¿Puede sumar los números positivos 1 y 2?  @Test  public  void  testSumPositiveNumbersOneAndTwo ()  {  Adder  sumador  =  new  AdderImpl ();  asertEquals ( 3 ,  sumador . agregar ( 1 ,  2 ));  } // ¿Puede sumar los números positivos 2 y 2?  @Test  public  void  testSumPositiveNumbersTwoAndTwo ()  {  Adder  sumador  =  new  AdderImpl ();  asertEquals ( 4 ,  sumador . agregar ( 2 ,  2 ));  } // es cero neutral?  @Test  public  void  testSumZeroNeutral ()  {  Sumador  sumador  =  nuevo  AdderImpl ();  asertEquals ( 0 ,  sumador . agregar ( 0 ,  0 ));  } // ¿Puede sumar los números negativos -1 y -2?  @Test  public  void  testSumNegativeNumbers ()  {  Sumador  sumador  =  new  AdderImpl ();  asertEquals ( - 3 ,  sumador . agregar ( - 1 ,  - 2 ));  } // ¿Puede agregar un positivo y un negativo?  @Test  public  void  testSumPositiveAndNegative ()  {  Sumador  sumador  =  nuevo  AdderImpl ();  asertEquals ( 0 ,  sumador . agregar ( - 1 ,  1 ));  } // ¿Qué tal números más grandes?  @Test  public  void  testSumLargeNumbers ()  {  Sumador  sumador  =  nuevo  AdderImpl ();  asertEquals ( 2222 ,  sumador . agregar ( 1234 ,  988 ));  } }

En este caso, las pruebas unitarias, habiendo sido escritas primero, actúan como un documento de diseño que especifica la forma y el comportamiento de una solución deseada, pero no los detalles de implementación, que quedan para el programador. Siguiendo la práctica de "hacer lo más simple que pueda funcionar", a continuación se muestra la solución más sencilla que hará que la prueba pase.

interfaz  Sumador  {  int  agregar ( int  a ,  int  b ); } class  AdderImpl  implementa  Adder  {  public  int  add ( int  a ,  int  b )  {  return  a  +  b ;  } }

Como especificaciones ejecutables [ editar ]

El uso de pruebas unitarias como especificación de diseño tiene una ventaja significativa sobre otros métodos de diseño: el documento de diseño (las pruebas unitarias en sí mismas) se puede utilizar para verificar la implementación. Las pruebas nunca pasarán a menos que el desarrollador implemente una solución de acuerdo con el diseño.

Las pruebas unitarias carecen de la accesibilidad de una especificación de diagrama como un diagrama UML , pero pueden generarse a partir de la prueba unitaria utilizando herramientas automatizadas. La mayoría de los lenguajes modernos tienen herramientas gratuitas (generalmente disponibles como extensiones para IDE ). Las herramientas gratuitas, como las basadas en el marco xUnit , subcontratan a otro sistema la representación gráfica de una vista para consumo humano.

Aplicaciones [ editar ]

Programación extrema [ editar ]

Las pruebas unitarias son la piedra angular de la programación extrema , que se basa en un marco de pruebas unitarias automatizado . Este marco de prueba unitario automatizado puede ser de un tercero, por ejemplo, xUnit , o creado dentro del grupo de desarrollo.

La programación extrema utiliza la creación de pruebas unitarias para el desarrollo basado en pruebas . El desarrollador escribe una prueba unitaria que expone un requisito de software o un defecto. Esta prueba fallará porque el requisito aún no se ha implementado o porque expone intencionalmente un defecto en el código existente. Luego, el desarrollador escribe el código más simple para que la prueba, junto con otras pruebas, pase.

La mayor parte del código de un sistema se prueba unitariamente, pero no necesariamente todas las rutas a través del código. La programación extrema exige una estrategia de "probar todo lo que pueda romper", sobre el método tradicional de "probar cada ruta de ejecución". Esto lleva a los desarrolladores a desarrollar menos pruebas que los métodos clásicos, pero esto no es realmente un problema, más una reafirmación de un hecho, ya que los métodos clásicos rara vez se han seguido lo suficientemente metódicamente como para que todas las rutas de ejecución se hayan probado a fondo. [ cita requerida ] La programación extrema simplemente reconoce que las pruebas rara vez son exhaustivas (porque a menudo son demasiado costosas y requieren mucho tiempo para ser económicamente viables) y brinda orientación sobre cómo enfocar de manera efectiva los recursos limitados.

Fundamentalmente, el código de prueba se considera un artefacto de proyecto de primera clase en el sentido de que se mantiene con la misma calidad que el código de implementación, con toda duplicación eliminada. Los desarrolladores lanzan el código de prueba de la unidad al repositorio de código junto con el código que prueba. Las pruebas unitarias exhaustivas de la programación extrema permiten los beneficios mencionados anteriormente, como un desarrollo y refactorización de código más simple y seguro , integración de código simplificada, documentación precisa y diseños más modulares. Estas pruebas unitarias también se ejecutan constantemente como una forma de prueba de regresión .

Las pruebas unitarias también son fundamentales para el concepto de diseño emergente . Dado que el diseño emergente depende en gran medida de la refactorización, las pruebas unitarias son un componente integral. [17]

Marcos de pruebas unitarias [ editar ]

Los marcos de prueba unitarios son, con mayor frecuencia, productos de terceros que no se distribuyen como parte del conjunto de compiladores. Ayudan a simplificar el proceso de prueba unitaria, ya que se han desarrollado para una amplia variedad de idiomas .

Por lo general, es posible realizar pruebas unitarias sin el soporte de un marco específico escribiendo código de cliente que ejercita las unidades bajo prueba y utiliza aserciones , manejo de excepciones u otros mecanismos de flujo de control para señalar fallas. Las pruebas unitarias sin un marco son valiosas porque existe una barrera de entrada para la adopción de pruebas unitarias; tener pocas pruebas unitarias es apenas mejor que no tener ninguna, mientras que una vez que se implementa un marco, agregar pruebas unitarias se vuelve relativamente fácil. [18] En algunos marcos, faltan muchas funciones avanzadas de pruebas unitarias o deben codificarse manualmente.

Compatibilidad con pruebas unitarias a nivel de idioma [ editar ]

Algunos lenguajes de programación admiten directamente las pruebas unitarias. Su gramática permite la declaración directa de pruebas unitarias sin importar una biblioteca (ya sea de terceros o estándar). Además, las condiciones booleanas de las pruebas unitarias se pueden expresar en la misma sintaxis que las expresiones booleanas utilizadas en el código de prueba no unitaria, como lo que se utiliza para las declaraciones ify while.

Los idiomas con soporte de prueba unitario incorporado incluyen:

  • Apéndice
  • Cobra
  • Cristal [19]
  • D [20]
  • Ir [21]
  • LabVIEW
  • MATLAB
  • Python [22]
  • Raqueta [23] [24]
  • Rubí [25]
  • Óxido [26]

Algunos lenguajes sin soporte de prueba de unidad incorporado tienen bibliotecas / marcos de prueba de unidad muy buenos. Esos idiomas incluyen:

  • ABAP
  • C ++
  • C#
  • Clojure [27]
  • Elixir
  • Java
  • JavaScript
  • C objetivo
  • Perl
  • PHP
  • PowerShell [28]
  • R con prueba que
  • Scala
  • tcl
  • Visual Basic .NET
  • Xojo con XojoUnit

Ver también [ editar ]

  • Test de aceptación
  • Prueba de caracterización
  • Prueba de usabilidad basada en componentes
  • Predicados de diseño
  • Diseño por contrato
  • Programación extrema
  • Pruebas funcionales
  • Pruebas de integración
  • Lista de marcos de pruebas unitarias
  • Pruebas de regresión
  • Arqueología de software
  • Pruebas de software
  • Caso de prueba
  • Desarrollo impulsado por pruebas
  • xUnit : una familia de marcos de pruebas unitarias.

Referencias [ editar ]

  1. ^ a b Kolawa, Adam; Huizinga, Dorota (2007). Prevención automatizada de defectos: mejores prácticas en la gestión de software . Prensa de la Sociedad de Computación Wiley-IEEE. pag. 75. ISBN 978-0-470-04212-0.
  2. ↑ a b Hamill, Paul (2004). Marcos de prueba unitarios: herramientas para el desarrollo de software de alta calidad . O'Reilly Media, Inc. ISBN 9780596552817.
  3. ^ Xie, Tao . "Hacia un marco para pruebas unitarias diferenciales de programas orientados a objetos" (PDF) . Consultado el 23 de julio de 2012 .
  4. ^ Fowler, Martin (2 de enero de 2007). "Los simulacros no son talones" . Consultado el 1 de abril de 2008 .
  5. ^ "Introducción a xUnit.net (escritorio)" .
  6. ^ "Teorías" .
  7. ^ "Pruebas parametrizadas" .
  8. ^ Boehm, Barry W .; Papaccio, Philip N. (octubre de 1988). "Comprensión y control de los costos del software" (PDF) . Transacciones IEEE sobre ingeniería de software . 14 (10): 1462–1477. doi : 10.1109 / 32.6191 . Consultado el 13 de mayo de 2016 .
  9. ^ "Prueba temprano y con frecuencia" . Microsoft.
  10. ^ "Demuestre que funciona: utilizando el marco de pruebas unitarias para validación y pruebas de software" . Instrumentos Nacionales . 21 de agosto de 2017.
  11. ^ Cramblitt, Bob (20 de septiembre de 2007). Alberto Savoia alaba las pruebas de software ” . Consultado el 29 de noviembre de 2007 .
  12. ^ Brooks, Frederick J. (1995) [1975]. El mes mítico del hombre . Addison-Wesley. pag. 64 . ISBN 978-0-201-83595-3.
  13. ^ Kolawa, Adam (1 de julio de 2009). "Mejores prácticas de pruebas unitarias" . Consultado el 23 de julio de 2012 .
  14. daVeiga, Nada (6 de febrero de 2008). "Cambiar código sin miedo: utilizar una red de seguridad de regresión" . Consultado el 8 de febrero de 2008 .
  15. ^ Kucharski, Marek (23 de noviembre de 2011). "Hacer que las pruebas unitarias sean prácticas para el desarrollo integrado" . Consultado el 20 de julio de 2020 .
  16. ^ http://wiki.c2.com/?UnitTestsAndDatabases
  17. ^ "Diseño emergente ágil" . Sherpa ágil. 3 de agosto de 2010. Archivado desde el original el 22 de marzo de 2012 . Consultado el 8 de mayo de 2012 .
  18. ^ Tecnología de prueba de Bullseye (2006-2008). "Metas de cobertura intermedia" . Consultado el 24 de marzo de 2009 .
  19. ^ "Especificaciones de cristal" . crystal-lang.org . Consultado el 18 de septiembre de 2017 .
  20. ^ "Pruebas unitarias - Lenguaje de programación D" . D Lenguaje de programación . Fundación Idioma D . Consultado el 5 de agosto de 2017 .
  21. ^ "prueba - El lenguaje de programación Go" . golang.org . Consultado el 3 de diciembre de 2013 .
  22. ^ Documentación de Python (2016). "unittest - marco de prueba unitario" . Consultado el 18 de abril de 2016 .
  23. ^ Galés, Noel; Culpepper, Ryan. "RackUnit: Prueba unitaria" . PLT Design Inc . Consultado el 26 de febrero de 2019 .
  24. ^ Galés, Noel; Culpepper, Ryan. "El paquete RackUnit Unit Testing forma parte de la distribución principal de Racket" . PLT Design Inc . Consultado el 26 de febrero de 2019 .
  25. ^ "Minitest (Ruby 2.0)" . Ruby-Doc.org.
  26. ^ Los desarrolladores del proyecto Rust (2011-2014). "La guía de prueba de óxido (óxido 0.12.0-antes de la noche)" . Consultado el 12 de agosto de 2014 .
  27. ^ Sierra, Stuart. "API para clojure.test - Clojure v1.6 (estable)" . Consultado el 11 de febrero de 2015 .
  28. ^ "Marco de Pester" . Consultado el 28 de enero de 2016 .

Enlaces externos [ editar ]

  • Desarrollo basado en pruebas (Wiki de Ward Cunningham)