unit testing - unitarias - ¿Por qué debería escribir una clase falsa y probarla en una unidad?
tdd (10)
Entiendo la necesidad de probar una clase que tiene lógica (por ejemplo, una que puede calcular descuentos), donde puedes probar la clase real .
Pero recién comencé a escribir pruebas unitarias para un proyecto que actuará como un repositorio (obtener objetos de una base de datos). Me encuentro escribiendo un repositorio "falso" que implementa una interfaz ISomethingRepository
. Utiliza un Dictionary<Guid, Something>
para el almacenamiento interno. Implementa los métodos Add(Something)
y GetById(Guid)
de la interfaz.
¿Por qué estoy escribiendo esto? Nada de lo que estoy escribiendo realmente será utilizado por el software cuando se implemente, ¿verdad? Realmente no veo el valor de este ejercicio.
También recibí el consejo de usar un objeto simulado que puedo configurar de antemano para cumplir con ciertas expectativas. Eso me parece aún más inútil: ¡por supuesto que la prueba tendrá éxito, me burlé / fingí para tener éxito! Y todavía no estoy seguro de que el software real funcione como debería cuando se conecta a la base de datos ...
confuso...
¿Puede alguien señalarme en la dirección correcta para ayudarme a entender esto?
¡Gracias!
El objetivo del objeto simulacro / trozo no debe probarse en lugar de la unidad que está tratando de probar, sino que le permite probar esa unidad sin necesidad de otras clases.
Básicamente es para poder evaluar las clases de una en una sin tener que probar todas las clases de las que también dependen.
No deberías estar probando la clase falsa.
Lo que normalmente haces es: creas clases simuladas para todas las clases con las que interactúa la clase con la que estás probando.
Digamos que estás probando una clase llamada Bicycle que toma los objetos constructor de las clases Wheel, Saddle, HandleBar, etc.
Y luego, dentro de la clase Bike, quiere probar su método GetWeight, que probablemente recorre cada parte y llama el peso de la propiedad / método y luego devuelve el total.
Que haces:
- usted escribe una clase simulada para cada parte (rueda, sillín, etc.) que simplemente implementa el bit de peso
- luego pasas esas clases simuladas a la Bicicleta
- prueba el método GetWeight en la clase de Bicicletas
De esta forma, puedes concentrarte en probar GetWeight en la clase Bicycle, de una manera que es independiente de otras clases (digamos que aún no se han implementado, no son deterministas, etc.)
No está probando su objeto simulado sino alguna otra clase que esté interactuando con él. Por lo tanto, podría probar, por ejemplo, que un controlador reenvía una llamada al método de guardar a su repositorio falso. Hay algo mal si estás "probando tus objetos falsos"
No pruebes la clase falsa. Pruebe la clase de producción utilizando la clase simulada.
El objetivo de la clase de soporte de prueba es tener algo que pueda predecir su comportamiento. Si necesita probar la clase de soporte de prueba para predecir su comportamiento, existe un problema.
En el artículo de la base de datos falsa que vinculó en un comentario, el autor necesita probar su base de datos falsa porque es su producto (al menos en el contexto del artículo).
Editar: términos actualizados para ser más consistente.
- Simulacro: creado por burlarse del marco
- Fake - creado manualmente, podría funcionar realmente.
- Soporte de prueba: simulacros, falsificaciones, apéndices y todo lo demás. No producción
Usted escribe una clase "falsa" llamada objeto Stub o Mock porque quiere probar una implementación de una manera simple sin probar la clase concreta real. El objetivo es simplificar las pruebas probando solo la interfaz (o clase abstracta).
En su ejemplo, está probando algo que tiene un dictionario. Puede que se llene de verdad en la base de datos o que tenga mucha lógica detrás. En su objeto "falso", puede simplificar todo manteniendo constantes todos los datos. De esta forma, solo prueba el comportamiento de la interfaz y no cómo se construye el objeto concreto.
¿Quién mira a los observadores?
Es interesante, por ejemplo, si la implementación simulada arroja excepciones específicas para los casos de esquina, para que sepa que las clases que usan o dependen de IRepositorySomething pueden manejar las excepciones que se lanzan en la vida real. Algunas de estas excepciones no se pueden generar fácilmente con una base de datos de prueba.
No prueba el objeto Mock con una prueba unitaria, pero lo usa para probar las clases que dependen de él.
En lugar de escribir una clase falsa usted mismo, puede usar una herramienta (como Rhino o Typemock ) para burlarse de ella. Es mucho más fácil que escribir todos los burla tú mismo. Y como dijeron otros, no es necesario probar el código falso, que no es código si usa la herramienta.
De hecho, he encontrado dos usos para las clases simuladas que usamos en las pruebas de implementación de repositorios.
El primero es probar los servicios que usan una implementación del equivalente "ISomethingRepository" que mencionas. Sin embargo, nuestras implementaciones de repositorio son creadas por una fábrica. Esto significa que escribimos pruebas contra el "ISomethingRepository", pero no contra el "MockSomethingRepository" directamente. Al realizar pruebas en la interfaz, podemos afirmar fácilmente que la cobertura del código para nuestras pruebas cubre el 100% de la interfaz. Las revisiones de código proporcionan una verificación simple de que los nuevos miembros de la interfaz son probados. Incluso si los desarrolladores se están ejecutando contra el simulacro de que la fábrica regresa, el servidor de compilación tiene una configuración diferente que prueba contra la implementación concreta que la fábrica devuelve dentro de las compilaciones nocturnas. Proporciona lo mejor de ambos mundos, en términos de cobertura de prueba y rendimiento local.
El segundo uso es uno que me sorprende que nadie más haya mencionado. Mi equipo es responsable del nivel medio. Nuestros desarrolladores web son responsables del front-end de los productos web. Al crear implementaciones de repositorio simuladas, no existe el obstáculo artificial de esperar a que la base de datos se modele e implemente antes de comenzar el trabajo de front-end. Se pueden crear vistas que se construirán a partir de la simulación para proporcionar una cantidad mínima de datos "reales" para cumplir con las expectativas de los desarrolladores web. Por ejemplo, se pueden proporcionar datos para contener datos de cadena de longitud mínima y máxima para verificar que ni rompan su implementación, etc.
Como las fábricas que utilizamos están conectadas con respecto a qué "ISomethingRepository" devolver, tenemos configuraciones de prueba locales, configuraciones de prueba de compilación, configuraciones de producción, etc. Intentamos asegurarnos de que ningún equipo en el proyecto tenga tiempos de espera irrazonables debido a el tiempo de implementación de otro equipo La mayor cantidad de tiempo de espera todavía es proporcionado por el equipo de desarrollo, pero podemos generar nuestros objetos de dominio, repositorios y servicios a un ritmo más rápido que el desarrollo de front-end.
Por supuesto, YMMV. ;-)
Eche un vistazo al siguiente artículo para una buena explicación de esto:
http://msdn.microsoft.com/en-us/magazine/cc163358.aspx
Básicamente, si escribe un objeto falso y resulta ser bastante complejo, a veces vale la pena probar la unidad para asegurarse de que funciona como se espera.
Dado que un repositorio puede ser complejo, a menudo es lógico escribir pruebas de unidades para él.
En general, no es necesario ejecutar pruebas de unidades clásicas en la capa de acceso a datos. Tal vez pueda escribir pruebas integradas de unidad de estilo para sus clases de acceso a datos, es decir, prueba de integración (= integración de su código de capa de acceso a datos con el DB) usando las características de los marcos de prueba de unidades.
Por ejemplo, en un proyecto de Spring puede usar Spring Testcontext para iniciar su contexto de Spring dentro de una prueba unitaria, y luego conectarse a una base de datos real y probar que las consultas arrojan resultados correctos. Probablemente necesites una base de datos propia para pruebas unitarias, o quizás puedas conectarlas con un DB de desarrollador.