Hasta ahora he estado de mirón en la lista ^_^, soy desarrollador en
LAMP y me estoy iniciando en el mundillo del TDD.
Me han animado a presentar en la lista una pequeña clase para hacer
stubs en PHP, que tiene ciertas particularidades con el uso de getMock
de PHPUnit para stubear.
El proyecto de SimpleStub todavía le falta rodaje, casos de uso y ver si
merece la pena implementar más opciones. Por ahora tiene pendiente el
retorno de excepciones, aunque ya se está trabajando en ello todavía no
está publicada ninguna versión que las soporte.
Lo que más me ha gustado de hacer esta pequeña clase, es que he
aprendido como tiene que comportarse un Stub y en que casos deberíamos
usarlo.
A diferencia del Stub de PHPUnit, si quieres stubear un método de la
clase que pasas como colaborador, no es necesario stubear el resto de
métodos públicos si ya los tienes implementados.
La idea surgió tras el Code Retreat de Donosti en Noviembre tras una
iteración con Carlos Ble.
Podéis ver el código en: https://github.com/mariotux/php-simple-stub
Este es mi primer mensaje, pero estoy a la espera del mensaje de
Guillermo (@ggalmazor) comenzar un debate que me parece interesante para
ir conociendo más el mundo del TDD.
Salu2.
Mario Nunes
--
Has recibido este mensaje porque estás suscrito al grupo "TDDev" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a tdde...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a tddev-sp+u...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en http://groups.google.com/group/tddev-sp?hl=es.
Dicho esto, la funcionalidad de devolver algo en función de un parametro de entrada es muy habitual en los frameworks de dobles de prueba y yo la he usado muchas veces, y alberto seguro que también aunque ahora no se acuerde :P, por ejemplo en la ultima entrada que escribí sobre dobles de prueba lo uso.
@Test public voidsi_el_cliente_tiene_fondos_para_realizar_la_compra_la_puedo_confirmar() {when(unProductoStub.precio()).thenReturn(50);when(otroProductoStub.precio()).thenReturn(25);when(pasarelaDePagoStub.tieneElUsuarioFondosPorValorDe(75)).thenReturn(true);compra.anadir(unProductoStub);compra.anadir(otroProductoStub);assertTrue(compra.confirmar());}
@Test public voidsi_el_cliente_tiene_fondos_para_realizar_la_compra_la_puedo_confirmar() {when(pasarelaDePagoStub.tieneElUsuarioFondosPorValorDe(any())).thenReturn(true);compra.anadir(unProductoStub);compra.anadir(otroProductoStub);assertTrue(compra.confirmar());}
Pues si, tienes razón y el test tal y como esta prueba más cosas de las que dice, además de probar que el cliente si tiene fondos puede realizar la compra prueba "de paso" que la compra realiza correctamente el calculo del precio.
Es un olor de diseño porque el calculo de la compra o bien esta en algún colaborador que contenga la estrategia del calculo de compra o bien puede ser un método público de la compra, no necesito comprobarlo en ese test y lo único que consigo haciendo eso es atar más el test a la implementación.
Era un ejemplo para demostrar el uso de stubs y mocks en el mismo método y no se me ocurrió hacerlo mejor, pero es un mal diseño y ese test lo demuestra. No se me ocurre ningún caso ahora mismo donde sea imprescindible definir lo que devuelve el stub en función del parametro de entrada, pero de eso a decir que es un mal olor siempre... tampoco estoy seguro porque es un mal olor programar así los stubs, en principio no veo que tenga nada "intrinsecamente" malo.
@Test public voidal_comprar_llamamos_a_la_pasarela_de_pago_con_el_valor_total_de_la_compra() {when(unProductoStub.precio()).thenReturn(50);when(otroProductoStub.precio()).thenReturn(25);compra.anadir(unProductoStub);compra.anadir(otroProductoStub);compra.confirmar();expect(pasarelaDePagoStub.tieneElUsuarioFondosPorValorDe(75));}
Yo sigo sin entender el porqué de que un stub tenga que entender de
parámetros. Lo que yo entiendo de un stub, es que deseamos que cuando se
invoque a un método X nos retorne el valor Y.
Si queremos controlar los parámetros usaríamos un Mock? Todavía no me he
metido mucho con esta parte pero, entiendo que el Mock es para controlar
si la invocación al método se realiza y con que parámetros.
No acabo de ver el poder stubear un método aplicándole una lógica.
Si stubeo un colaborador, entiendo que cuando la clase que estoy
testeando vaya a invocar el método de ese colaborador yo espero que
retorne Y para el test que estoy haciendo.
Si quiero comprobar la lógica del método de ese colaborador, el mismo,
tendrá sus tests.
Para que aplicar una lógica en el stub?
Pero como he comentado en el inicio de este hilo, soy novato y estamos
aprendiendo :-) Iluminarnos Maestros del TDD ^_^
Salu2.
Mario Nunes
Aunque no he tenido tiempo de estudiarlo con detenimiento (tiene
much�sima miga), he le�do las definiciones de Test Stub y Fake Object de
xunitpatterns.com. No conoc�a la web y la cantidad de literatura que hay
al respecto es abrumadora.
Definici�n de un Test Double: http://xunitpatterns.com/Test%20Double.html
Definici�n de un Test Stub: http://xunitpatterns.com/Test%20Stub.html
Definici�n de un Fake Object: http://xunitpatterns.com/Fake%20Object.html
En principio no parece estar expl�citamente definido si un stub debe
devolver el mismo valor o distintos valores. Volviendo a la
justificaci�n de todo este tinglado, parece que lo que importa es poder
usar *un doble* para sustituir un componente que est� fuera de nuestro
contexto de testeo. Luego, en funci�n de aquello que estemos
sustituyendo por un doble, tendremos que optar por uno u otro tipo de
implementaci�n: desde clases hechas a mano a tal efecto, hasta usando
clases definidas din�micamente con frameworks como jmock, mockito, etc.
Pienso que toda la confusi�n que tengo en este sentido se debe a c�mo
est�n implementados los frameworks de mockeo en PHP, ya que ninguno
permite distintos valores de retorno en funci�n de ciertas condiciones.
jMock, por ejemplo, permite esto de f�brica por medio de dos mecanismos:
distintos valores en llamadas consecutivas y condicionadores de retorno
tipo "with(blablabla).returnValue(blablabla)". Por no hablar de la
libertad que tenemos de crear una clase a mano que haga literalmente lo
que nos d� la gana.
Insisto, todo esto sin tener en cuenta si es lo m�s apropiado o no a la
hora de definir nuestros tests unitarios.
On 03/02/11 08:33, mariotux wrote:
> Buenas,
>
> Yo sigo sin entender el porqu� de que un stub tenga que entender de
> par�metros. Lo que yo entiendo de un stub, es que deseamos que cuando se
> invoque a un m�todo X nos retorne el valor Y.
>
> Si queremos controlar los par�metros usar�amos un Mock? Todav�a no me he
> metido mucho con esta parte pero, entiendo que el Mock es para controlar
> si la invocaci�n al m�todo se realiza y con que par�metros.
>
> No acabo de ver el poder stubear un m�todo aplic�ndole una l�gica.
>
> Si stubeo un colaborador, entiendo que cuando la clase que estoy
> testeando vaya a invocar el m�todo de ese colaborador yo espero que
> retorne Y para el test que estoy haciendo.
>
> Si quiero comprobar la l�gica del m�todo de ese colaborador, el mismo,
> tendr� sus tests.
>
> Para que aplicar una l�gica en el stub?
>
> Pero como he comentado en el inicio de este hilo, soy novato y estamos
> aprendiendo :-) Iluminarnos Maestros del TDD ^_^
>
> Salu2.
>
> Mario Nunes
>
> El mi�, 02-02-2011 a las 08:11 +0100, Guillermo escribi�:
>> Muchas gracias por vuestras respuestas.
>>
>>
>> Las conclusiones entonces parecen las siguientes:
>>
>>
>> 1) salvo en casos excepcionales, es preferible reorientar el test
>> hacia otro tipo de prueba
>> 2) tiene sentido que un stub pueda devolver un valor distinto en
>> funci�n de par�metros
>>
>>
>> Claramente, en mi ejemplo, lo que procede es reorientar el test.
>>
>>
>> Perdonad la brevedad del mensaje, pero escribo de camino al curro :P
>>
>>
>> Guille
>> Enviado desde mi iPhone
>>
>> El 02/02/2011, a las 02:42, Alberto Pe�a<plag...@gmail.com>
>> escribi�:
>>
>>
>>
>>> El 2011/2/2 Alfredo Casado<casado....@gmail.com> dijo
>>> Pues si, tienes raz�n y el test tal y como esta prueba m�s
>>> cosas de las que dice, adem�s de probar que el cliente si
>>> tiene fondos puede realizar la compra prueba "de paso" que
>>> la compra realiza correctamente el calculo del precio.
>>>
>>>
>>> Es un olor de dise�o porque el calculo de la compra o bien
>>> esta en alg�n colaborador que contenga la estrategia del
>>> calculo de compra o bien puede ser un m�todo p�blico de la
>>> compra, no necesito comprobarlo en ese test y lo �nico que
>>> consigo haciendo eso es atar m�s el test a la
>>> implementaci�n.
>>>
>>>
>>>
>>>
>>> A eso iba. Normalmente, cuando especificamos los valores de entrada
>>> es porque estamos comprobando diferentes comportamientos con un solo
>>> test.
>>>
>>>
>>>
>>>
>>> Era un ejemplo para demostrar el uso de stubs y mocks en el
>>> mismo m�todo y no se me ocurri� hacerlo mejor, pero es un
>>> mal dise�o y ese test lo demuestra. No se me ocurre ning�n
>>> caso ahora mismo donde sea imprescindible definir lo que
>>> devuelve el stub en funci�n del parametro de entrada, pero
>>> de eso a decir que es un mal olor siempre... tampoco estoy
>>> seguro porque es un mal olor programar as� los stubs, en
>>> principio no veo que tenga nada "intrinsecamente" malo.
>>>
>>>
>>>
>>> No se, puede ser que no sea un olor, pero es algo que, al menos
>>> �ltimamente :P, me dice que estoy haciendo algo mal. M�s que nada
>>> por lo que has dicho antes ( y que ya he repetido :D). Me da la
>>> impresi�n de que intentamos probar varias cosas a la vez. Adem�s,
>>> los argumentos de entrada que reciben los colaboradores de una clase
>>> es un "detalle de implementaci�n" que acopla el test al c�digo. Esto
>>> es as�.
>>> Otra cosa es que usemos mocks y hagamos algo como esto:
>>>
>>>
>>> @Test public void
>>>
>>>
>>> al_comprar_llamamos_a_la_pasarela_de_pago_con_el_valor_total_de_la_compra() {
>>>
>>>
>>> when(unProductoStub.precio()).thenReturn(50);
>>>
>>> when(otroProductoStub.precio()).thenReturn(25);
>>>
>>>
>>>
>>> compra.anadir(unProductoStub);
>>>
>>> compra.anadir(otroProductoStub);
>>>
>>>
>>> compra.confirmar();
>>>
>>>
>>>
>>> expect(pasarelaDePagoStub.tieneElUsuarioFondosPorValorDe(75));
>>>
>>>
>>>
>>> }
>>>
>>>
>>>
>>> En este caso, estamos probando algo muy diferente y s� que tiene
>>> sentido que el "expect" tenga un valor definido. (En este caso, los
>>> productos son stubs y solo me interesa lo que devuelven)
>>>
>>>
>>> A ver si me aclaro. En este caso, el colaborador "pasarela de pago"
>>> es un mock y por eso tiene sentido que especifiquemos lo que recibe,
>>> ya que es justo lo que queremos probar. En el caso del anterior
>>> mensaje, el colaborador "pasarela de pago" es un stub y y me da
>>> igual lo que reciba porque solo me interesa definir su
>>> comportamiento (que acepta el pago). �Ayuda esto en algo?
>>>
>>>
>>> Cuando a un colaborador del que solo nos interesa el resultado de su
>>> colaboraci�n (stub) le definimos los argumentos de entrada creo que,
>>> en cierto modo, estamos creando un "expect", ya que nuestro test
>>> solo va a funcionar SI Y SOLO SI el stub recibe dicho argumento de
>>> entrada. Es decir, el colaborador actua como mock y como stub a la
>>> vez, dej�ndonos con el culo al aire. A ver si aclaro un poco mis
>>> ideas y escribo un post sobre esto :P
>>>
>>>
>>> Disclaimer:
>>> Cuando digo �ltimamente es hace 3 meses o as� (hasta donde me llega
>>> la memoria :D), por lo que tampoco hay que tomarme muy en serio. De
>>> todas formas, visto lo visto, probablemente dentro de unos meses
>>> dire algo as� como "por supuesto que hay que pasar los argumentos a
>>> los stubs. Siempre lo he hecho as� y creo que no hacerlo as� es un
>>> olor" :P
>>>
>>>
>>> Un saludo
>>>
>>> --
>>> Alberto Pe�a Abril
>>>
>>> http://twitter.com/plagelao
>>> http://plagelao.blogspot.com/
>>>
>>>
>>>
>>> --
>>> Has recibido este mensaje porque est�s suscrito al grupo "TDDev" de
>>> Grupos de Google.
>>> Para publicar una entrada en este grupo, env�a un correo electr�nico
>>> a tdde...@googlegroups.com.
>>> Para anular tu suscripci�n a este grupo, env�a un correo electr�nico
>>> a tddev-sp+u...@googlegroups.com
>>> Para tener acceso a m�s opciones, visita el grupo en
>>> http://groups.google.com/group/tddev-sp?hl=es.
>>>
>> --
>> Has recibido este mensaje porque est�s suscrito al grupo "TDDev" de
>> Grupos de Google.
>> Para publicar una entrada en este grupo, env�a un correo electr�nico a
>> tdde...@googlegroups.com.
>> Para anular tu suscripci�n a este grupo, env�a un correo electr�nico a
>> tddev-sp+u...@googlegroups.com
>> Para tener acceso a m�s opciones, visita el grupo en
>> http://groups.google.com/group/tddev-sp?hl=es.
>
Todo esto con el warning que ha dado Alfredo (creo). Si estás haciendo
un stub complicado, si empiezas a complicar la lógica de respuesta...
¿Puede que tu test sea demasiado ambicioso? No hay respuestas
enlatadas, todas hay que currárselas ;)
Saludos,
Jorge
On Thursday, February 3, 2011, mariotux <ma...@pensandoenred.com> wrote:
> Buenas,
>
> Yo sigo sin entender el porqué de que un stub tenga que entender de
> parámetros. Lo que yo entiendo de un stub, es que deseamos que cuando se
> invoque a un método X nos retorne el valor Y.
>
> Si queremos controlar los parámetros usaríamos un Mock? Todavía no me he
> metido mucho con esta parte pero, entiendo que el Mock es para controlar
> si la invocación al método se realiza y con que parámetros.
>
> No acabo de ver el poder stubear un método aplicándole una lógica.
>
--
_
Jorge Uriarte Aretxaga
http://www.gailen.es
http://www.linkedin.com/in/jorgeuriarte
--
Has recibido este mensaje porque estás suscrito al grupo "TDDev" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a tdde...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a tddev-sp+u...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en http://groups.google.com/group/tddev-sp?hl=es.
Buenas!
Aunque no he tenido tiempo de estudiarlo con detenimiento (tiene muchísima miga), he leído las definiciones de Test Stub y Fake Object de xunitpatterns.com. No conocía la web y la cantidad de literatura que hay al respecto es abrumadora.
Definición de un Test Double: http://xunitpatterns.com/Test%20Double.html
Definición de un Test Stub: http://xunitpatterns.com/Test%20Stub.html
Definición de un Fake Object: http://xunitpatterns.com/Fake%20Object.html
En principio no parece estar explícitamente definido si un stub debe devolver el mismo valor o distintos valores. Volviendo a la justificación de todo este tinglado, parece que lo que importa es poder usar *un doble* para sustituir un componente que está fuera de nuestro contexto de testeo. Luego, en función de aquello que estemos sustituyendo por un doble, tendremos que optar por uno u otro tipo de implementación: desde clases hechas a mano a tal efecto, hasta usando clases definidas dinámicamente con frameworks como jmock, mockito, etc.
Pienso que toda la confusión que tengo en este sentido se debe a cómo están implementados los frameworks de mockeo en PHP, ya que ninguno permite distintos valores de retorno en función de ciertas condiciones. jMock, por ejemplo, permite esto de fábrica por medio de dos mecanismos: distintos valores en llamadas consecutivas y condicionadores de retorno tipo "with(blablabla).returnValue(blablabla)". Por no hablar de la libertad que tenemos de crear una clase a mano que haga literalmente lo que nos dé la gana.
Insisto, todo esto sin tener en cuenta si es lo más apropiado o no a la hora de definir nuestros tests unitarios.
On 03/02/11 08:33, mariotux wrote:
Buenas,
Yo sigo sin entender el porqué de que un stub tenga que entender de
parámetros. Lo que yo entiendo de un stub, es que deseamos que cuando se
invoque a un método X nos retorne el valor Y.
Si queremos controlar los parámetros usaríamos un Mock? Todavía no me he
metido mucho con esta parte pero, entiendo que el Mock es para controlar
si la invocación al método se realiza y con que parámetros.
No acabo de ver el poder stubear un método aplicándole una lógica.
Si stubeo un colaborador, entiendo que cuando la clase que estoy
testeando vaya a invocar el método de ese colaborador yo espero que
retorne Y para el test que estoy haciendo.
Si quiero comprobar la lógica del método de ese colaborador, el mismo,
tendrá sus tests.
Para que aplicar una lógica en el stub?
Pero como he comentado en el inicio de este hilo, soy novato y estamos
aprendiendo :-) Iluminarnos Maestros del TDD ^_^
Salu2.
Mario Nunes
El mié, 02-02-2011 a las 08:11 +0100, Guillermo escribió:
Muchas gracias por vuestras respuestas.
Las conclusiones entonces parecen las siguientes:
1) salvo en casos excepcionales, es preferible reorientar el test
hacia otro tipo de prueba
2) tiene sentido que un stub pueda devolver un valor distinto en
función de parámetros
Claramente, en mi ejemplo, lo que procede es reorientar el test.
Perdonad la brevedad del mensaje, pero escribo de camino al curro :P
Guille
Enviado desde mi iPhone
El 02/02/2011, a las 02:42, Alberto Peña<plag...@gmail.com>
escribió:
El 2011/2/2 Alfredo Casado<casado....@gmail.com> dijo
Pues si, tienes razón y el test tal y como esta prueba más
cosas de las que dice, además de probar que el cliente si
tiene fondos puede realizar la compra prueba "de paso" que
la compra realiza correctamente el calculo del precio.
Es un olor de diseño porque el calculo de la compra o bien
esta en algún colaborador que contenga la estrategia del
calculo de compra o bien puede ser un método público de la
compra, no necesito comprobarlo en ese test y lo único que
consigo haciendo eso es atar más el test a la
implementación.
A eso iba. Normalmente, cuando especificamos los valores de entrada
es porque estamos comprobando diferentes comportamientos con un solo
test.
Era un ejemplo para demostrar el uso de stubs y mocks en el
mismo método y no se me ocurrió hacerlo mejor, pero es un
mal diseño y ese test lo demuestra. No se me ocurre ningún
caso ahora mismo donde sea imprescindible definir lo que
devuelve el stub en función del parametro de entrada, pero
de eso a decir que es un mal olor siempre... tampoco estoy
seguro porque es un mal olor programar así los stubs, en
principio no veo que tenga nada "intrinsecamente" malo.
No se, puede ser que no sea un olor, pero es algo que, al menos
últimamente :P, me dice que estoy haciendo algo mal. Más que nada
por lo que has dicho antes ( y que ya he repetido :D). Me da la
impresión de que intentamos probar varias cosas a la vez. Además,
los argumentos de entrada que reciben los colaboradores de una clase
es un "detalle de implementación" que acopla el test al código. Esto
es así.
Otra cosa es que usemos mocks y hagamos algo como esto:
@Test public void
al_comprar_llamamos_a_la_pasarela_de_pago_con_el_valor_total_de_la_compra() {
when(unProductoStub.precio()).thenReturn(50);
when(otroProductoStub.precio()).thenReturn(25);
compra.anadir(unProductoStub);
compra.anadir(otroProductoStub);
compra.confirmar();
expect(pasarelaDePagoStub.tieneElUsuarioFondosPorValorDe(75));
}
En este caso, estamos probando algo muy diferente y sí que tiene
sentido que el "expect" tenga un valor definido. (En este caso, los
productos son stubs y solo me interesa lo que devuelven)
A ver si me aclaro. En este caso, el colaborador "pasarela de pago"
es un mock y por eso tiene sentido que especifiquemos lo que recibe,
ya que es justo lo que queremos probar. En el caso del anterior
mensaje, el colaborador "pasarela de pago" es un stub y y me da
igual lo que reciba porque solo me interesa definir su
comportamiento (que acepta el pago). ¿Ayuda esto en algo?
Cuando a un colaborador del que solo nos interesa el resultado de su
colaboración (stub) le definimos los argumentos de entrada creo que,
en cierto modo, estamos creando un "expect", ya que nuestro test
solo va a funcionar SI Y SOLO SI el stub recibe dicho argumento de
entrada. Es decir, el colaborador actua como mock y como stub a la
vez, dejándonos con el culo al aire. A ver si aclaro un poco mis
ideas y escribo un post sobre esto :P
Disclaimer:
Cuando digo últimamente es hace 3 meses o así (hasta donde me llega
la memoria :D), por lo que tampoco hay que tomarme muy en serio. De
todas formas, visto lo visto, probablemente dentro de unos meses
dire algo así como "por supuesto que hay que pasar los argumentos a
los stubs. Siempre lo he hecho así y creo que no hacerlo así es un
olor" :P
Un saludo
--
Alberto Peña Abril
Has recibido este mensaje porque estás suscrito al grupo "TDDev" de
Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico
a tdde...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico
a tddev-sp+u...@googlegroups.com--
Para tener acceso a más opciones, visita el grupo en
http://groups.google.com/group/tddev-sp?hl=es.
Has recibido este mensaje porque estás suscrito al grupo "TDDev" de
Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a
tdde...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a
tddev-sp+u...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en
http://groups.google.com/group/tddev-sp?hl=es.
--
Has recibido este mensaje porque estás suscrito al grupo "TDDev" de Grupos de Google.
Para publicar una entrada en este grupo, envía un correo electrónico a tdde...@googlegroups.com.
Para anular tu suscripción a este grupo, envía un correo electrónico a tddev-sp+u...@googlegroups.com
Para tener acceso a más opciones, visita el grupo en http://groups.google.com/group/tddev-sp?hl=es.
La mayor parte de las veces mezclo las dos cosas. En general me
interesa el patrón de llamadas que se hace a los colaboradores cuando
invoco un método, y a la vez el resultado del método que suele
depender de lo que devuelvan estos. Por lo tanto en mi caso mezclo
mocks con stubs y no me preocupo por hacer distinciones teóricas.
Lo importante es que el test sea tan legible o más que el código de
producción, y que no sea frágil. Por frágil me refiero a que se acople
a los detalles de implementación de un método, y en cuanto cambias
algún detalle irrelevante de este rompes el test. Para mi lo más
difícil de testear con mocks es que tus tests no se vuelvan frágiles.
Cosas como forzar al mock a que deba recibir llamadas en determinado
orden cuando en realidad no es necesario o comprobar con un nivel de
detalle irrelevante los parámetros de las llamadas, o el número de
llamadas recibido por cada métodos.
Salud !
2011/2/5 Carlos Ble <ble.j...@gmail.com>:
El 06/02/2011, a las 02:36, alberto rodriguez
<albert...@gmail.com> escribió: