Así que hoy tuve una conversación con mi compañero de equipo sobre las pruebas unitarias. Todo comenzó cuando me preguntó "oye, ¿dónde están las pruebas para esa clase, solo veo una?". Toda la clase era un gerente (o un servicio si prefieres llamarlo así) y casi todos los métodos simplemente delegaban 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 un repetitivo útil 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 probarlo unitariamente (creo que vale la pena mencionar que hice una prueba completa del DAO). Sus principales argumentos 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 prueba. implementación y agregue nueva (o más como "cualquier") lógica (en cuyo caso supongo que alguien debería simplemente probar esa lógica ).
Sin embargo, esto me hizo pensar. ¿Debemos esforzarnos por obtener el mayor porcentaje de cobertura de prueba? ¿O es simplemente un arte por el arte entonces? Simplemente no veo ninguna razón detrás de probar cosas como:
- captadores y establecedores (a menos que realmente tengan algo de lógica en ellos)
- "código repetitivo
Obviamente, una prueba para tal método (con simulacros) me llevaría menos de un minuto, pero supongo que todavía es tiempo perdido y un milisegundo más para cada CI.
¿Hay alguna razón racional / no "inflamable" de por qué uno debe probar cada línea de código (o tantas como pueda)?
fuente
Respuestas:
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 / setters triviales y las frases sencillas como la suya anterior no valen la pena. Pero, de nuevo, paso la mayor parte de mi tiempo escribiendo pruebas unitarias para código heredado, solo soñando con un buen proyecto TDD greenfield ... En tales proyectos, las reglas son diferentes. Con el código heredado, el objetivo principal es cubrir la mayor cantidad de 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 uno es pedante con respecto a la terminología. Y cuando tiene dificultades para aumentar la cobertura general del código del 0%, o simplemente logra superarlo en un 25%, los captadores y establecedores de pruebas unitarias son la menor de sus preocupaciones.
OTOH en un proyecto de TDD totalmente nuevo, puede ser más práctico escribir pruebas incluso para tales métodos. Especialmente porque ya ha escrito la prueba antes de tener la oportunidad de comenzar a preguntarse "¿vale esta una 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 de ninguna manera.
fuente
Existen pocos tipos de pruebas unitarias:
Si primero escribiera su prueba, tendría más sentido, ya que esperaría llamar a una capa de acceso a datos. La prueba fallaría inicialmente. Luego escribiría el código de producción para hacer pasar la prueba.
Idealmente, debería probar el código lógico, pero las interacciones (objetos que llaman a otros objetos) son igualmente importantes. En tu caso, lo haría
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 permanezca igual, entonces consideraría llamar a la capa de acceso a datos directamente desde el consumidor. Haría esto solo si el resto del equipo está en la misma página. No desea enviar un mensaje incorrecto al equipo diciendo "Hola chicos, está bien ignorar la capa de dominio, solo llame a la capa de acceso a datos directamente".
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 ciegamente todo. Establecería los puntos calientes (componentes con alta complejidad y alto riesgo de rotura). Luego me concentraría en estos componentes. No tiene sentido tener una base de código donde el 90% de la base de código es bastante sencilla y está cubierta por pruebas unitarias, cuando el 10% restante representa la lógica central del sistema y no están cubiertas 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. La cobertura del código debe ser un producto derivado de un buen conjunto de pruebas unitarias. Por ejemplo, puede escribir una prueba que caminará por el árbol y le dará una cobertura del 100% de este método, o puede escribir tres pruebas unitarias que también le darán una cobertura del 100%. La diferencia es que al escribir tres pruebas se prueban casos extremos, en lugar de simplemente caminar por el árbol.
fuente
Aquí hay una buena manera de pensar en la calidad de su software:
Para las funciones triviales y repetitivas, puede confiar en que la verificación de tipos haga su trabajo, y para el resto, necesita casos de prueba.
fuente
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 unitarias. El nivel de Complejidad Ciclomática de McCabe debería ser más de 1. Otra palabra debería haber una declaración mínima de 1 bloque.
fuente
Un rotundo SÍ con TDD (y con algunas excepciones)
Muy controvertido, pero diría que a cualquiera 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
TDD es a menudo citado por tener los principales beneficios.
Separar la responsabilidad de la implementación
Como programadores, es terriblemente tentador pensar en los atributos como algo de importancia y captadores y establecedores como una especie de sobrecarga.
Pero los atributos son un detalle de implementación, mientras que los establecedores y captadores son la interfaz contractual que realmente hace que los programas funcionen.
Es mucho más importante deletrear que un objeto debe:
y
entonces cómo se almacena realmente este estado (para el cual un atributo es la forma más común, pero no la única).
Una prueba como
es importante para la parte de documentación de TDD.
El hecho de que la implementación final es trivial (atributo) y no conlleva 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 del sistema es la falta de ingeniería de ida y vuelta 1 : el proceso de desarrollo de un sistema se fragmenta en subprocesos desarticulados cuyos artefactos (documentación, código) a menudo son 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 de documentación de TDD que asegura que las especificaciones del sistema y su código sean siempre consistentes.
Diseñe primero, implemente después
Dentro de TDD, primero escribimos la prueba de aceptación fallida, solo luego escribimos el código que les permitió pasar.
Dentro del BDD de nivel superior, primero escribimos escenarios y 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 implemente el código que la hace pasar.
Entonces pregúntate a ti mismo:
Como getters y setters 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 escribe el código primero, la respuesta puede no ser tan clara.
Excepciones
Hay algunas excepciones obvias a esta regla: funciones que son detalles de implementación claros y claramente no forman parte del diseño del sistema.
Por ejemplo, a el método local 'B ()':
O la función privada
square()
aquí:O cualquier otra función que no sea parte de una
public
interfaz que necesite ortografía en el diseño del componente del sistema.fuente
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 de desarrollo / costo se alinean: pasa menos tiempo escribiendo pruebas que reparando defectos.
Pero más allá de ese punto, no lo hacen. No es tan difícil, digamos, un error reportado por desarrollador por mes. Reducir a la mitad a uno por dos meses solo libera un presupuesto de quizás un día o dos, y esa cantidad de pruebas adicionales probablemente no reducirá a la mitad su tasa de defectos. Entonces ya no es un simple ganar / ganar; debe justificarlo en función del costo del defecto para el cliente.
Este costo variará (y, si desea ser malvado, también lo hará su capacidad para hacer que se le apliquen dichos costos, ya sea a través del mercado o de una demanda). No quieres ser malvado, así que cuentas esos costos por completo; a veces algunas pruebas aún globalmente hacen que el mundo sea más pobre por su existencia.
En resumen, si intenta aplicar ciegamente los mismos estándares a un sitio web interno que el software de vuelo de un avión de pasajeros, terminará fuera del negocio o en la cárcel.
fuente
Su respuesta a esto depende de su filosofía (¿cree que es Chicago vs Londres? Estoy seguro de que alguien lo buscará). El jurado todavía está fuera de esto en el enfoque más efectivo en tiempo (porque, después de todo, ese es el mayor impulsor de este tiempo menos dedicado a las soluciones).
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 probar 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, regrese y vea cuál se adapta mejor a sus necesidades.
fuente
Es una pregunta difícil.
Hablando estrictamente, diría que no es necesario. Es mejor escribir pruebas de unidad de estilo BDD y de nivel de sistema que aseguren que los requisitos comerciales funcionen según lo previsto en escenarios positivos y negativos.
Dicho esto, si su método no está cubierto por estos casos de prueba, debe preguntarse por qué existe en primer lugar y si es necesario, o si hay requisitos ocultos en el código que no se reflejan en su documentación o historias de usuario que debe codificarse en un caso de prueba de estilo BDD.
Personalmente, me gusta mantener la cobertura por línea en torno al 85-95% y los registros de entrada a la línea principal para asegurar que la cobertura de prueba de unidad existente por línea alcance este nivel para todos los archivos de código y que no se descubran archivos.
Suponiendo que se sigan las mejores prácticas de prueba, esto ofrece una gran cobertura sin obligar a los desarrolladores a perder tiempo tratando de descubrir cómo obtener cobertura adicional en código difícil de ejercer o código trivial simplemente por el bien de la cobertura.
fuente
El problema es la pregunta en sí, no necesita probar todos los "métodos" o todas las "clases" que necesita para probar todas las características de sus sistemas.
Su pensamiento clave en términos de características / comportamientos en lugar de pensar en términos de métodos y clases. Por supuesto, un método está aquí para proporcionar soporte para una o más funciones, al final se prueba todo su código, al menos todo el código importa en su base de código.
En su escenario, probablemente esta clase de "administrador" es 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 unidad prueba porque esta clase no tiene ninguna lógica comercial relevante. Probablemente necesite esta clase para que alguna característica funcione, la prueba para esta característica cubre esta clase, de esta manera puede refactorizar esta clase y hacer una prueba que verifique que lo que importa, sus características, todavía funciona después de la refactorización.
Piense en características / comportamientos no en clases de métodos, no puedo repetir esto suficientes veces.
fuente
Sí, idealmente al 100%, pero algunas cosas no son comprobables por unidad.
Getters / Setters son estúpidos , simplemente no los uses. En su lugar, coloque su variable miembro en la sección pública.
Obtenga un código común y pruébelo en la unidad. Eso debería ser tan simple como eso.
Al no hacerlo, es posible que te pierdas algunos errores muy obvios. Las pruebas unitarias son como una red segura para detectar ciertos tipos de errores, y debe usarla tanto como sea posible.
Y lo último: estoy en un proyecto donde la gente no quería perder el tiempo escribiendo pruebas unitarias para un "código simple", pero luego decidieron no escribir nada. Al final, partes del código se convirtieron en una gran bola de barro .
fuente