¿Hay alguna razón por la que las pruebas no se escriben en línea con el código que prueban?

91

He estado leyendo un poco sobre programación literaria recientemente, y me hizo pensar ... Las pruebas bien escritas, especialmente las especificaciones de estilo BDD, pueden hacer un mejor trabajo al explicar qué hace el código que la prosa, y tienen la gran ventaja de verificar su propia precisión

Nunca he visto pruebas escritas en línea con el código que prueban. ¿Es esto solo porque los idiomas no tienden a simplificar la separación de la aplicación y el código de prueba cuando se escriben en el mismo archivo fuente (y nadie lo ha facilitado), o hay una razón más basada en principios para que las personas separen el código de prueba del código de la aplicación?

Chris Devereux
fuente
33
Algunos lenguajes de programación como python con doctest le permiten hacer eso.
Simon Bergot
2
Puede sentir que las especificaciones de estilo BDD son mejores que la prosa para explicar el código, pero eso no significa que la combinación de los dos no sea mejor.
JeffO
55
La mitad de los argumentos aquí se aplican también a la documentación en línea.
CodesInChaos
3
Los doctest de @Simon son demasiado simplistas para pruebas serias, principalmente porque no están diseñados para ello. Fueron diseñados para, y se destacan en, tener ejemplos de código en la documentación que pueden verificarse automáticamente. Ahora, algunas personas los usan también para pruebas unitarias, pero últimamente (como en los últimos años) esto ha sido objeto de muchas críticas, ya que tiende a terminar en problemas frágiles, "documentación" demasiado detallada y otros problemas.
77
El diseño por contrato permite especificaciones en línea que hacen que las pruebas sean sencillas.
Fuhrmanator

Respuestas:

89

La única ventaja que se me ocurre para las pruebas en línea sería la reducción de la cantidad de archivos que se escribirán. Con los IDEs modernos, esto realmente no es un gran problema.

Sin embargo, hay una serie de inconvenientes obvios para las pruebas en línea:

  • Viola la separación de las preocupaciones . Esto puede ser discutible, pero para mí probar la funcionalidad es una responsabilidad diferente que implementarla.
  • Tendría que introducir nuevas características de lenguaje para distinguir entre pruebas / implementación, o correría el riesgo de difuminar la línea entre los dos.
  • Es más difícil trabajar con archivos de origen más grandes: más difíciles de leer, más difíciles de entender, es más probable que tenga que lidiar con conflictos de control de origen.
  • Creo que sería más difícil ponerse el sombrero "probador", por así decirlo. Si observa los detalles de implementación, estará más tentado a omitir la implementación de ciertas pruebas.
vaughandroid
fuente
99
Eso es interesante. Supongo que la ventaja que puedo ver es que cuando tienes puesto tu sombrero "codificador", quieres pensar en las pruebas, pero es un buen punto que lo contrario no es cierto.
Chris Devereux
2
En este sentido, es posible (y quizás deseable) que una persona cree las pruebas y una segunda implemente el código. Poner las pruebas en línea hace que esto sea más difícil.
Jim Nutt
66
Votaría si pudiera. ¿Cómo es esta una respuesta de alguna manera? Los implementadores no escriben pruebas? ¿Las personas se saltan las pruebas si miran los detalles de implementación? "Simplemente demasiado difícil" Conflictos en archivos grandes? ¿Y cómo de alguna manera podría confundirse una prueba con un detalle de implementación?
bharal
55
@bharal También, wrt a "Simplemente demasiado duro", el masoquismo es una virtud tonta. Quiero que todo sea fácil, excepto el problema que realmente estoy tratando de resolver.
deworde
3
La prueba unitaria puede considerarse documentación. Eso sugiere que las pruebas unitarias deberían incluirse en el código por la misma razón que los comentarios, para mejorar la legibilidad. Sin embargo, el problema con esto es que suele haber muchas pruebas unitarias y muchos gastos generales de implementación de pruebas que no especifican los resultados esperados. Incluso los comentarios dentro del código deben mantenerse sucintos, con explicaciones más amplias que se eliminen del camino: a un bloque de comentarios fuera de la función, a un archivo separado o tal vez a un documento de diseño. Las pruebas unitarias son IMO rara vez, si es que son lo suficientemente cortas como para mantener el código probado como comentarios.
Steve314
36

Puedo pensar en algunos:

  • Legibilidad. La intercalación de códigos y pruebas "reales" hará que sea más difícil leer el código real.

  • Código hinchado. Mezclar código "real" y código de prueba en los mismos archivos / clases / lo que sea probable que dé como resultado archivos compilados más grandes, etc. Esto es particularmente importante para los idiomas con enlace tardío.

  • Es posible que no desee que sus clientes / clientes vean su código de prueba. (No me gusta esta razón ... pero si está trabajando en un proyecto de código cerrado, es poco probable que el código de prueba ayude al cliente de todos modos).

Ahora hay posibles soluciones para cada uno de estos problemas. Pero en mi opinión, es más simple no ir allí en primer lugar.


Vale la pena observar que en los primeros días, los programadores de Java solían hacer este tipo de cosas; por ejemplo, incluir un main(...)método en una clase para facilitar las pruebas. Esta idea ha desaparecido casi por completo. Es práctica de la industria implementar pruebas por separado utilizando un marco de prueba de algún tipo.

También vale la pena observar que la programación literaria (tal como la concibió Knuth) nunca ha tenido éxito en la industria de la ingeniería de software.

Stephen C
fuente
44
Problemas de legibilidad +1: el código de prueba puede ser proporcionalmente mayor que el código de implementación, especialmente en los diseños OO.
Fuhrmanator
2
+1 para señalar utilizando marcos de prueba. No me puedo imaginar usar un buen marco de prueba al mismo tiempo que el código de producción.
joshin4colours
1
RE: Es posible que no desee que sus clientes / clientes vean su código de prueba. (No me gusta esta razón ... pero si está trabajando en un proyecto de código cerrado, es poco probable que el código de prueba ayude al cliente de todos modos). Puede ser conveniente ejecutar las pruebas en la máquina del cliente. Ejecución de las pruebas puede ayudar a identificar rápidamente cuál es el problema y para las diferencias de ID en el env clientes ..
sixtyfootersdude
1
@sixtyfootersdude: esa es una situación bastante inusual. Y suponiendo que estuviera desarrollando código cerrado, no querría incluir sus pruebas en su distribución binaria estándar por si acaso. (Crearía un paquete separado que contiene las pruebas que desea que ejecute el cliente).
Stephen C
1
1) ¿Te perdiste la primera parte de mi respuesta donde di tres razones reales? Hubo algún "pensamiento crítico" involucrado allí ... 2) ¿Te perdiste la segunda parte donde dije que los programadores de Java solían hacer esto, pero ahora no lo hacen? Y la obvia implicación de que los programadores dejaron de hacer esto ... ¿por una buena razón?
Stephen C
14

En realidad, puede pensar en Design By Contract como haciendo esto. El problema es que la mayoría de los lenguajes de programación no le permiten escribir código como este :( Es muy fácil probar las condiciones previas a mano, pero las condiciones de publicación son un verdadero desafío sin cambiar la forma en que escribe el código (una gran IMO negativa).

Michael Feathers tiene una presentación sobre esto y esta es una de las muchas formas en que menciona que puede mejorar la calidad del código.

Daniel Kaplan
fuente
13

Por muchas de las mismas razones por las que intenta evitar el acoplamiento estrecho entre clases en su código, también es una buena idea evitar el acoplamiento innecesario entre las pruebas y el código.

Creación: las pruebas y el código pueden ser escritos en diferentes momentos, por diferentes personas.

Control: si se utilizan pruebas para especificar requisitos, seguramente querrá que estén sujetas a diferentes reglas sobre quién puede cambiarlas y cuándo es el código real.

Reusabilidad: si coloca las pruebas en línea, no puede usarlas con otro código.

Imagine que tiene una porción de código que hace el trabajo correctamente, pero deja mucho que desear en términos de rendimiento, mantenibilidad, lo que sea. Decide reemplazar ese código con código nuevo y mejorado. Usar el mismo conjunto de pruebas puede ayudarlo a verificar que el nuevo código produzca los mismos resultados que el código anterior.

Posibilidad de selección: mantener las pruebas separadas del código facilita la elección de las pruebas que desea ejecutar.

Por ejemplo, puede tener un pequeño conjunto de pruebas que se relacionan solo con el código en el que está trabajando actualmente, y un conjunto más grande que prueba todo el proyecto.

Caleb
fuente
Estoy desconcertado por sus razones: TDD ya dice que la creación de la prueba ocurre antes (o al mismo tiempo) que el código de producción, ¡y debe hacerla el mismo codificador! También sugieren que las pruebas son muy parecidas a los requisitos. Por supuesto, estas objeciones no se aplican si no se suscribe al dogma TDD (lo cual sería aceptable, ¡pero debe dejarlo en claro!). Además, ¿qué es exactamente una prueba "reutilizable"? ¿Las pruebas, por definición, no son específicas del código que prueban?
Andres F.
1
@AndresF. No, las pruebas no son específicas del código que prueban; son específicos del comportamiento para el que prueban. Entonces, supongamos que tiene un módulo Widget completo con un conjunto de pruebas que verifican que Widget se está comportando correctamente. A su colega se le presenta BetterWidget, que pretende hacer lo mismo que Widget pero tres veces más rápido. Si las pruebas para Widget están incrustadas en el código fuente del Widget de la misma manera que Literate Programming integra la documentación dentro del código fuente, no puede aplicar esas pruebas a BetterWidget para verificar que se comporta igual que Widget.
Caleb
@AndresF. no es necesario especificar que no sigue TDD. No es un defecto cósmico. En cuanto al punto de reutilización. Al probar un sistema, le interesan las entradas y salidas, no las partes internas. Cuando necesite crear un nuevo sistema que se comporte igual pero que se implemente de manera diferente, es bueno tener pruebas que pueda ejecutar tanto en el sistema antiguo como en el nuevo. Esto me sucedió más de una vez, a veces necesitas trabajar en el nuevo sistema mientras el viejo todavía está en producción o incluso ejecutarlo de lado a lado. mire la forma en que Facebook estaba probando 'reaccionar fibra' con las pruebas de reacción para llegar a la paridad.
user1852503
10

Aquí hay algunas razones adicionales en las que puedo pensar:

  • tener pruebas en una biblioteca separada hace que sea más fácil vincular solo esa biblioteca con su marco de prueba, y no con su código de producción (esto podría ser evitado por algún preprocesador, pero por qué construir tal cosa cuando la solución más fácil es escribir las pruebas en un lugar separado)

  • Las pruebas de una función, una clase, una biblioteca generalmente se escriben desde el punto de vista de "usuarios" (un usuario de esa función / clase / biblioteca). Dicho "uso de código" generalmente se escribe en un archivo o biblioteca separados, y una prueba puede ser más clara o "más realista" si imita esa situación.

Doc Brown
fuente
5

Si las pruebas estuvieran en línea, sería necesario eliminar el código que necesita para realizar las pruebas cuando envíe el producto a su cliente. Por lo tanto, un lugar adicional donde almacena sus pruebas simplemente se separa entre el código que necesita y el código que necesita su cliente .

mhr
fuente
99
No imposible. Requeriría una fase de preprocesamiento adicional, al igual que LP. Se podría hacer fácilmente en C, o un lenguaje de compilación a js, por ejemplo.
Chris Devereux
+1 por señalarme eso. He editado mi respuesta para representar eso.
mhr
También se supone que el tamaño del código es importante en todos los casos. Solo porque importa en algunos casos no significa que importe en todos los casos. Hay muchos entornos en los que los programadores no están obligados a optimizar el tamaño del código fuente. Si ese fuera el caso, no estarían creando tantas clases.
zumalifeguard
5

Esta idea simplemente equivale a un método "Self_Test" dentro del contexto de un diseño orientado a objetos o basado en objetos. Si utiliza un lenguaje compilado basado en objetos como Ada, el compilador marcará todo el código de autocomprobación como no utilizado (nunca invocado) durante la compilación de producción y, por lo tanto, todo se optimizará, ninguno aparecerá en el ejecutable resultante.

Usar un método "Self_Test" es una muy buena idea, y si los programadores estuvieran realmente preocupados por la calidad, todos lo estarían haciendo. Sin embargo, una cuestión importante es que el método "Self_Test" debe tener una disciplina intensa, ya que no puede acceder a ninguno de los detalles de implementación y en su lugar debe confiar únicamente en todos los demás métodos publicados dentro de la especificación del objeto. Obviamente, si la autocomprobación falla, la implementación deberá cambiar. La autocomprobación debe probar rigurosamente todas las propiedades publicadas de los métodos del objeto, pero nunca depender de ningún modo de ningún detalle de una implementación específica.

Los lenguajes basados ​​en objetos y orientados a objetos con frecuencia proporcionan exactamente ese tipo de disciplina con respecto a los métodos externos al objeto probado (hacen cumplir la especificación del objeto, impiden el acceso a los detalles de su implementación y generan un error de compilación si se detecta cualquier intento de este tipo). ) Pero todos los métodos internos del objeto tienen acceso completo a cada detalle de implementación. Por lo tanto, el método de autocomprobación se encuentra en una situación única: debe ser un método interno debido a su naturaleza (la autocomprobación es obviamente un método del objeto que se está probando), pero necesita recibir toda la disciplina del compilador de un método externo ( tiene que ser independiente de los detalles de implementación del objeto). Pocos lenguajes de programación ofrecen la capacidad de disciplinar un objeto ' s método interno como si fuera un método externo. Así que este es un problema importante de diseño del lenguaje de programación

En ausencia de un soporte de lenguaje de programación adecuado, la mejor manera de hacerlo es crear un objeto complementario. En otras palabras, para cada objeto que codifique (llamémoslo "Big_Object"), también crea un segundo objeto complementario cuyo nombre consiste en un sufijo estándar concatenado con el nombre del objeto "real" (en este caso, "Big_Object_Self_Test "), y cuya especificación consiste en un único método (" Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) return Boolean; "). El objeto complementario dependerá entonces de la especificación del objeto principal, y el compilador aplicará completamente toda la disciplina de esa especificación contra la implementación del objeto complementario.

comentarista8
fuente
4

Esto es en respuesta a una gran cantidad de comentarios que sugieren que no se realizan pruebas en línea porque es difícil o imposible eliminar el código de prueba de las versiones de lanzamiento. Esto no es cierto. Casi todos los compiladores y ensambladores ya lo admiten, con lenguajes compilados, como C, C ++, C #, esto se hace con las llamadas directivas de compilación.

En el caso de C # (también creo que C ++, la sintaxis podría haber sido ligeramente diferente dependiendo del compilador que esté usando) así es como puede hacerlo.

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

Debido a que utiliza directivas de compilación, el código no existirá en los archivos ejecutables que se crean si no se establecen los indicadores. Así es también como se hacen programas de "escribir una vez, compilar dos veces" para múltiples plataformas / hardware.

John
fuente
2

Utilizamos pruebas en línea con nuestro código Perl. Hay un módulo, Test :: Inline , que genera archivos de prueba a partir del código en línea.

No soy particularmente bueno organizando mis pruebas, y he encontrado que es más fácil y más probable que se mantengan cuando están en línea.

Respondiendo a un par de preocupaciones planteadas:

  • Las pruebas en línea están escritas en secciones POD, por lo que no forman parte del código real. El intérprete los ignora, por lo que no hay código hinchado.
  • Utilizamos el plegado Vim para ocultar las secciones de prueba. Lo único que ves es una sola línea encima de cada método que se está probando +-- 33 lines: #test----. Cuando desee trabajar con la prueba, simplemente amplíela.
  • El módulo Test :: Inline "compila" las pruebas en archivos normales compatibles con TAP, para que puedan coexistir con las pruebas tradicionales.

Para referencia:

mla
fuente
1

Erlang 2 en realidad admite pruebas en línea. Cualquier expresión booleana en el código que no se utiliza (por ejemplo, asignada a una variable o aprobada) se trata automáticamente como una prueba y el compilador la evalúa; Si la expresión es falsa, el código no se compila.

Mark Rendle
fuente
1

Otra razón para separar las pruebas es que a menudo utiliza bibliotecas adicionales o incluso diferentes para las pruebas que para la implementación real. Si combina pruebas e implementación, el compilador no puede detectar el uso accidental de las bibliotecas de prueba en la implementación.

Además, las pruebas tienden a tener muchas más líneas de código que las partes de implementación que prueban, por lo que tendrá problemas para encontrar la implementación entre todas las pruebas. :-)

Hans-Peter Störr
fuente
0

Esto no es verdad Es mucho mejor colocar las pruebas unitarias junto al código de producción cuando el código de producción, especialmente cuando la rutina de producción es pura.

Si está desarrollando bajo .NET, por ejemplo, puede poner su código de prueba en el ensamblaje de producción y luego usar Scalpel para eliminarlos antes de enviarlo.

zumalifeguard
fuente