Consulta de buenas practicas en TDD

29 views
Skip to first unread message

Miguel Eduardo Román Martínez

unread,
Mar 5, 2015, 11:47:19 AM3/5/15
to altnet-...@googlegroups.com
​​Que tal señores, actualmente formo parte de un equipo de programadores bastante entusiastas y que están en la disposición de que trabajemos con TDD (actualmente no realizamos ningún tipo de pruebas unitarias y las consecuencias se pueden notar). empezamos con un par de proyectos y surgió el siguiente tema a discusión:

Revisando el código de los compañeros me topé con el siguiente TEST:

[Test]
public void Rechazar_CuandoLaSolicitudEstaPendienteDeAprobacion_MarcaLaSolicitudComoRezazada()
{
    var storage = new Mock<ISolicitudStorage>();

    storage.Setup(x => x.ObtenerPorId(100))
           .Returns(new Solicitud { 
              Id = 100, 
              Estado = Estados.PendienteDeAprobacion 
            });

    storage.Setup(x => x.CambiarEstado(100, Estados.Rechazada));

    var servicio = new SolicitudServicio(storage.Object);

    servicio.Rechazar(100);

    storage.Verify(x => x.CambiarEstado(100, Estados.Rechazada), Times.Once);

}

Ahora bien mi conflicto existencial con este test o este tipo de pruebas es que hay algo que me hace pensar que está probando la implementación del método Aprobar mas que el resultado de lo que hace. por ejemplo, ¿que pasaría si yo hago un refactoring y ahí descubro que debería eliminar el método ISolicitudStorage.CambiarEstado porque hay otro en otro lugar que hace exactamente lo mismo? yo modificaría el método Aprobar para llamar al ISolicitudStorage.ActualizarSolicitud por ejemplo en lugar del CambiarEstado. Eso haría fallar mi test, aunque no quiera decir que mi funcionalidad esté mala, sino que cambie la forma en la que apruebo una solicitud obteniendo el mismo resultado.

Ahora bien, para rediseñar el test yo estaba pensando involucrar a una base de datos de pruebas (aunque algo me dice que eso podría llegar a ahogarme en algún momento jejeje). Por ejemplo, pensaba cambiar el test para que se viera algo como esto (pensaba abrir una transaccion en el Setup y hacerle rollback al finalizar el test):

[Test]
public void Rechazar_CuandoLaSolicitudEstaPendienteDeAprobacion_MarcaLaSolicitudComoRezazada()
{
    var storage = new OracleSolicitudStorage("TestsConnectionString");

    var servicio = new SolicitudServicio(storage);

    servicio.Rechazar(100);

    var solicitud = sotage.ObtenerPorId(100);

    Assert.AreEqual(Estados.Rechazada, solicitud.EstadoId);

}

Creo que con este test ya no importaría la forma en la que yo altero el estado de la solicitud, lo importante es que al rechazar se altere el estado de esa solicitud, este test soporta un poco mas refactorización en el método Rechazar que el test anterior. ¿no sé que tanto de la implementación de un método deba conocer el test? ¿ambos tests están incorrectos :)? ¿Podría lograr un resultado similar utilizando Moks y no conectarme a la base de datos?.

¿Algunos Tips generales para realizar Tests que sean realmente útiles y que no implique que la refactorización de mis clases impliquen siempre refactorización de mis tests?

Gracias, Miguel Román.


Vicenç Garcia

unread,
Mar 5, 2015, 11:53:43 AM3/5/15
to altnet-...@googlegroups.com
Hola Miguel,

