Creo que esta es una idea falsa de cualquier manera que se me ocurra.
El código de prueba que prueba el código de producción no es en absoluto similar. Lo demostraré en python:
def multiply(a, b):
"""Multiply ``a`` by ``b``"""
return a*b
Entonces una prueba simple sería:
def test_multiply():
assert multiply(4, 5) == 20
Ambas funciones tienen una definición similar, pero ambas hacen cosas muy diferentes. No hay código duplicado aquí. ;-)
También ocurre que las personas escriben pruebas duplicadas que esencialmente tienen una afirmación por función de prueba. Esto es una locura y he visto gente haciendo esto. Esta es una mala práctica.
def test_multiply_1_and_3():
"""Assert that a multiplication of 1 and 3 is 3."""
assert multiply(1, 3) == 3
def test_multiply_1_and_7():
"""Assert that a multiplication of 1 and 7 is 7."""
assert multiply(1, 7) == 7
def test_multiply_3_and_4():
"""Assert that a multiplication of 3 and 4 is 12."""
assert multiply(3, 4) == 12
Imagine hacer esto para más de 1000 líneas de código efectivas. En su lugar, realiza una prueba por "característica":
def test_multiply_positive():
"""Assert that positive numbers can be multiplied."""
assert multiply(1, 3) == 3
assert multiply(1, 7) == 7
assert multiply(3, 4) == 12
def test_multiply_negative():
"""Assert that negative numbers can be multiplied."""
assert multiply(1, -3) == -3
assert multiply(-1, -7) == 7
assert multiply(-3, 4) == -12
Ahora, cuando se agregan / eliminan funciones, solo tengo que considerar agregar / eliminar una función de prueba.
Es posible que haya notado que no he aplicado for
bucles. Esto se debe a que repetir algunas cosas es bueno. Cuando hubiera aplicado bucles, el código sería mucho más corto. Pero cuando falla una aserción, podría ofuscar la salida que muestra un mensaje ambiguo. Si esto ocurre entonces sus pruebas serán menos útiles y que se necesita un depurador para inspeccionar donde las cosas van mal.
assert multiply(1,3)
fallaría, pero tampoco obtendría el informe de prueba fallidoassert multiply(3,4)
.def test_shuffle
realiza dos afirmaciones.assert multiply(*, *) == *
por lo que podría definir unaassert_multiply
función. En el escenario actual, no importa el recuento de filas y la legibilidad, pero con pruebas más largas puede reutilizar aserciones complicadas, accesorios, código generador de accesorios, etc. No sé si esta es una práctica recomendada, pero generalmente lo hago. esta.No, esto no es verdad.
Las pruebas tienen un propósito diferente al de su implementación:
fuente
No. DRY se trata de escribir código solo una vez para realizar una tarea en particular, las pruebas son validación de que la tarea se está realizando correctamente. Es algo parecido a un algoritmo de votación, donde obviamente usar el mismo código sería inútil.
fuente
No, el objetivo final de DRY en realidad significaría la eliminación de todo el código de producción .
Si nuestras pruebas pudieran ser especificaciones perfectas de lo que queremos que haga el sistema, solo tendríamos que generar el código de producción correspondiente (o binarios) automáticamente, eliminando efectivamente la base del código de producción per se.
Esto es en realidad lo que los enfoques como la arquitectura basada en modelos afirman lograr: una única fuente de verdad diseñada por humanos de la cual todo se deriva por computación.
No creo que lo contrario (deshacerse de todas las pruebas) sea deseable porque:
fuente
Porque a veces repetirlo está bien. Ninguno de estos principios está destinado a ser tomado en todas las circunstancias sin preguntas o contexto. A veces he escrito pruebas contra una versión ingenua (y lenta) de un algoritmo, que es una violación bastante clara de DRY, pero definitivamente beneficiosa.
fuente
Dado que las pruebas unitarias consisten en dificultar los cambios involuntarios , a veces también pueden dificultar los cambios intencionales . Este hecho está relacionado con el principio DRY.
Por ejemplo, si tiene una función
MyFunction
que se llama en el código de producción en un solo lugar y escribe 20 pruebas unitarias para ella, puede terminar fácilmente teniendo 21 lugares en su código donde se llama esa función. Ahora, cuando tiene que cambiar la firma deMyFunction
, o la semántica, o ambos (porque algunos requisitos cambian), tiene 21 lugares para cambiar en lugar de solo uno. Y la razón es una violación del principio DRY: repitió (al menos) la misma llamada de funciónMyFunction
21 veces.El enfoque correcto para tal caso es aplicar el principio DRY a su código de prueba también: al escribir 20 pruebas unitarias, encapsule las llamadas
MyFunction
en sus pruebas unitarias en solo unas pocas funciones auxiliares (idealmente solo una), que son utilizadas por el 20 pruebas unitarias. Idealmente, terminas con solo dos lugares en tu código de llamadaMyFunction
: uno de tu código de producción y otro de tus pruebas unitarias. Entonces, cuando tenga que cambiar la firma deMyFunction
más adelante, tendrá solo unos pocos lugares para cambiar en sus pruebas."Algunos lugares" siguen siendo más que "un lugar" (lo que se obtiene sin pruebas unitarias), pero las ventajas de tener pruebas unitarias deberían ser mayores que la ventaja de tener menos código para cambiar (de lo contrario, se realizan pruebas unitarias completamente incorrecto).
fuente
Uno de los mayores desafíos para crear software es capturar los requisitos; eso es para responder la pregunta, "¿qué debe hacer este software?" El software necesita requisitos exactos para definir con precisión lo que el sistema necesita hacer, pero quienes definen las necesidades de sistemas y proyectos de software a menudo incluyen personas que no tienen un fondo de software o formal (matemático). La falta de rigor en la definición de requisitos obligó al desarrollo de software a encontrar una manera de validar el software a los requisitos.
El equipo de desarrollo se encontró traduciendo la descripción coloquial de un proyecto en requisitos más rigurosos. La disciplina de prueba se ha unido como el punto de control para el desarrollo de software, para cerrar la brecha entre lo que un cliente dice que quiere y qué software entiende que quiere. Tanto los desarrolladores de software como el equipo de calidad / pruebas forman la comprensión de la especificación (informal), y cada uno (independientemente) escribe software o pruebas para garantizar que su comprensión se ajuste. Agregar a otra persona para comprender los requisitos (imprecisos) agregó preguntas y una perspectiva diferente para perfeccionar aún más la precisión de los requisitos.
Como siempre ha habido pruebas de aceptación, era natural ampliar el rol de prueba para escribir pruebas automatizadas y unitarias. El problema era que eso significaba contratar programadores para hacer pruebas y, por lo tanto, redujo la perspectiva del aseguramiento de la calidad a los programadores que realizan pruebas.
Dicho todo esto, probablemente estás haciendo una prueba incorrecta si tus pruebas difieren poco de los programas reales. La sugerencia de Msdy sería centrarse más en qué en las pruebas y menos en cómo.
La ironía es que, en lugar de capturar una especificación formal de los requisitos de la descripción coloquial, la industria ha optado por implementar pruebas puntuales como código para automatizar las pruebas. En lugar de producir requisitos formales para los cuales el software podría construirse para responder, el enfoque adoptado ha sido probar algunos puntos, en lugar de abordar el software de construcción utilizando la lógica formal. Esto es un compromiso, pero ha sido bastante efectivo y relativamente exitoso.
fuente
Si cree que su código de prueba es demasiado similar a su código de implementación, esto puede ser una indicación de que está utilizando en exceso un marco burlón. Las pruebas simuladas a un nivel demasiado bajo pueden terminar con la configuración de prueba muy parecida al método que se está probando. Intente escribir pruebas de nivel superior que sean menos propensas a romperse si cambia su implementación (sé que esto puede ser difícil, pero si puede administrarlo tendrá un conjunto de pruebas más útil como resultado).
fuente
Las pruebas unitarias no deben incluir una duplicación del código bajo prueba, como ya se ha señalado.
Sin embargo, agregaría que las pruebas unitarias generalmente no son tan SECAS como el código de "producción", porque la configuración tiende a ser similar (pero no idéntica) en todas las pruebas ... especialmente si tiene un número significativo de dependencias de las que se está burlando / fingiendo.
Por supuesto, es posible refactorizar este tipo de cosas en un método de configuración común (o conjunto de métodos de configuración) ... pero he descubierto que esos métodos de configuración tienden a tener largas listas de parámetros y son bastante frágiles.
Así que sé pragmático. Si puede consolidar el código de configuración sin comprometer la mantenibilidad, hágalo por todos los medios. Pero si la alternativa es un conjunto complejo y frágil de métodos de configuración, un poco de repetición en sus métodos de prueba está bien.
Un evangelista local de TDD / BDD lo expresa así:
"Su código de producción debe estar SECO. Pero está bien que sus pruebas estén 'húmedas'".
fuente
Esto no es cierto, las pruebas describen los casos de uso, mientras que el código describe un algoritmo que pasa los casos de uso, por lo que es más general. Con TDD, comienza a escribir casos de uso (probablemente basados en la historia del usuario) y luego implementa el código necesario para pasar estos casos de uso. Entonces escribe una pequeña prueba, una pequeña porción de código, y luego refactoriza si es necesario para deshacerse de las repeticiones. Asi es como funciona.
Por pruebas puede haber repeticiones también. Por ejemplo, puede reutilizar dispositivos, código generador de dispositivos, afirmaciones complicadas, etc. Por lo general, hago esto para evitar errores en las pruebas, pero generalmente olvido probar primero si una prueba realmente falla y realmente puede arruinar el día. , cuando está buscando el error en el código durante media hora y la prueba es incorrecta ... xD
fuente