¿Debemos probar todos nuestros métodos?

54

Así que hoy hablé con mi compañero de equipo sobre las pruebas de unidad. Todo comenzó cuando me preguntó "hey, ¿dónde están las pruebas para esa clase, solo veo una?". Toda la clase era un administrador (o un servicio, si prefieres llamarlo así) y casi todos los métodos eran simplemente delegar cosas a un DAO, por lo que era similar a:

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

Un tipo de repetitivo sin lógica (o, al menos, no considero una delegación tan simple como lógica), pero es útil como repetitivo en la mayoría de los casos (separación de capas, etc.). Y tuvimos una discusión bastante larga sobre si debería o no realizar una prueba unitaria (creo que vale la pena mencionar que hice una prueba completa de la DAO). Sus argumentos principales son que no era TDD (obviamente) y que alguien podría querer ver la prueba para verificar qué hace este método (no sé cómo podría ser más obvio) o que en el futuro alguien podría querer cambiar la Implementar y agregarle una nueva (o más como "cualquier") lógica (en cuyo caso supongo que alguien debería simplemente probar esa lógica ).

Esto me hizo pensar, sin embargo. ¿Debemos esforzarnos por obtener el porcentaje de cobertura más alto? ¿O es simplemente un arte por el arte entonces? Simplemente no veo ninguna razón para probar cosas como:

  • captadores y definidores (a menos que realmente tengan alguna lógica en ellos)
  • código "repetitivo"

Obviamente, una prueba para este método (con simulacros) me tomaría menos de un minuto, pero supongo que todavía es una pérdida de tiempo y un milisegundo más por cada CI.

¿Hay alguna razón racional / no "inflamable" por la cual uno debería probar cada línea de código (o tantas como pueda)?

    
pregunta Zenzen 19.01.2012 - 21:40

10 respuestas

44

Voy por la regla de oro de Kent Beck:

Prueba todo lo que pueda romperse.

Por supuesto, eso es subjetivo hasta cierto punto. Para mí, los captadores / instaladores triviales y los de una sola línea como el suyo anterior no valen la pena. Pero, de nuevo, paso la mayor parte de mi tiempo escribiendo pruebas unitarias para el código heredado, solo soñando con un buen proyecto de TDD greenfield ... En tales proyectos, las reglas son diferentes. Con el código heredado, el objetivo principal es cubrir todo el terreno con el menor esfuerzo posible, por lo que las pruebas unitarias tienden a ser de mayor nivel y más complejas, más como pruebas de integración si se trata de una terminología de la terminología. Y cuando está luchando por obtener una cobertura general del código del 0%, o solo logró aumentarla en más del 25%, los evaluadores de pruebas de unidad son la menor de sus preocupaciones.

OTOH en un proyecto de TDD greenfield, puede ser más práctico escribir pruebas incluso para tales métodos. Especialmente porque ya has escrito la prueba antes de que tengas la oportunidad de comenzar a preguntarte "¿vale esta línea una prueba dedicada?". Y al menos estas pruebas son triviales de escribir y rápidas de ejecutar, por lo que no es un gran problema en cualquier caso.

    
respondido por el Péter Török 19.01.2012 - 21:49
12

Hay pocos tipos de pruebas unitarias:

  • basado en el estado. Actúas y luego aseveras contra el estado del objeto. P.ej. Hago un deposito Luego verifico si el saldo ha aumentado.
  • Valor de retorno basado. Usted actúa y afirma contra el valor de retorno.
  • Basado en la interacción. Usted verifica que su objeto llamó a otro objeto. Esto parece ser lo que estás haciendo en tu ejemplo.

Si escribiera su prueba primero, tendría más sentido, ya que esperaría llamar una capa de acceso a datos. La prueba fallaría inicialmente. A continuación, escribiría el código de producción para hacer que la prueba pase.

Lo ideal es que estés probando el código lógico, pero las interacciones (objetos que llaman a otros objetos) son igualmente importantes. En tu caso, me gustaría

  • Compruebe que he llamado a la capa de acceso a datos con el parámetro exacto que se ha pasado.
  • Comprueba que se ha llamado solo una vez.
  • Comprueba que devuelvo exactamente lo que me ha dado la capa de acceso a datos. De lo contrario, también podría devolver nulo.

Actualmente no hay lógica allí, pero no siempre será así.

Sin embargo, si está seguro de que no habrá lógica en este método y es probable que se mantenga igual, entonces consideraría llamar la capa de acceso a los datos directamente del consumidor. Haría esto solo si el resto del equipo está en la misma página. No desea enviar un mensaje equivocado al equipo diciendo "Hola chicos, está bien ignorar la capa de dominio, solo llame directamente a la capa de acceso a datos".

También me concentraría en probar otros componentes si hubiera una prueba de integración para este método. Sin embargo, todavía no he visto una empresa con pruebas de integración sólidas.

Habiendo dicho todo esto, no probaría todo a ciegas. Yo establecería los puntos calientes (componentes con alta complejidad y alto riesgo de rotura). Entonces me concentraría en estos componentes. No tiene sentido tener una base de código en la que el 90% de la base de código sea bastante sencilla y esté cubierta por pruebas unitarias, cuando el 10% restante represente la lógica central del sistema y no esté cubierta por pruebas unitarias debido a su complejidad.

Finalmente, ¿cuál es el beneficio de probar este método? ¿Cuáles son las implicaciones si esto no funciona? ¿Son catastróficos? No se esfuerce por obtener una alta cobertura de código. El código de cobertura debe ser un producto de un buen conjunto de pruebas unitarias. Por ejemplo, puede escribir una prueba que recorrerá el árbol y le dará una cobertura del 100% de este método, o puede escribir tres pruebas de unidad que también le darán una cobertura del 100%. La diferencia es que al escribir tres pruebas, se evalúan casos de borde, en lugar de simplemente caminar por el árbol.

    
respondido por el CodeART 28.03.2014 - 00:43
8

Esta es una buena manera de pensar acerca de la calidad de su software:

  1. la verificación de tipos está manejando parte del problema.
  2. las pruebas se encargarán del resto

Para las funciones básicas y triviales, puede confiar en que la verificación de tipos haga su trabajo y, por lo demás, necesita casos de prueba.

    
respondido por el tp1 19.01.2012 - 22:19
6

En mi opinión, la complejidad ciclomática es un parámetro. Si un método no es lo suficientemente complejo (como getters y setters). No se necesitan pruebas de unidad. El nivel de Complejidad Ciclomática de McCabe debería ser más de 1. Otra palabra debe ser un mínimo de una declaración de bloque.

    
respondido por el Fırat KÜÇÜK 20.10.2012 - 22:03
1

Cuando se enfrente a una pregunta filosófica, vuelva a los requisitos de manejo.

¿Su objetivo es producir software razonablemente confiable a un costo competitivo?

¿O es para producir software de la mayor confiabilidad posible casi sin importar el costo?

Hasta cierto punto, los dos objetivos de calidad y velocidad / velocidad de desarrollo se alinean: usted pasa menos tiempo escribiendo pruebas que reparando defectos.

Pero más allá de ese punto, no lo hacen. No es tan difícil llegar a, digamos, un error reportado por desarrollador por mes. Reducir a la mitad a uno por dos meses solo libera un presupuesto de tal vez uno o dos días, y que muchas pruebas adicionales probablemente no reduzcan a la mitad la tasa de defectos. Así que ya no es un simple ganar / ganar; Debe justificarlo en función del costo del defecto para el cliente.

Este costo variará (y, si usted quiere ser malo, también lo hará su capacidad de imponerle esos costos, ya sea a través del mercado o una demanda). No quieres ser malvado, así que cuentas esos costos de nuevo en su totalidad; A veces, algunas pruebas todavía globalmente hacen al mundo más pobre por su existencia.

En resumen, si intenta aplicar ciegamente los mismos estándares a un sitio web interno como el software de vuelo de un avión de pasajeros, terminará fuera del negocio o en la cárcel.

    
respondido por el soru 28.03.2014 - 00:11
1

Un sí rotundo con TDD (y con algunas excepciones)

Está bien, es controversial, pero yo diría que a cualquier persona que responda "no" a esta pregunta le falta un concepto fundamental de TDD.

Para mí, la respuesta es un rotundo sí si sigues TDD. Si no lo eres, entonces no es una respuesta plausible.

El DDD en TDD

A menudo se cita que TDD tiene sus principales beneficios.

  • Defensa
    • Asegurarse de que el código puede cambiar pero no es su comportamiento .
    • Esto permite la práctica tan importante de refactorización .
    • Ganaste este TDD o no.
  • Diseño
    • Usted especifica qué debe hacer algo, cómo debe comportarse antes de implementarlo .
    • Esto suele significar más decisiones de implementación .
  • Documentación
    • El conjunto de pruebas debe servir como documentación de especificación (requisitos).
    • El uso de pruebas para tal fin significa que la documentación y la implementación siempre se encuentran en un estado coherente; un cambio en uno significa un cambio en otro. Compare con los requisitos de mantenimiento y el diseño en un documento de Word separado.

Separar la responsabilidad de la implementación

Como programadores, es terriblemente tentador pensar que los atributos son algo de importancia y que los captadores y definidores son una especie de sobrecarga.

Pero los atributos son un detalle de implementación, mientras que los definidores y captadores son la interfaz contractual que realmente hace que los programas funcionen.

Es mucho más importante deletrear que un objeto debe:

  

Permitir a sus clientes cambiar su estado

y

  

Permitir a sus clientes consultar su estado

luego, cómo se almacena realmente este estado (para el cual un atributo es el más común, pero no la única forma).

Una prueba como

(The Painter class) should store the provided colour

es importante para la documentación de TDD.

El hecho de que la implementación final sea trivial (atributo) y no conlleve ningún beneficio de defensa debe ser desconocido para usted cuando escribe la prueba.

La falta de ingeniería de ida y vuelta ...

Uno de los problemas clave en el mundo del desarrollo de sistemas es la falta de round-trip engineering 1 : el proceso de desarrollo de un sistema se fragmenta en subprocesos inconexos. los artefactos de los cuales (documentación, código) son a menudo inconsistentes.

1 Brodie, Michael L. "John Mylopoulos: cosiendo semillas del modelado conceptual". Modelado Conceptual: Fundamentos y Aplicaciones. Springer Berlin Heidelberg, 2009. 1-9.

... y cómo lo resuelve TDD

Es la parte documentación de TDD la que garantiza que las especificaciones del sistema y su código sean siempre coherentes.

Diseña primero, implementa más tarde

En TDD, primero escribimos la prueba de aceptación fallida, luego escribimos el código que los dejó pasar.

Dentro de la BDD de nivel superior, escribimos los escenarios primero, luego los hacemos pasar.

¿Por qué debería excluir setters y getter?

En teoría, es perfectamente posible dentro de TDD que una persona escriba la prueba y otra que implemente el código que la haga pasar.

Entonces pregúntate:

  

En caso de que la persona que escribe las pruebas para una clase mencione a captadores y colocadores.

Dado que los captadores y definidores son una interfaz pública para una clase, la respuesta es obviamente sí , o no habrá forma de establecer o consultar el estado de un objeto.

Obviamente, si escribes el código primero, la respuesta puede no ser tan clara.

Excepciones

Hay algunas excepciones obvias a esta regla: funciones que son detalles claros de la implementación y que claramente no forman parte del diseño del sistema.

Por ejemplo, un método local 'B ()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

O la función privada square() aquí:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

O cualquier otra función que no sea parte de una interfaz public que necesite ortografía en el diseño del componente del sistema.

    
respondido por el Izhaki 18.11.2015 - 15:18
0

Su respuesta a esto depende de su filosofía (¿cree que es Chicago vs Londres? Estoy seguro de que alguien lo buscará). El jurado aún está deliberando sobre el enfoque más efectivo en el tiempo (porque, después de todo, ese es el mayor impulsor de este: menos tiempo dedicado a las correcciones).

Algunos enfoques dicen probar solo la interfaz pública, otros dicen probar el orden de cada llamada de función en cada función. Se han librado muchas guerras santas. Mi consejo es que pruebes ambos enfoques. Elija una unidad de código y hágalo como X y otra como Y. Después de unos meses de prueba e integración, vuelva atrás y vea cuál se adapta mejor a sus necesidades.

    
respondido por el anon 21.01.2012 - 02:26
0

Es una pregunta difícil.

Estrictamente hablando, diría que no es necesario. Es mejor que escriba pruebas de unidad de estilo BDD y pruebas de nivel de sistema que aseguren que los requisitos comerciales funcionen como se pretende en escenarios positivos y negativos.

Dicho esto, si su método no está cubierto por estos casos de prueba, entonces tiene que preguntarse por qué existe en primer lugar y si es necesario, o si existen requisitos ocultos en el código que no se reflejan en su documentación o historias de usuario que deben codificarse en un caso de prueba de estilo BDD.

Personalmente, me gusta mantener la cobertura por línea en aproximadamente el 85-95% y las verificaciones de la puerta a la línea principal para asegurar que la cobertura de prueba unitaria existente por línea llegue a este nivel para todos los archivos de código y que no haya ningún archivo descubierto.

Suponiendo que se sigan las mejores prácticas de prueba, esto brinda mucha cobertura sin obligar a los desarrolladores a perder tiempo tratando de averiguar cómo obtener cobertura adicional en código difícil de ejercer o código trivial simplemente por el bien de la cobertura.

    
respondido por el Keith Brings 20.01.2012 - 20:50
-1

El problema es la pregunta en sí misma, no es necesario probar todos los "métodos" o todas las "clases" que necesita para probar todas las características de sus sistemas.

Su pensamiento clave de términos de características / comportamientos en lugar de pensar en términos de métodos y clases. Por supuesto, aquí hay un método para proporcionar soporte para una o más funciones; al final, todo su código se ha probado, al menos todo el código importa en su base de código.

En su escenario, probablemente esta clase de "administrador" sea redundante o innecesaria (como todas las clases con un nombre que contiene la palabra "administrador"), o tal vez no, pero parece un detalle de implementación, probablemente esta clase no merece una prueba de unidad porque esta clase no tiene ninguna lógica de negocios relevante. Probablemente necesite esta clase para que funcione alguna característica, la prueba para esta característica cubre esta clase, de esta manera puede refactorizar esta clase y hacer pruebas que verifiquen que lo que importa, sus características, aún funciona después del refactor.

Piensa en características / comportamientos no en clases de métodos, no puedo repetir esto lo suficiente.

    
respondido por el AlfredoCasado 28.03.2014 - 03:18

Lea otras preguntas en las etiquetas