una rapidita. Refactorizar quiere decir cambiar el funcionamiento interno de una clase sin cambiar como se interactua con ella (no lo digo yo, lo dice Martin Fowler http://martinfowler.com/bliki/DefinitionOfRefactoring.html ), con lo que una refactorizacion no te deberia cambiar los tests. Si cambias la forma en que apruebas una solicitud, primeo deberias cambiar el test y despues el codigo.

Un saludo,
Vicenc

--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a altnet-hispan...@googlegroups.com.
Para publicar en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.
Visita este grupo en http://groups.google.com/group/altnet-hispano.
Para acceder a más opciones, visita https://groups.google.com/d/optout.



--

Angel Java Lopez

unread,
Mar 5, 2015, 12:17:58 PM3/5/15
to altnet-...@googlegroups.com
El segundo test tiene mejor forma, en mi opinion y experiencia.

Si, tambien uso bastante lo de ir contra la base, usan transaccion en setup y rollback en el cleanup

Igual, lo que me pasa si sigo TDD como flujo de trabajo, es que un subproducto que queda es un IStorage, implementado en memoria, en algun momento de los primeros dias y tests. Por lo menos para las primeras cosas, como mantener una lista y recuperar por id. Por eso no uso mocks en este tipo de tests. O van contra la base, o van contra una implementacion alternativa.

Nos leemos!

Angel "Java" Lopez
@ajlopez

Diego Güemes

unread,
Mar 5, 2015, 1:40:28 PM3/5/15
to altnet-...@googlegroups.com
Hola Miguel,

¡qué tema más interesante! En cuanto a cual de los dos tests es mejor, diría que como en todo, depende :P.

El segundo, aunque es un test de integración contra la base de datos, no expone tantos detalles de implementación como el primero, por lo tanto, refactorings o transformaciones en el código, no impactarán tanto al test. Este es el problema de los TDDers clasicistas y mockistas.

Sin embargo, me atrevería a decir que en este caso, el problema del primer test, es un problema de diseño. Me explico:
Creo que en ese caso concreto, el servicio sabe demasiado sobre la solicitud, y utiliza la solicitud como una mera estructura de datos, y por lo tanto, la lógica de negocio está dividida en varias clases. También un codesmell que aparece ahí, es que lo que verdaderamente está probando el test, es el método cambiar estado del storage. Utilizar un mock para comprobar que todo lo que hace el servicio es delegar en el storage...¿No parece un test muy útil, no? (Ojo, estoy asumiendo que el servicio sólamente está hablando con el storage, si hace más cosas, este último punto cambia).

¿Qué tal si el objeto solicitud tiene un método rechazar, y de esta manera no expone los detalles de implementación y el manejo de estados:

[Test]
public void Rechazar_CuandoLaSolicitudEstaPendienteDeAprobacion_MarcaLaSolicitudComoRezazada()
{
    var solicitud = new Solicitud { 
        Id = 100, 
        Estado = Estados.PendienteDeAprobacion 
    };
    var storage = new Mock<ISolicitudStorage>();
    var servicioSolicitudes = new SolicitudServicio(storage.Object);

    storage.Setup(x => x.ObtenerPorId(100)).Returns(solicitud);

    servicioSolicitudes.Rechazar(100); // este utilizaría solicitud.rechazar(), que cambiaría el estado del objeto y además más orientado a DDD.
    
    Assert.True(solicitud.esta_rechazada());
    storage.Verify(x => x.Guardar(solicitud));

César Pistiner

unread,
Mar 5, 2015, 3:27:10 PM3/5/15
to altnet-...@googlegroups.com
Hola Miguel,

Te comento lo que yo veo:

El primer test no esta testeando que se marque la solicitud como rechazada, sino que esta testeando que se llame a CambiarEstado del servicio. O le cambias el nombre o cambias el test ajajajaja. Tal vez si tu test fuera así:

[Test]
public void Rechazar_CuandoLaSolicitudEstaPendienteDeAprobacion_MarcaLaSolicitudComoRezazada()
{
    var storage = new Mock<ISolicitudStorage>();
    var solicitud = new Solicitud { 
Id = 100, 
        Estado = Estados.PendienteDeAprobacion 
    });

    storage.Setup(x => x.ObtenerPorId(100))
           .Returns(solicitud);

    storage.Setup(x => x.CambiarEstado(100, Estados.Rechazada));

    var servicio = new SolicitudServicio(storage.Object);

    servicio.Rechazar(100);

    Assert.AreEqual(Estados.Rechazada, solicitud.EstadoId);

}

Ya no tendrías problemas. Y en todo caso, CambiarEstado sería algo privado. Puede que no sea tan así pero viendo el código que expones no hay necesidad de que CambiarEstado sea algo público.

Saludos,
César

César Pistiner

unread,
Mar 5, 2015, 3:49:49 PM3/5/15
to altnet-...@googlegroups.com
Pero que gilun!! No vi que el CambiarEstado era del storage, en ese caso, cambiaría el nombre del test a: Rechazar_CuandoLaSolicitudEstaPendienteDeAprobacion_LlamaACambiarEstadoDelStorage() y seguiría la recomendación de Vicenc. No cambies nada dentro de tu servicio sin antes tener un test que te obligue a hacerlo.

Saludos y perdón por la pifiada inicial,
César

Oscar Zárate

unread,
Mar 5, 2015, 3:59:40 PM3/5/15
to altnet-...@googlegroups.com
Hola Miguel,

Pido perdón de entrada si es que mi respuesta suena pragmática, pero quisiera definir algunos conceptos:

TDD: Test driven design (o development para algunos). Es el ciclo de rojo-verde-refactorear que se hace sobre una clase que vas a crear (o sea, primero el test y luego la clase, nunca escribis una linea de código sin su test)

Prueba Unitaria (Unit Test): Test sobre una unidad (normalmente un método en una clase) (tu primer test)

Prueba de integración (Integration Test): Test que agrupa la funcionalidad de varios métodos y tal vez, varias clases y capas (incluso accediendo a bases de datos o sistemas externos). Caso extremo de esto es un test End2End (de punta a punta) donde podes incluir la interfaz del ususario (y hacer Automation UI Test). (tu segundo test)

Pruebas de Estado: (State verification) Donde se ejercita el método para obtener un estado final (tu segundo test)

Pruebas de Comportamiento (Behavior verification) Donde se ejecuta el método para verificar su comportamiento (tu primer test).

SaludOZ,

PS: No se puede pasar por esto sin leer (una vez más) esto http://martinfowler.com/articles/mocksArentStubs.html

PS: Con respecto a tu pregunta ... me parece que los dos test suman, sólo habría que ver cual es cual y tal vez mejorar la fragilidad del test (que tan fácil se rompen)

César Pistiner

unread,
Mar 5, 2015, 6:23:45 PM3/5/15
to altnet-...@googlegroups.com

Oscar,

Muy bueno el artículo de Martin Fowler!

Consulto para quien use Moq, los Stubs serían usando el Mock.Of<> ?

Saludos,
César

Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a altnet-hispano+unsubscribe@googlegroups.com.
Para publicar en este grupo, envía un correo electrónico a altnet-hispano@googlegroups.com.

Visita este grupo en http://groups.google.com/group/altnet-hispano.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a altnet-hispano+unsubscribe@googlegroups.com.
Para publicar en este grupo, envía un correo electrónico a altnet-hispano@googlegroups.com.

Oscar Zárate

unread,
Mar 5, 2015, 7:24:56 PM3/5/15
to altnet-...@googlegroups.com
Cesar,

Como dice el maestro AngelJ, si estoy haciendo TDD, mis stubs son cosas que creo yo (en memoria) para reemplazar lo en un futuro será un "real" (digo si todos estos son "fakes" :-)).

