¿Cómo deben escribirse exactamente las pruebas unitarias sin burlarse ampliamente?

80

Según tengo entendido, el objetivo de las pruebas unitarias es probar las unidades de código de forma aislada . Esto significa que:

  1. No deben interrumpir ningún cambio de código no relacionado en otra parte de la base de código.
  2. Solo una prueba de unidad debe romperse por un error en la unidad probada, en oposición a las pruebas de integración (que pueden romperse en montones).

Todo esto implica que todas las dependencias externas de una unidad probada deben ser burladas. Y me refiero a todas las dependencias externas , no solo a las "capas externas" como redes, sistema de archivos, base de datos, etc.

Esto lleva a una conclusión lógica, que prácticamente todas las pruebas unitarias deben burlarse . Por otro lado, una búsqueda rápida en Google sobre burla revela toneladas de artículos que afirman que "burlarse es un olor a código" y que debe evitarse en su mayoría (aunque no completamente).

Ahora, a la (s) pregunta (s).

  1. ¿Cómo deben escribirse correctamente las pruebas unitarias?
  2. ¿Dónde se encuentra exactamente la línea entre ellos y las pruebas de integración?

Actualización 1

Considere el siguiente pseudocódigo:

class Person {
    constructor(calculator) {}

    calculate(a, b) {
        const sum = this.calculator.add(a, b);

        // do some other stuff with the `sum`
    }
}

¿Se puede considerar una prueba unitaria una prueba que prueba el Person.calculatemétodo sin burlarse de la Calculatordependencia (dado que Calculatores una clase ligera que no accede al "mundo exterior")?

Alexander Lomia
fuente
10
Parte de esto es solo la experiencia de diseño que vendrá con el tiempo. Aprenderá a estructurar sus componentes para que no tengan muchas dependencias difíciles de burlarse. Esto significa que la capacidad de prueba debe ser un objetivo de diseño secundario de cualquier software. Este objetivo es mucho más fácil de cumplir si las pruebas se escriben antes o junto con el código, por ejemplo, usando TDD y / o BDD.
amon
33
Ponga pruebas que se ejecutan de manera rápida y confiable en una carpeta. Ponga pruebas que son lentas y potencialmente frágiles en otra. Ejecute las pruebas en la primera carpeta con la mayor frecuencia posible (literalmente, cada vez que hace una pausa al escribir y el código se compila es lo ideal, pero no todos los entornos de desarrollo lo admiten). Ejecute las pruebas más lentas con menos frecuencia (cuando tenga un descanso para tomar café, por ejemplo). No se preocupe por los nombres de unidad e integración. Llámalos rápido y lento si quieres. No importa.
David Arno
66
"que prácticamente todas las pruebas unitarias deben burlarse" Sí, ¿y? "Una búsqueda rápida en Google sobre burla revela toneladas de artículos que afirman que" burlarse es un olor a código "" están equivocados .
Michael
13
@Michael Simplemente decir "sí, entonces" y declarar que la opinión opuesta es incorrecta no es una excelente manera de abordar un tema polémico como este. Tal vez escriba una respuesta y explique por qué cree que la burla debería estar en todas partes, y tal vez por qué piensa que 'toneladas de artículos' son inherentemente incorrectos.
AJFaraday
66
Dado que no mencionó "burlarse es un olor a código", solo puedo suponer que está malinterpretando lo que lee. Burlarse no es un olor a código. La necesidad de usar reflejos u otras travesuras para inyectar tus simulacros es un olor a código. La dificultad de burlarse es inversamente proporcional a la calidad de su diseño de API. Si puede escribir pruebas unitarias sencillas y sencillas que simplemente pasen simulacros a los constructores, lo está haciendo bien.
TKK

Respuestas:

59

El objetivo de las pruebas unitarias es probar las unidades de código de forma aislada.

Martin Fowler en prueba de unidad

Las pruebas unitarias a menudo se mencionan en el desarrollo de software, y es un término con el que he estado familiarizado durante todo mi tiempo escribiendo programas. Sin embargo, como la mayoría de la terminología de desarrollo de software, está muy mal definida, y veo que a menudo puede ocurrir confusión cuando la gente piensa que está más estrictamente definida de lo que realmente es.

Lo que Kent Beck escribió en Test Driven Development, por ejemplo

Los llamo "pruebas unitarias", pero no coinciden muy bien con la definición aceptada de pruebas unitarias

Cualquier afirmación dada de "el punto de las pruebas unitarias es" dependerá en gran medida de qué definición de "prueba unitaria" se está considerando.

Si su perspectiva es que su programa se compone de muchas unidades pequeñas que dependen unas de otras, y si se limita a un estilo que pruebe cada unidad de forma aislada, muchas pruebas de duplicación son una conclusión inevitable.

El consejo contradictorio que ve proviene de personas que operan bajo un conjunto diferente de supuestos.

Por ejemplo, si está escribiendo pruebas para apoyar a los desarrolladores durante el proceso de refactorización, y dividir una unidad en dos es una refactorización que debe ser compatible, entonces algo debe darse. ¿Quizás este tipo de prueba necesita un nombre diferente? O tal vez necesitamos una comprensión diferente de "unidad".

Es posible que desee comparar:

¿Puede una prueba que prueba el método Person.calculate sin burlarse de la dependencia de la Calculadora (dado que la Calculadora es una clase ligera que no accede al "mundo exterior") puede considerarse una prueba unitaria?

Creo que esa es la pregunta incorrecta que hacer; nuevamente es un argumento sobre las etiquetas , cuando creo que lo que realmente nos importa son las propiedades .

Cuando introduzco cambios en el código, no me importa el aislamiento de las pruebas: ya sé que "el error" está en algún lugar de mi pila actual de ediciones no verificadas. Si ejecuto las pruebas con frecuencia, entonces limito la profundidad de esa pila, y encontrar el error es trivial (en el caso extremo, las pruebas se ejecutan después de cada edición; la profundidad máxima de la pila es una). Pero ejecutar las pruebas no es el objetivo, es una interrupción, por lo que tiene valor reducir el impacto de la interrupción. Una forma de reducir la interrupción es asegurarse de que las pruebas sean rápidas ( Gary Bernhardt sugiere 300 ms , pero no he descubierto cómo hacerlo en mis circunstancias).

Si invocar Calculator::addno aumenta significativamente el tiempo requerido para ejecutar la prueba (o cualquiera de las otras propiedades importantes para este caso de uso), entonces no me molestaría en usar una prueba doble: no proporciona beneficios que superen los costos .

Observe los dos supuestos aquí: un ser humano como parte de la evaluación de costos, y la pequeña pila de cambios no verificados en la evaluación de beneficios. En circunstancias donde esas condiciones no se cumplen, el valor de "aislamiento" cambia bastante.

Ver también la lava caliente , por Harry Percival.

VoiceOfUnreason
fuente
55
una cosa que hace burlarse de agregar es demostrar que la calculadora puede ser burlada, es decir, que el diseño no combina persona y calculadora (aunque esto también se puede verificar de otras maneras)
jk.
40

¿Cómo deben escribirse exactamente las pruebas unitarias sin burlarse ampliamente?

Al minimizar los efectos secundarios en su código.

Tomando su código de ejemplo, si, calculatorpor ejemplo, habla con una API web, entonces crea pruebas frágiles que dependen de poder interactuar con esa API web, o crea una simulación de ella. Sin embargo, si se trata de un conjunto de funciones de cálculo determinista y sin estado, entonces no (y no debería) burlarse de él. Si lo hace, corre el riesgo de que su simulacro se comporte de manera diferente al código real, lo que provocará errores en sus pruebas.

Los simulacros solo deberían ser necesarios para el código que lee / escribe en el sistema de archivos, bases de datos, puntos finales de URL, etc. que dependen del entorno en el que se está ejecutando; o que son de naturaleza altamente declarativa y no determinista. Entonces, si mantiene esas partes del código al mínimo y las oculta detrás de abstracciones, entonces son fáciles de burlarse y el resto de su código evita la necesidad de simulacros.

Para los puntos de código que tienen efectos secundarios, vale la pena escribir pruebas que se burlen y pruebas que no. Sin embargo, estos últimos necesitan cuidados, ya que serán inherentemente frágiles y posiblemente lentos. Por lo tanto, es posible que solo desee ejecutarlos durante la noche en un servidor de CI, en lugar de cada vez que guarde y cree su código. Sin embargo, las pruebas anteriores deben ejecutarse con la mayor frecuencia posible. En cuanto a si cada prueba es entonces una prueba de unidad o integración, se convierte en académica y evita las "guerras de llamas" sobre lo que es y no es una prueba de unidad.

David Arno
fuente
8
Esta es la respuesta correcta tanto en la práctica como en términos de esquivar un debate semántico sin sentido.
Jared Smith el
¿Tiene un ejemplo de una base de código de código abierto no trivial que usa ese estilo y todavía obtiene una buena cobertura de prueba?
Joeri Sebrechts
44
@JoeriSebrechts cada FP uno? Ejemplo
Jared Smith el
No es exactamente lo que estoy buscando, ya que es solo una colección de funciones que son independientes entre sí, no un sistema de funciones que se llaman entre sí. ¿Cómo evitas tener que construir argumentos complejos para una función con el fin de probarla, si esa función es una de las de nivel superior? Por ejemplo, el bucle central de un juego.
Joeri Sebrechts
1
@JoeriSebrechts hmm, o no estoy entendiendo lo que quieres, o no profundizaste lo suficiente en el ejemplo que di. Las funciones ramda utilizan llamadas internas en todo el lugar en su fuente (por ejemplo R.equals). Debido a que estas son en su mayoría funciones puras, generalmente no se burlan en las pruebas.
Jared Smith
31

Estas preguntas son bastante diferentes en su dificultad. Tomemos la pregunta 2 primero.

Las pruebas unitarias y las pruebas de integración están claramente separadas. Una prueba de unidad prueba una unidad (método o clase) y usa otras unidades solo la cantidad necesaria para lograr ese objetivo. Puede ser necesario burlarse, pero no es el objetivo de la prueba. Una prueba de integración prueba la interacción entre diferentes unidades reales . Esta diferencia es la razón por la que necesitamos pruebas unitarias y de integración: si una hiciera el trabajo de la otra lo suficientemente bien, no lo haríamos, pero resultó que generalmente es más eficiente usar dos herramientas especializadas en lugar de una herramienta generalizada .

Ahora para la pregunta importante: ¿Cómo se debe probar la unidad? Como se dijo anteriormente, las pruebas unitarias deben construir estructuras auxiliares solo en la medida necesaria . A menudo es más fácil usar una base de datos simulada que su base de datos real o incluso cualquier base de datos real. Sin embargo, burlarse en sí mismo no tiene valor. Si a menudo sucede que, de hecho, es más fácil usar componentes reales de otra capa como entrada para una prueba de unidad de nivel medio. Si es así, no dudes en usarlos.

Muchos profesionales temen que si la prueba unitaria B reutiliza las clases que ya fueron probadas por la prueba unitaria A, entonces un defecto en la unidad A causa fallas en la prueba en varios lugares. Considero que esto no es un problema: tiene un conjunto de pruebas para tener éxito 100% con el fin de darle la tranquilidad que necesita, por lo que no es un gran problema a tener demasiados fracasos - después de todo, hacer tener un defecto. El único problema crítico sería si un defecto provocara muy pocas fallas.

Por lo tanto, no hagas una religión de burla. Es un medio, no un fin, por lo que si puede evitar el esfuerzo adicional, debe hacerlo.

Kilian Foth
fuente
44
The only critical problem would be if a defect triggered too few failures.Este es uno de los puntos débiles de la burla. Tenemos que "programar" el comportamiento esperado, por lo que podríamos fallar al hacerlo, haciendo que nuestras pruebas terminen como "falsos positivos". Pero burlarse es una técnica muy útil para lograr el determinismo (la condición más importante de las pruebas). Los uso en todos mis proyectos cuando es posible. También me muestran cuando la integración es demasiado compleja o la dependencia demasiado estrecha.
Laiv
1
Si una unidad que se está probando usa otras unidades, ¿no es realmente una prueba de integración? Porque, en esencia, esta unidad probaría la interacción entre dichas unidades, exactamente como lo haría una prueba de integración.
Alexander Lomia
11
@AlexanderLomia: ¿Cómo llamarías a una unidad? ¿Llamarías a 'String' una unidad también? Lo haría, pero no soñaría con burlarme de él.
Bart van Ingen Schenau
55
" Las pruebas unitarias y las pruebas de integración están claramente separadas. Una prueba unitaria prueba una unidad (método o clase) y usa otras unidades solo la cantidad necesaria para lograr ese objetivo ". Aquí está el problema. Esa es su definición de una prueba unitaria. El mío es muy diferente. Por lo tanto, la distinción entre ellos solo está "claramente separada" para cualquier definición dada, pero la separación varía entre definiciones.
David Arno
44
@Voo Habiendo trabajado con tales bases de código, aunque puede ser una molestia encontrar el problema original (especialmente si la arquitectura ha pintado sobre las cosas que usarías para depurarlo), todavía he tenido más problemas con las simulaciones que causaron el problema. pruebas para seguir trabajando después de que el código que se utilizaron para probar se había roto.
James_pic
6

OK, para responder sus preguntas directamente:

¿Cómo deben escribirse correctamente las pruebas unitarias?

Como dices, deberías burlarte de las dependencias y probar solo la unidad en cuestión.

¿Dónde se encuentra exactamente la línea entre ellos y las pruebas de integración?

Una prueba de integración es una prueba unitaria en la que no se burlan de sus dependencias.

¿Se puede considerar una prueba unitaria una prueba que prueba el método Person.calculate sin burlarse de la Calculadora?

No. Debe inyectar la dependencia de la calculadora en este código y puede elegir entre una versión simulada o una real. Si usa uno simulado, es una prueba unitaria, si usa uno real, es una prueba de integración.

Sin embargo, una advertencia. ¿realmente te importa cómo la gente cree que deberían llamarse tus pruebas?

Pero su verdadera pregunta parece ser esta:

una búsqueda rápida en Google sobre burla revela toneladas de artículos que afirman que "burlarse es un olor a código" y que debe evitarse en su mayoría (aunque no completamente).

Creo que el problema aquí es que mucha gente usa simulacros para recrear completamente las dependencias. Por ejemplo, podría burlarme de la calculadora en tu ejemplo como

public class MockCalc : ICalculator
{
     public Add(int a, int b) { return 4; }
}

No haría algo como:

myMock = Mock<ICalculator>().Add((a,b) => {return a + b;})
myPerson.Calculate()
Assert.WasCalled(myMock.Add());

Yo diría que eso sería "probar mi simulacro" o "probar la implementación". Yo diría " ¡No escribas simulacros! * Así".

Otras personas no estarían de acuerdo conmigo, comenzaríamos guerras masivas de llamas en nuestros blogs sobre la Mejor manera de burlarse, lo que realmente no tendría sentido a menos que entendiera todo el trasfondo de los diversos enfoques y realmente no ofrezca mucho valor a alguien que solo quiere escribir buenas pruebas.

Ewan
fuente
Gracias por una respuesta exhaustiva. En cuanto a preocuparme por lo que otras personas piensan sobre mis pruebas, en realidad quiero evitar escribir pruebas de semiintegración y semiunidades que tienden a convertirse en un desastre poco confiable a medida que avanza el proyecto.
Alexander Lomia
sin problemas, creo que el problema es que las definiciones de las dos cosas no están 100% acordadas por todos.
Ewan
Cambiaría el nombre de tu clase MockCalca StubCalc, y lo llamaría un trozo, no un simulacro. martinfowler.com/articles/…
bdsl
@bdsl Ese artículo tiene 15 años
Ewan
4
  1. ¿Cómo deben implementarse las pruebas unitarias correctamente?

Mi regla general es que las pruebas unitarias adecuadas:

  • Están codificados contra interfaces, no implementaciones . Esto tiene muchos beneficios. Por un lado, asegura que sus clases sigan el Principio de Inversión de Dependencia de SOLID . Además, esto es lo que hacen tus otras clases ( ¿verdad? ), Por lo que tus pruebas deberían hacer lo mismo. Además, esto le permite probar múltiples implementaciones de la misma interfaz mientras reutiliza gran parte del código de prueba (solo la inicialización y algunas afirmaciones cambiarían).
  • Son independientes . Como dijiste, los cambios en cualquier código externo no pueden afectar el resultado de la prueba. Como tal, las pruebas unitarias pueden ejecutarse en tiempo de construcción. Esto significa que necesita simulacros para eliminar cualquier efecto secundario. Sin embargo, si está siguiendo el Principio de Inversión de Dependencia, esto debería ser relativamente fácil. Los buenos marcos de prueba como Spock se pueden usar para proporcionar dinámicamente implementaciones simuladas de cualquier interfaz para usar en sus pruebas con una codificación mínima. Esto significa que cada clase de prueba solo necesita ejercer código de exactamente una clase de implementación, más el marco de prueba (y tal vez las clases de modelo ["beans"]).
  • No requiere una aplicación en ejecución por separado . Si la prueba necesita "hablar con algo", ya sea una base de datos o un servicio web, es una prueba de integración, no una prueba unitaria. Trazo la línea en las conexiones de red o en el sistema de archivos. Una base de datos SQLite puramente en memoria, por ejemplo, es un juego justo en mi opinión para una prueba unitaria si realmente la necesita.

Si hay clases de utilidad de marcos que complican las pruebas unitarias, incluso puede resultarle útil crear interfaces y clases "envolventes" muy simples para facilitar la burla de esas dependencias. Esas envolturas no estarían necesariamente sujetas a pruebas unitarias.

  1. ¿Dónde se encuentra exactamente la línea entre ellos [pruebas unitarias] y las pruebas de integración?

He encontrado esta distinción como la más útil:

  • Las pruebas unitarias simulan "código de usuario" , verificando el comportamiento de las clases de implementación contra el comportamiento deseado y la semántica de las interfaces de nivel de código .
  • Las pruebas de integración simulan al usuario , verificando el comportamiento de la aplicación en ejecución frente a casos de uso específicos y / o API formales. Para un servicio web, el "usuario" sería una aplicación cliente.

Hay área gris aquí. Por ejemplo, si puede ejecutar una aplicación en un contenedor Docker y ejecutar las pruebas de integración como la etapa final de una compilación, y destruir el contenedor después, ¿está bien incluir esas pruebas como "pruebas unitarias"? Si este es tu debate candente, estás en un lugar bastante bueno.

  1. ¿Es cierto que prácticamente todas las pruebas unitarias deben burlarse?

No. Algunos casos de prueba individuales serán para condiciones de error, como pasar nullcomo parámetro y verificar que obtenga una excepción. Muchas pruebas como esa no requerirán ningún simulacro. Además, las implementaciones que no tienen efectos secundarios, por ejemplo, el procesamiento de cadenas o las funciones matemáticas, pueden no requerir simulacros porque simplemente verifica la salida. Pero creo que la mayoría de las clases que valen la pena requerirán al menos un simulacro en alguna parte del código de prueba. (Cuanto menos, mejor).

El problema de "olor a código" que mencionó surge cuando tiene una clase que es demasiado complicada, que requiere una larga lista de dependencias simuladas para poder escribir sus pruebas. Esta es una pista de que necesita refactorizar la implementación y dividir las cosas, para que cada clase tenga una huella más pequeña y una responsabilidad más clara y, por lo tanto, sea más fácilmente comprobable. Esto mejorará la calidad a largo plazo.

Solo una prueba de unidad debe romperse por un error en la unidad probada.

No creo que sea una expectativa razonable, porque funciona contra la reutilización. Es posible que tenga un privatemétodo, por ejemplo, llamado por varios publicmétodos publicados por su interfaz. Un error introducido en ese método podría causar múltiples fallas de prueba. Esto no significa que deba copiar el mismo código en cada publicmétodo.

fresa
fuente
3
  1. No deben interrumpir ningún cambio de código no relacionado en otra parte de la base de código.

No estoy realmente seguro de cómo es útil esta regla. Si un cambio en una clase / método / lo que sea puede romper el comportamiento de otra en el código de producción, entonces las cosas son, en realidad, colaboradoras y no están relacionadas. Si sus pruebas se rompen y su código de producción no, entonces sus pruebas son sospechosas.

  1. Solo una prueba de unidad debe romperse por un error en la unidad probada, en oposición a las pruebas de integración (que pueden romperse en montones).

Consideraría esta regla con sospecha también. Si eres lo suficientemente bueno como para estructurar tu código y escribir tus pruebas de manera que un error cause exactamente una falla en la prueba de la unidad, entonces estás diciendo que ya has identificado todos los errores potenciales, incluso a medida que la base de código evoluciona para usar casos No lo he anticipado.

