2009-08-03 11 views
8

Suponga que tiene un método:prueba Unidad de métodos muy triviales (sí o no)

public void Save(Entity data) 
{ 
    this.repositoryIocInstance.EntitySave(data); 
} 

escribirías una prueba de unidad en absoluto?

public void TestSave() 
{ 
    // arrange 
    Mock<EntityRepository> repo = new Mock<EntityRepository>(); 
    repo.Setup(m => m.EntitySave(It.IsAny<Entity>()); 

    // act 
    MyClass c = new MyClass(repo.Object); 
    c.Save(new Entity()); 

    // assert 
    repo.Verify(m => EntitySave(It.IsAny<Entity>()), Times.Once()); 
} 

porque más adelante si lo hace la implementación del método de cambio para hacer más "complejo" cosas como:

public void Save(Entity data) 
{ 
    if (this.repositoryIocInstance.Exists(data)) 
    { 
     this.repositoryIocInstance.Update(data); 
    } 
    else 
    { 
     this.repositoryIocInstance.Create(data); 
    } 
} 

... su unidad de prueba fracasaría pero probablemente no romper su aplicación ...

pregunta

¿Debo siquiera se molestan creación de pruebas unitarias en los métodos que no tiene ningún tipo de devolución * o ** no cambia nada fuera de la simulación interna?

Respuesta

2

Es cierto que su prueba depende de su implementación, que es algo que debe evitar (aunque a veces no es así de simple) y no es necesariamente malo. Pero se espera que este tipo de pruebas se rompa aunque su cambio no rompa el código.

Usted podría tener muchos enfoques para esto:

  • Crear una prueba de que realmente va a la base de datos y comprobar si el estado se cambió como se esperaba (que no ser una pruebaunidad más)
  • Cree un objeto de prueba que falsifique una base de datos y realice operaciones en memoria (otra implementación para su repositorioIocInstance) y verifique que el estado se modificó como se esperaba. Los cambios en la interfaz del repositorio también implicarían cambios en este objeto. Pero sus interfaces no deberían estar cambiando mucho, ¿verdad?
  • Ver todo esto como demasiado caro, y usar su enfoque, que incidan sobre innecesariamente romper pruebas más tarde (pero una vez que la oportunidad es bajo, es bien tomar el riesgo)
+0

Su respuesta es, hasta ahora, la más cercana a lo que quería saber. Pero la segunda viñeta sería en este caso el repositorio de pruebas y no el método en sí, ¿no es así? –

+0

No lo creo. Este podría ser un objeto que contiene una lista (o tal vez un conjunto, depende de lo que desee) y el "EntitySave" simplemente agregaría la entidad (si existe? No sé cómo se supone que debe funcionar). No unirse a varias tablas u otras cosas complicadas que su repositorio podría hacer. –

+0

Su implementación de objetos sería más costosa, como se mencionó, pero en mi humilde opinión es la mejor manera de ver si la llamada se realizó de manera consistente. –

1

La regla general es que usted prueba todas las cosas, que probablemente podrían romperse. Si está seguro de que el método es lo suficientemente simple (y se mantiene lo suficientemente simple) como para no ser un problema, déjelo salir con pruebas.

Lo segundo es que debe probar el contrato del método, no la implementación. Si la prueba falla luego de un cambio, pero no la aplicación, entonces su prueba no es la correcta. La prueba debe cubrir casos que son importantes para su aplicación. Esto debería garantizar que cada cambio al método que no rompa la aplicación tampoco falle la prueba.

+0

En este caso, este método no necesita una prueba de unidad, ya que no se rompe la aplicación, la prueba se rompe. Estoy probando la implementación en este caso, no el contrato que no está presente en el alcance del método. –

1

Un método que no devuelve ningún resultado todavía cambia el estado de su aplicación. La prueba de su unidad, en este caso, debería probar si el nuevo estado es el deseado.

+0

El nuevo estado en la prueba unitaria depende únicamente del simulacro ... Es por eso que tengo dudas. Si todo lo que hace mi método es col en el repositorio y se burla de él en una prueba, tendrá un resultado predecible basado en una situación falsa y no en la vida real. SIEMPRE pasará. –

+0

No debería probar el simulacro, sino la operación que interactúa con el simulacro. Digamos que tienes un objeto 'X' con un método' M() 'que llama al repositorio. Desea probar que la llamada al repositorio en realidad está hecha, por lo que escribe un simulacro del depósito y luego llama al método 'X.M()'. La prueba debería desencadenar un cambio en el simulacro, lo que demostraría que su método 'M()' funciona según sea necesario. Cuando tiene una implementación real del repositorio, luego prueba esa implementación (no el simulacro); entonces estaría probando el repositorio. –

+0

@jeyoung: pero esa es una prueba de unidad separada. Es una prueba unitaria del repositorio, no de la clase que llama al repositorio según el código anterior ... Pero sí. el problema es que no pruebo DAL ya que es código generado. Y se espera que funcione como debería. Y no pretendo probarlo en una unidad. –

1

"la prueba de la unidad fallaría pero probablemente no rompería su aplicación"

Esto es - en realidad - muy importante saber. Puede parecer molesto y trivial, pero cuando alguien más comienza a mantener su código, es posible que haya hecho un cambio realmente malo para Guardar y (de manera improbable) quebrar la aplicación.

El truco está en priorizar.

Pruebe primero las cosas importantes. Cuando las cosas son lentas, agregue pruebas para cosas triviales.

+0

Puede que tenga razón en la última oración (+1 para eso). El principal problema aquí es que la prueba se volverá inválida, no el código, que evolucionará a una versión mejor. –

6

No olvide que las pruebas unitarias no se limitan a probar el código. Se trata de permitirle determinar cuando el comportamiento cambia.

Así que es posible que tenga algo que es trivial. Sin embargo, su implementación cambia y puede tener un efecto secundario. Desea que su suite de pruebas de regresión lo cuente.

p. Ej. A menudo la gente dice que no debes probar setters/getters ya que son triviales. No estoy de acuerdo, no porque sean métodos complicados, sino que alguien puede cambiarlos inadvertidamente por ignorancia, escenarios de dedos gordos, etc.

Teniendo en cuenta todo lo que acabo de decir, definitivamente implementaría pruebas para lo anterior (a través de la burla , y/o tal vez vale la pena diseñar sus clases con la capacidad de prueba en mente y hacer que denuncien el estado, etc.)

+0

Estoy de acuerdo con lo que dijo sobre las pruebas de regresión. Y también diría que las pruebas de integración también mostrarían algunas partes que no funcionan. Pero dado que no creo pruebas de unidad para mi DAL (se genera), supongo que no debería crear pruebas de unidad a menos que cambie mi método para tener algún tipo de devolución (como bool) para informar el estado. –

+0

Diría que la 'regresión' abarca tanto la unidad como la integración, indica que se ejecutan continuamente (por ejemplo, en cada compilación).En cuanto a probar el código generado automáticamente, creo que es una decisión que podría tomar cualquier forma, dependiendo de su fe en el proceso de generación, cuán complejo es, etc. –

1

Cuando hay ISN' En una afirmación en un método, esencialmente estás afirmando que las excepciones no se lanzan.

También estoy luchando con la cuestión de cómo probar el vacío público myMethod(). Supongo que si decide agregar un valor de retorno para la capacidad de prueba, el valor de retorno debe representar todos los hechos importantes necesarios para ver qué cambió sobre el estado de la aplicación.

public void myMethod() 

convierte

public ComplexObject myMethod() { 
DoLotsOfSideEffects() 
return new ComplexObject { rows changed, primary key, value of each column, etc }; 
} 

y no

public bool myMethod() 
    DoLotsOfSideEffects() 
    return true; 
+0

Los cambios en el estado de un objeto se reflejan eventualmente externamente, ya sea por el resultado diferente de un comportamiento del objeto o por el resultado diferente que devuelve cuando se consulta su conocimiento. También es en estos puntos que el estado del objeto se vuelve relevante. Por lo tanto, la forma de probar los métodos que cambian el estado interno es probar estos cambios en el comportamiento o el conocimiento en los puntos donde se vuelven visibles. –

+0

@jeyoung: Si te entiendo correctamente, este tipo de pruebas van más allá de las pruebas unitarias ... Van más allá de la unidad que se está probando, ¿no? –

+0

@jeyoung. Usted dice que para probar "public void saveRow()", debe invocar "DataRow loadRow()" pública? – MatthewMartin

2

La respuesta corta a tu pregunta es: Sí , debería métodos de prueba por el estilo.

Asumo que es importante que el método Save realidad guarda los datos. Si no escribe una prueba unitaria para esto, ¿cómo lo sabe?

Alguien más puede aparecer y eliminar esa línea de código que invoca el método EntitySave, y ninguna de las pruebas de unidad fallará. Más adelante, se preguntará por qué los artículos nunca se conservan ...

En su método, podría decir que cualquier persona que borre esa línea solo lo haría si tienen intenciones malignas, pero la cosa es: las cosas simples no lo hacen Para ser más simple, es mejor escribir las pruebas unitarias antes de que las cosas se compliquen.

Es no detalle de la implementación que el método Save invoca EntitySave en el repositorio; es parte del comportamiento esperado, y una parte bastante crucial, si se me permite decirlo. Desea asegurarse de que los datos se estén guardando realmente.

El hecho de que un método no devuelva un valor no significa que no valga la pena probarlo.En general, si observa una buena separación de comando/consulta (CQS), se espera que cualquier método de vacío cambie el estado de algo.

A veces ese algo es la clase en sí, pero otras veces, puede ser el estado de otra cosa. En este caso, cambia el estado del Repositorio, y eso es lo que debería probar.

Esto se llama prueba salidas Indirecta, en lugar de las salidas directas (valores de retorno) más normales.

El truco está en escribir pruebas unitarias para que no se rompan con demasiada frecuencia. Al usar Mocks, es fácil escribir accidentalmente Pruebas sobre especificadas, por lo que la mayoría de los simuladores dinámicos (como Moq) se configuran de manera predeterminada en el modo Stub, donde no importa cuántas veces invoque un método determinado.

Todo esto, y mucho más, se explica en el excelente xUnit Test Patterns.

+0

No creo que tenga la idea de las pruebas de Unidad y la burla. Si un método nulo solo cambia el estado interno de algún otro objeto y usted escribe una prueba unitaria para él, estará escribiendo un simulacro para ese objeto interno. Tendrás que escribir su comportamiento. Entonces su método no cambiará ningún estado. Solo usará el comportamiento simulado preprogramado. Apenas verificar las llamadas puede ser problemático de una manera que expliqué en mi pregunta. Lo que significa que una prueba no es válida, no el código que la unidad prueba. –

+0

@Robert Koritnik: Creo que es la primera vez que alguien afirma que no entiendo las pruebas unitarias y los Dobles de prueba ... Estoy de acuerdo en que si un método * solo * cambia * estado interno *, no hay razón para escribir una unidad probarlo, pero, una vez más, ¿qué motivo habría para tener dicho código? Pude haber sacado conclusiones precipitadas de su pregunta, pero asumí que la variable miembro en cuestión representaba un Repositorio, y un Repositorio representa mucho el estado * externo * en mi libro. –

+2

>> Si no escribe una prueba unitaria para esto, ¿cómo lo sabe? << Al mirar el código que es trivial? De lo contrario, uno ni siquiera puede estar seguro de que la prueba sea correcta: parece más complicado que el código bajo prueba. – KolA

3

Hazte dos preguntas. "¿Cuál es el equivalente manual de esta prueba unitaria?" y "¿vale la pena automatizar?". En su caso, sería algo así como:

¿Qué es el equivalente manual? - iniciar depurador - paso en "Guardar" método - paso en siguiente, asegúrese de que está dentro de la aplicación IRepository.EntitySave

¿Vale la pena la automatización? Mi respuesta es no". Es 100% obvio desde el código. De cientos de pruebas de residuos similares, no vi ninguna que fuera útil.