Pero en el GitHub de Kzu podes encontrar esto

Setup a property so that it will automatically start tracking its value (also known as Stub):
// start "tracking" sets/gets to this property
mock.SetupProperty(f => f.Name);

// alternatively, provide a default value for the stubbed property
mock.SetupProperty(f => f.Name, "foo");


// Now you can do:

IFoo foo = mock.Object;
// Initial value was stored
Assert.Equal("foo", foo.Name);

// New value set which changes the initial value
foo.Name = "bar";
Assert.Equal("bar", foo.Name);
Stub all properties on a mock (not available on Silverlight):

mock.SetupAllProperties();


SaludOZ,

Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a altnet-hispan...@googlegroups.com.
Para publicar en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.

César Pistiner

unread,
Mar 5, 2015, 8:52:54 PM3/5/15
to altnet-...@googlegroups.com

Hola Oscar,

Ajam, esa forma no la había visto antes en Moq, alguna vez intenté usar el SetupProperty pero no recuerdo esa implementación. Y como no pude lograrlo terminé creando el objeto en sí. Voy a retomar esos casos a ver si lo logro.

Gracias por el aporte, y perdón por desviar un poco el hilo!

Saludos,
César

Miguel Eduardo Román Martínez

unread,
Mar 6, 2015, 1:18:14 AM3/6/15
to altnet-...@googlegroups.com
Gracias a todos por sus comentarios, realmente nos han brindado mayor orientación con respecto a decisiones al momento de escribir los tests.

Las dudas surgían también porque algunas veces (debido a que no tenemos la costumbre de hacer tests) se siente un poco "engorroso" estar creando tests, pero definitivamente si logramos confiar mas en el código que escribimos.

Saludos,
Miguel.

Oscar Zarate

unread,
Mar 6, 2015, 1:43:40 AM3/6/15
to altnet-...@googlegroups.com
Miguel,

No temas equivocarte. La única forma de no cometer errores es no haciendo NADA.

Acordate que 1 test es mejor que 0 tests y esto se puede relacionar al thread de "is tdd dead?" que tuvimos hace un tiempo.

SaludOZ

From: Miguel Eduardo Román Martínez
Sent: ‎6/‎03/‎2015 5:18 PM
To: altnet-...@googlegroups.com
Subject: Re: [altnet-hispano] Consulta de buenas practicas en TDD

Alejandro Miralles

unread,
Mar 6, 2015, 7:52:35 AM3/6/15
to altnet-...@googlegroups.com

"is tdd dead?"

No, otra vez nooooooooooooooo!!!! ;)

 

Saludos, Ale Miralles.

http://amiralles.net

 

PD: +1 para la aclaración de Oscar. (Sobre todo Unit test vs Integration tests).

Carlos Admirador

unread,
Feb 24, 2026, 2:07:09 PM (4 days ago) Feb 24
to AltNet-Hispano
estado del arte actual del testing en 2026 ?
Reply all
Reply to author
Forward
0 new messages