¿Dónde se encuentra exactamente la línea entre ellos y las pruebas de integración?

No creo que sea una distinción importante. ¿Qué es una 'unidad' de código de todos modos?

Intente encontrar puntos de entrada en los que pueda escribir pruebas que simplemente 'tengan sentido' en términos de las reglas de dominio / negocio problemáticas con las que se enfrenta ese nivel del código. A menudo, estas pruebas son de naturaleza algo "funcional": ingrese una entrada y compruebe que la salida es la esperada. Si las pruebas expresan un comportamiento deseado del sistema, a menudo permanecen bastante estables incluso a medida que el código de producción evoluciona y se refactoriza.

¿Cómo deben escribirse exactamente las pruebas unitarias sin burlarse ampliamente?

No lea demasiado la palabra 'unidad', e inclínese hacia el uso de sus clases de producción reales en las pruebas, sin preocuparse demasiado si está involucrando a más de uno de ellos en una prueba. Si uno de ellos es difícil de usar (porque requiere mucha inicialización o necesita golpear una base de datos / servidor de correo electrónico real, etc.), deje que sus pensamientos se vuelvan burlones / falsos.

topo morto
fuente
" ¿Qué es una 'unidad' de código de todos modos? ", Una pregunta muy buena que puede tener respuestas inesperadas que incluso pueden depender de quién está respondiendo. Por lo general, la mayoría de las definiciones de pruebas unitarias las explican como relacionadas con un método o una clase, pero esa no es una medida realmente útil de una "unidad" en todos los casos. Si tengo un Person:tellStory()método que incorpora los detalles de una persona en una cadena y luego lo devuelve, entonces la "historia" es probablemente una unidad. Si creo un método auxiliar privado que oculta parte del código, entonces no creo que haya introducido una nueva unidad, no necesito probarlo por separado.
VLAZ
2

Primero, algunas definiciones:

Una prueba de unidad prueba las unidades de forma aislada de otras unidades, pero lo que eso significa no está definido concretamente por ninguna fuente autorizada, así que definámoslo un poco mejor: si se cruzan los límites de E / S (si esa E / S es red, disco, pantalla o entrada UI), hay un lugar semi-objetivo donde podemos dibujar una línea. Si el código depende de E / S, está cruzando el límite de una unidad y, por lo tanto, tendrá que burlarse de la unidad responsable de esa E / S.

Según esa definición, no veo una razón convincente para burlarse de cosas como funciones puras, lo que significa que las pruebas unitarias se prestan a funciones puras o funciones sin efectos secundarios.

Si desea unir las unidades de prueba con efectos, las unidades responsables de los efectos deben ser burladas, pero quizás debería considerar una prueba de integración. Entonces, la respuesta corta es: "si necesita burlarse, pregúntese si lo que realmente necesita es una prueba de integración". Pero hay una respuesta mejor y más larga aquí, y la madriguera del conejo es mucho más profunda. Los simulacros pueden ser el olor de mi código favorito porque hay mucho que aprender de ellos.

Olores de código

Para esto, pasaremos a Wikipedia:

En la programación de computadoras, un olor a código es cualquier característica en el código fuente de un programa que posiblemente indique un problema más profundo.

Continúa más tarde ...

"Los olores son ciertas estructuras en el código que indican una violación de los principios fundamentales del diseño y tienen un impacto negativo en la calidad del diseño". Suryanarayana, Girish (noviembre de 2014). Refactorización para olores de diseño de software. Morgan Kaufmann. pags. 258.

Los olores de código generalmente no son errores; no son técnicamente incorrectos y no impiden que el programa funcione. En cambio, indican debilidades en el diseño que pueden ralentizar el desarrollo o aumentar el riesgo de errores o fallas en el futuro.

En otras palabras, no todos los olores de código son malos. En cambio, son indicaciones comunes de que algo podría no expresarse en su forma óptima, y ​​el olor puede indicar una oportunidad para mejorar el código en cuestión.

En el caso de burlarse, el olor indica que las unidades que parecen estar pidiendo burlas dependen de las unidades que se burlan. Puede ser una indicación de que no hemos descompuesto el problema en piezas con solución atómica, y eso podría indicar una falla de diseño en el software.

La esencia de todo desarrollo de software es el proceso de dividir un gran problema en partes más pequeñas e independientes (descomposición) y componer las soluciones juntas para formar una aplicación que resuelva el gran problema (composición).

Se requiere burlarse cuando las unidades utilizadas para dividir el gran problema en partes más pequeñas dependen unas de otras. Dicho de otra manera, se requiere burlarse cuando nuestras supuestas unidades de composición atómica no son realmente atómicas, y nuestra estrategia de descomposición no ha logrado descomponer el problema más grande en problemas más pequeños e independientes para ser resueltos.

Lo que hace que burlarse de un código huela no es que haya algo intrínsecamente malo con la burla, a veces es muy útil. Lo que lo convierte en un olor a código es que podría indicar una fuente problemática de acoplamiento en su aplicación. A veces, eliminar esa fuente de acoplamiento es mucho más productivo que escribir un simulacro.

Hay muchos tipos de acoplamiento, y algunos son mejores que otros. Comprender que los simulacros son un olor a código puede enseñarle a identificar y evitar los peores tipos al principio del ciclo de vida del diseño de la aplicación, antes de que el olor se convierta en algo peor.

Eric Elliott
fuente
1

La burla solo debe usarse como último recurso, incluso en pruebas unitarias.

Un método no es una unidad, e incluso una clase no es una unidad. Una unidad es cualquier separación lógica de código que tenga sentido, independientemente de cómo se llame. Un elemento importante de tener un código bien probado es poder refactorizar libremente, y parte de poder refactorizar libremente significa que no tiene que cambiar sus pruebas para hacerlo. Cuanto más te burles, más tendrás que cambiar tus pruebas cuando refactorices. Si considera el método como la unidad, debe cambiar sus pruebas cada vez que refactorice. Y si considera que la clase es la unidad, entonces debe cambiar sus pruebas cada vez que quiera dividir una clase en varias clases. Cuando tiene que refactorizar sus pruebas para refactorizar su código, hace que las personas elijan no refactorizar su código, que es lo peor que le puede pasar a un proyecto. Es esencial que pueda dividir una clase en varias clases sin tener que refactorizar sus pruebas, o terminará con clases de espagueti de 500 líneas de gran tamaño. Si está tratando métodos o clases como sus unidades con pruebas unitarias, probablemente no esté haciendo Programación Orientada a Objetos, sino algún tipo de programación funcional mutante con objetos.

Aislar tu código para una prueba unitaria no significa que te burles de todo lo que está fuera de él. Si lo hiciera, tendría que burlarse de la clase de matemáticas de su idioma, y ​​absolutamente nadie piensa que sea una buena idea. Las dependencias internas no deben tratarse de manera diferente a las dependencias externas. Confía en que están bien probados y funcionan como se supone que deben hacerlo. La única diferencia real es que si sus dependencias internas están rompiendo sus módulos, puede detener lo que está haciendo para solucionarlo en lugar de tener que publicar un problema en GitHub y cavar en una base de código que no entiende para solucionarlo. o espero lo mejor.

Aislar su código solo significa que trata sus dependencias internas como cuadros negros y no prueba las cosas que están sucediendo dentro de ellos. Si tiene el Módulo B que acepta entradas de 1, 2 o 3, y tiene el Módulo A, que lo llama, no tiene sus pruebas para que el Módulo A haga cada una de esas opciones, simplemente elija una y úsela. Significa que sus pruebas para el Módulo A deben evaluar las diferentes formas en que trata las respuestas del Módulo B, no las cosas que le pasa.

Entonces, si su controlador pasa un objeto complejo a una dependencia, y esa dependencia hace varias cosas posibles, tal vez guardándola en la base de datos y tal vez devolviendo una variedad de errores, pero todo lo que su controlador realmente hace es simplemente verificar para ver si regresa un error o no y pasa esa información, entonces todo lo que prueba en su controlador es una prueba de si devuelve un error y lo pasa y una prueba de si no devuelve un error. No prueba si algo se guardó en la base de datos o qué tipo de error es el error, porque eso sería una prueba de integración. No tiene que burlarse de la dependencia para hacer esto. Has aislado el código.

TiggerToo
fuente