2010-01-13 8 views
12

He estado trabajando en un proyecto ASP.NET MVC durante aproximadamente 8 meses. En su mayor parte he estado usando TDD, algunos aspectos fueron cubiertos por pruebas unitarias solo después de haber escrito el código real. En total, el proyecto tiene una buena cobertura de prueba.Consejos sobre cómo escribir pruebas TDD de la unidad de refactorización

Estoy bastante satisfecho con los resultados hasta ahora. Refactorizar realmente es mucho más fácil y mis pruebas me han ayudado a descubrir bastantes errores incluso antes de ejecutar mi software la primera vez. Además, he desarrollado falsificaciones y ayudantes más sofisticados para ayudarme a minimizar el código de prueba.

Sin embargo, lo que realmente no me gusta es el hecho de que frecuentemente me encuentro teniendo que actualizar las pruebas de unidades existentes para dar cuenta de las refactorizaciones que hice al software. La refacturación del software ahora es rápida y sin problemas, pero la refacturación de mis pruebas unitarias es bastante aburrida y tediosa. De hecho, el costo de mantener mis pruebas unitarias es mayor que el costo de escribirlas en primer lugar.

Me pregunto si podría estar haciendo algo mal o si esta relación de costo de desarrollo de prueba frente a mantenimiento de prueba es normal. Ya he intentado escribir tantas pruebas como sea posible para que estas cubran mis historias de usuario en lugar de cubrir sistemáticamente la interfaz de mi objeto como se sugiere en this blog article.

Además, ¿tiene más consejos sobre cómo escribir pruebas TDD para que la refactorización rompa la menor cantidad posible de pruebas?

Editar: Como Henning y tvanfosson señalaron correctamente, generalmente es la parte de configuración que es más costosa de escribir y mantener. Las pruebas rotas son (en mi experiencia) generalmente el resultado de una refactorización al modelo de dominio que no es compatible con la parte de instalación de esas pruebas.

+1

¿Puede dar un ejemplo específico de cómo la refacturación de su código significó cambios en las pruebas de su unidad? Aunque hay ocasiones en que esto sucederá, personalmente no creo que las pruebas de "unidad" verdaderas se vean afectadas por la refactorización (aparte de mover algunas pruebas y/o escribir nuevas pruebas para el código factorizado). Me doy cuenta de que no es una respuesta muy útil y espero que ejemplos específicos puedan conducir a una. – kdgregory

Respuesta

8

Este es un problema bien conocido que puede abordarse escribiendo pruebas de acuerdo con las mejores prácticas. Estas prácticas se describen en el excelente xUnit Test Patterns. Este libro describe los olores de las pruebas que conducen a pruebas que no se pueden mantener, así como también brinda orientación sobre cómo escribir pruebas de unidades que se pueden mantener.

Después de haber seguido esos patrones durante mucho tiempo, escribí AutoFixture que es una biblioteca de código abierto que encapsula muchos de esos patrones centrales.

Funciona como Test Data Builder, pero también se puede conectar para funcionar como contenedor de Auto-Mocking y hacer muchas otras cosas extrañas y maravillosas.

Ayuda mucho con respecto al mantenimiento, ya que aumenta considerablemente el nivel de abstracción de escribir una prueba. Las pruebas se vuelven mucho más declarativa porque puede indicar que desea una instancia de cierto tipo en lugar de escribir explícitamente cómo se creó.

Imagine que tiene aa clase con este constructor firma

public MyClass(Foo foo, Bar bar, Sgryt sgryt) 

Mientras AutoFixture puede resolver todos los argumentos del constructor, puede simplemente crear una nueva instancia de esta manera:

var sut = fixture.CreateAnonymous<MyClass>(); 

El El principal beneficio es que si decide refactorizar el constructor MyClass, no se rompen las pruebas porque AutoFixture lo resolverá por usted.

Eso es solo un vistazo de lo que AutoFixture puede hacer. Es una biblioteca independiente, por lo que funcionará con el marco de prueba de su unidad de elección.

+0

Muchas gracias, voy a echar un vistazo! –

+1

@Mark Seemann Gracias por su respuesta. Mencionar el libro xUnit Test Pattern fue especialmente útil para mí. Parece exactamente lo que estoy buscando: me aseguraré de pedir una copia. – phuibers

1

Lo que creo que quiere decir es que es la parte de configuración bastante tediosa de mantener. Estamos teniendo exactamente el mismo problema, especialmente cuando introducimos nuevas dependencias, dividimos las dependencias o cambiamos la forma en que se supone que se utilizará el código.

En general, cuando escribo y mantengo pruebas de unidades, dedico mi tiempo a escribir el código de configuración/organización. En muchas de nuestras pruebas tenemos exactamente el mismo código de configuración, y algunas veces hemos utilizado métodos de ayuda privada para realizar la configuración real, pero con diferentes valores.

Sin embargo, eso no es algo realmente bueno, porque todavía tenemos que crear todos esos valores en cada prueba. Por lo tanto, ahora estamos estudiando la posibilidad de escribir nuestras pruebas en un estilo más específico/BDD, lo que debería ayudar a reducir el código de configuración y, por lo tanto, la cantidad de tiempo invertida en el mantenimiento de las pruebas. Algunos recursos que puede consultar son http://elegantcode.com/2009/12/22/specifications/ y el estilo de prueba BDD con MSpec http://elegantcode.com/2009/07/05/mspec-take-2/

2

Puede que esté escribiendo las pruebas de su unidad demasiado cerca de sus clases. Lo que debes hacer es probar las API públicas. Cuando me refiero a las API públicas, no me refiero a los métodos públicos en todas sus clases, me refiero a sus controladores públicos.

Al hacer que sus pruebas imiten cómo un usuario interactuaría con la parte de su controlador sin tocar las clases de modelo o la función de ayuda directamente, se permite refactorizar su código sin tener que refactorizar sus pruebas. Por supuesto, a veces incluso su API pública cambia y aún tendrá que cambiar sus pruebas, pero eso ocurrirá con mucha menos frecuencia.

La desventaja de este enfoque es que a menudo tendrás que pasar por una configuración de controlador compleja solo para probar una nueva función de ayuda pequeña que deseas introducir, pero creo que al final, vale la pena.Además, terminará organizando su código de prueba de una manera más inteligente, haciendo que el código de configuración sea más fácil de escribir.

+0

Su respuesta es correcta, pero resume el artículo del blog que ya mencioné ... –

+0

Entonces debe estar haciendo algo mal, porque no, se supone que no debe refactorizar constantemente sus pruebas cuando refactorice su código. –

+0

No dije constantemente. Pero encontré que el costo de mantenimiento es excedido por el costo de creación de pruebas tdd. ¿Es eso diferente en tu experiencia? –

2

Este artículo me ha ayudado mucho: http://msdn.microsoft.com/en-us/magazine/cc163665.aspx

Por otro lado, no hay ningún método milagro para evitar las pruebas de unidad de refactorización.

Todo viene con un precio, y eso es especialmente cierto si desea realizar pruebas unitarias.

+0

Gracias por el artículo, voy a echar un vistazo. Soy consciente de que todo tiene un precio, me preguntaba por qué la gente se queja de que las pruebas unitarias son demasiado tediosas para escribir en esas discusiones sobre si TDD realmente tiene sentido, mientras que dedico mucho más tiempo a mantenerlas que escribirlas. –

1

La mayoría de las veces veo tales refactorizaciones que afectan la configuración de la prueba unitaria, que con frecuencia implican agregar dependencias o cambiar las expectativas en estas dependencias. Estas dependencias pueden ser introducidas por características posteriores pero afectan a pruebas anteriores. En estos casos, he encontrado que es muy útil refactorizar el código de configuración para que sea compartido por múltiples pruebas (parametrizadas de modo que puedan configurarse de manera flexible). Luego, cuando necesito hacer un cambio para una nueva característica que afecta la configuración, solo tengo que refactorizar las pruebas en un solo lugar.

+1

También hice algo similar agregando fábricas de objetos falsos. También tengo una clase de prueba básica que se ocupa de la inyección de dependencia. A veces también tengo un método de configuración compartido por múltiples pruebas. Supongo que eso es lo que quieres decir con la refacturación del código de configuración. –

0

Dos áreas en las que me concentro cuando comienzo a sentir el dolor del refactor en la configuración hacen que las pruebas de mi unidad sean más específicas y mi método/clase sea más pequeño. Esencialmente encuentro que me estoy alejando de SOLID/SRP. O tengo pruebas que están tratando de hacer mucho.

Vale la pena señalar que intento alejarme de las especificaciones BDD/context más lejos de la interfaz de usuario que obtengo. Probar un comportamiento es genial, pero siempre me lleva (tal vez no lo estoy haciendo bien?) A pruebas más grandes, con más especificaciones de contexto de las que me gustan.

Otra forma en que he visto que esto me pase es como un código de débito, introduciéndome en métodos que hacen crecer su lógica comercial a lo largo del tiempo. Por supuesto, siempre hay grandes métodos y clases con múltiples dependencias, pero cuanto menos tengo, menos "prueba de reescritura" tengo.

0

Si se encuentra creando andamios de prueba complicados que incluyen gráficos de objetos profundos como Russian dolls, considere refaccionar su código para que la clase bajo prueba obtenga exactamente lo que necesita en su constructor/argumentos, en lugar de tener que recorrer el gráfico.

intead de:

public class A { 

    public void foo(B b) { 
     String someField = b.getC().getD().getSomeField(); 
     // ... 
    } 
} 

Cambiar a:

public class A { 

    public void foo(String someField) { 
     // ... 
    } 
} 

Luego, su configuración de prueba se vuelve trivial.

Cuestiones relacionadas