54
Doble o Nada Jesús Miguel Benito Calzada - PHP Cáceres

Doble o nada

Embed Size (px)

Citation preview

Doble o NadaJesús Miguel Benito Calzada - PHP Cáceres

Jesús Miguel Benito Calzada

Software developer @ Intexdev

[email protected]

@beni0888

https://github.com/beni0888

¿Quién soy?

¿De qué va esto?

❖ Unit testing

❖ PHPUnit

❖ Test Doubles

❖ Mockery

DISCLAIMER

❖ Esta es mi manera de hacer las cosas, seguro que las hay mejores.

❖ No soy un experto en testing, pero a veces hago tests ;).

❖ Esto no trata de TDD, aunque es posible que el término aparezca en más de una ocasión.

Unit TestHerramienta que nos permite comprobar el correcto funcionamiento de una unidad de código. Esto sirve para asegurar que cada una de las partes del sistema funciona correctamente por separado.

Debe ser:

❖ Atómico

❖ Independiente

❖ Inocuo

❖ Rápido

Atómico

❖ El test prueba la mínima cantidad de funcionalidad posible .

❖ Un solo comportamiento del método o función.

❖ Distintos caminos de ejecución => un test para cada camino

Independiente❖ El resultado de la ejecución de un test no debe

depender de otros.

❖ No debe ser parte de una secuencia de tests que deba ejecutarse en un orden determinado

Inocuo❖ La ejecución de un test no debe alterar el estado del

sistema (ficheros, base de datos, etc) => no mancha :)

❖ El resultado debe ser el mismo ya lo ejecutemos una o mil veces => idempotente

Rápido❖ Normalmente lanzaremos un gran número de tests

en cada ejecución

❖ Tener que esperar resulta improductivo y aburrido => no haríamos test (menos de los que ya hacemos :P)

❖ Un test unitario debería ejecutarse en una pequeña fracción de segundo

Características ATRIP❖ Automatic: pueden ser corridos mediante un simple comando.

❖ Thorough (minucioso): cuanto más testemos mejor.

❖ Repeatable: podemos ejecutarlo una y mil veces con el mismo resultado.

❖ Independant: el resultado de un test no debe depender de otros.

❖ Professional: debemos ser igual de exigentes al escribir los tests de lo que lo somos al escribir nuestra lógica de negocio (prohibidas ñapas en los tests)

Un poco de terminología

Un poco de terminología

❖ SUT: Subject/System Under Test.

❖ Colaboradores: objetos que interactúan con el SUT y que necesita para implementar su funcionalidad.

❖ Fixtures: Algo así como los elementos de contexto, datos u objetos que necesitamos para construir el escenario que requiere el test.

Anatomía de un unit test: AAA

❖ Arrange: configuración y/o carga de fixtures, instanciar colaboradores, etc.

❖ Act: ejercitar la funcionalidad que queremos testear.

❖ Assert: afirmar que el estado es el esperado

PHPUnit❖ Versión para php de los frameworks xUnit

❖ Creado por Sebastian Bergman

❖ Estándar de facto en php

❖ https://phpunit.de/

InstalaciónPHP Archive (PHAR):

Composer:

TestCase❖ Los tests se organiza en clases que heredan de la clase

PHPUnit_Framework_TestCase

❖ Cada unit test es un método de la clase

❖ Para que un método sea considerado un test, su nombre debe llevar el sufijo test o debe estar marcado con la anotación @test

setUp() y tearDown()❖ setUp(): método que se ejecuta antes de cada test,

pude servirnos para la fase de arrangement.

❖ tearDown(): método que se ejecuta después de cada test, puede utilizarse para labores de limpieza

Aserciones

Excepciones❖ Podemos hacer que el test falle si no se lanza una

excepción

Organización de los tests❖ Fichero de configuración:

❖ Sistema de archivos:

• phpunit.xml.dist• phpunit.xml

Mocks❖ Son objetos que imitan a otros objetos de nuestro sistema y

que utilizamos en su lugar a la hora de hacer tests => “Test Doubles”

❖ Suelen reemplazar a los colaboradores en los tests unitarios

❖ En ocasiones pueden incluso reemplazar al SUT!!

❖ Permiten aislar la funcionalidad del SUT de la de los colaboradores

❖ Facilitan la configuración del escenario del test

❖ Permiten que los tests unitarios sean rápidos

¿Todos los Test Doubles son Mocks?

¿Todos los Test Doubles son Mocks?

❖ No

❖ Mock es una palabra empleada en el lenguaje informal para referirse a los test doubles en general.

❖ Existen varios tipos específicos de test doubles.

❖ Los mocks son un tipo específico de test double

❖ Vamos a verlos!

Dummies❖ Es un test doble que sólo se usa para rellenar

parámetros de un constructor o método

❖ Nunca es usado dentro del la funcionalidad del SUT que vamos a ejercitar (al menos en el camino de ejecución que vamos a testear)

❖ En teoría sus métodos deben devolver NULL (si lo generamos a mano implementando una interface)

❖ Generalmente implementarán una interface

Dummies

Algunos ejemplos robados de http://php-and-symfony.matthiasnoback.nl/2014/07/test-doubles/

❖ Dummies con phpunit:

❖ Ejemplo con mockery:

Stubs❖ Son test doubles que devuelve un valor fijo

predeterminado en las llamadas a sus métodos

❖ No tiene en cuenta los argumentos pasados a los métodos (no se verifican)

❖ No tiene en cuenta el número de llamadas realizadas a los métodos

Stubs

Fakes❖ Son una especie de stubs con cierta lógica distinta de la

verdadera lógica del colaborador al que imita y normalmente más ligera

❖ Suelen emplearse cuando el SUT depende de un colaborador que no está disponible, es lento o simplemente dificulta el test

❖ Tampoco tiene en cuenta el número de llamadas a los métodos

❖ No realiza verificación de parámetros

❖ Pueden volverse tan complicados que requieran sus propios tests unitarios

Fakes

Spies❖ Es una especie de fake o stub que que recaba

información en las llamadas realizadas a sus métodos y permite inspeccionarla después

❖ Puede por ejemplo registrar qué métodos han sido llamados, número de veces, parámetros recibidos, etc.

Spies

Mocks❖ Son un tipo muy especial de test double

❖ Se parece a los spies en el sentido de que registran las llamadas realizadas y los parámetros pasados

❖ Es mucho más potente que un spy

❖ Permite algo que el resto no: validación del comportamiento o interacción

Mocks❖Permite definir un conjunto de expectativas para validar:

• Llamadas a los métodos

• Parámetros pasados: número, tipo y valor

• Número de veces llamado: exacto, mínimo, máximo

• Orden de las llamadas

❖Pueden actuar como stubs devolviendo valores

❖Existen un gran número de librerías que permiten generar mocks de objetos (o interfaces) al vuelo, en php: phpunit, mockery, prophecy, fake, etc.

Mocks

To mock or not to mock?❖ Los mocks son una herramienta muy potente y su uso tiene sus pros y sus

contras

❖ Su uso puede llevar a sobrespecificación y dar lugar a tests frágiles

❖ El uso o no de los mismos permite diferenciar dos tipos de validación:

• Validación del estado: propiedades de objetos, valores devueltos, etc.

• Validación del comportamiento o interacción: llamadas a métodos, número de veces, orden, argumentos, etc.

❖ Esto a su vez da lugar a dos estilos de TDD:

• Classicist TDD (Chicago School): basado en la validación del estado

• Mockist TDD (London School): basado en la validación del comportamiento

Classic TDD vs Mockist TDD❖ TDD con mocks permite seguir un diseño outside-in:

• Se empieza creando un test para la capa más externa del sistema y se mockean lo colaboradores

• Posteriormente se desarrollan tests para los colaboradores

• Así sucesivamente se van descubriendo los objetos del sistema y su api

❖ TDD clásico suele seguir un diseño middle-out:

• Se elige una característica del dominio por la que comenzar la implementación

• Se avanza desarrollando el resto de objetos del dominio necesarios para el funcionamiento de dicha característica

• Una vez desarrollada se construye la capa superior

Classic TDD vs Mockist TDD❖ El uso de mocks suele hace que la fase de preparación del tests sea

más costosa ya que hay que definir los mocks y sus expectativas

❖ En el TDD clásico se suele reutilizar el código de configuración entre varios tests, este suele ir el método setUp

❖ Un fallo en un objeto de un sistema con mocks causará fallos sólo en los tests del objeto

❖ Un fallo en un objeto de un sistema clásico puede provocar fallos en los tests de todos los objetos que tienen al objeto como colaborador => puede ser complicado localizar el fallo

❖ Hacer tests de granularidad fina minimizará este problema

❖ Como ventaja los tests clásicos comprueban la integración real de los objetos del sistema

Classic TDD vs Mockist TDD❖ Los tests con mocks están muy acoplados a la

implementación del SUT => cambios en la implementación provocarán ruptura de los tests

❖ Los tests clásicos sólo se preocupan del estado, da igual cual sea la implementación si el resultado es el esperado

❖ La refactorización del código testado con mocks conllevará un trabajo importante de refactorización de los tests

Mockery❖ Librería php que nos permite crear mocks al vuelo

❖ https://github.com/padraic/mockery

❖ http://docs.mockery.io/en/latest/

❖ Instalación:

$ composer require mockery/mockery

Integración con phpunit

public function tearDown() { \Mockery::close();}

<listeners> <listener class="\Mockery\Adapter\Phpunit\TestListener"></listener></listeners>

❖ En el fichero de configuración:

❖ En cada test case:

❖ Si no hacemos esto no se validarán las expectativas

Mockeando$mock = \Mockery::mock(‘foo'); // Mock llamado “foo”

$mock = \Mockery::mock(array('foo'=>1,'bar'=>2)); // Mock sin nombre con array de espectativas

$mock = \Mockery::mock('foo', array('foo'=>1,'bar'=>2)); // Mock “foo” con espectativas

$mock = \Mockery::mock('stdClass'); // Mockeando clases

$mock = \Mockery::mock(‘PSR\Log\LoggerInterface’); // Mockeando interfaces

$mock = \Mockery::mock('stdClass, MyInterface1, MyInterface2'); // Mockeando una clase e implementando interfaces

Definiendo espectativas• shouldReceive(method_name)

// El método debe ser llamado

• shouldReceive(method1, method2, ...) // Varios métodos

• shouldReceive(array('method1'=>1, 'method2'=>2, …)) // Los métodos deben ser llamados y devolverán estos valores

• with(arg1, arg2, ...) / withArgs(array(arg1, arg2, ...)) // validando los parámetros recibidos

• withAnyArgs(), withNoArgs() //…

• andReturn(value) / andReturn(value1, value2, ...) // estableciendo valores devueltos en la llamada / llamadas

• andReturnNull() / andReturn([NULL]) / andReturnValues(array) / andReturnUsing(closure, ...)

Definiendo espectativas• andThrow(Exception) / andThrow(exception_name, message)

// lanzando excepciones en la llamada

• andSet(name, value1) / set(name, value1) // establecer valor de atributos en la llamada

• passthru() // llama a la verdadera implementación del método

• zeroOrMoreTimes() / once() / twice() / times(n) / never() / atLeast() / atMost() / between(min, max) // estableciendo el número de llamadas: exacto, mínimo, máximo, entre

• ordered() / ordered($group) // define el orden de llamadas o el orden dentro de un grupo

• globally() // el orden se entre todos los mocks, no sólo el actual

• byDefault() // define expectativas por defecto, que puden ser sobreescritas en los tests concretos

Validando argumentos• with(‘some-value’) // con un valor concreto

• with(\Mockery::any()) OR with(anything()) // cualquier valor

• with(\Mockery::type('resource')) OR with(resourceValue()) OR with(typeOf(‘resource')) // con un tipo concreto

• with(\Mockery::on(closure)) // validando mediante un closure

• with('/^foo/') OR with(matchesPattern(‘/^foo/')) // regexp

• with(\Mockery::ducktype('foo', ‘bar')) //cualquier objeto que contenga los métodos indicados

• with(\Mockery::mustBe(2)) OR with(identicalTo(2)) // ===

• with(\Mockery::not(2)) OR with(not(2)) // negando

• with(\Mockery::anyOf(1, 2)) OR with(anyOf(1,2)) // varias opciones

Validando argumentos• with(\Mockery::notAnyOf(1, 2)); // que no esté entre las opciones

• with(\Mockery::subset(array(0 => ‘foo’))); // un array que contenga este subconjunto

• with(\Mockery::contains(value1, value2)); // un array que contenga estos valores

• with(\Mockery::hasKey(key)); // un array con una clave

• with(\Mockery::hasValue(value)); // con un valor

Partial Mocks

• $mock = \Mockery::mock(‘MyClass[foo, bar]’);

Método tradicional, mockea los métodos foo y bar de la clase,hay que definir su comportamiento mediante expectativas.

• $mock = \Mockery::mock(‘MyClass')->makePartial();

Método pasivo, las llamadas son enviadas a los métodos originales,excepto si se han definido expectativas para el método.

• $mock = \Mockery::mock(new MyClass);

Mock parcial con proxy, crea un proxy que intercepta las llamadas en lugar de heredar del objeto a mockear, esto permite mockear clases declaradas “final” o con métodos “final”. El inconveniente es que no validará los type hints ya que no extiende la clase a mockear.

❖ Permiten mockear sólo algunos métodos de los objetos

Y más en la documentación…

❖ Mockear atributos públicos

❖ Métodos estáticos

❖ Conservar el paso de parámetros por refencia

❖ Mockear cadenas de Demeter

❖ Grabación de interacciones con objetos reales

❖ …

Ejemplos Reales

Referencias

❖ http://blog.8thlight.com/uncle-bob/2014/05/10/WhenToMock.html

❖ http://blog.8thlight.com/uncle-bob/2014/05/14/TheLittleMocker.html

❖ http://martinfowler.com/articles/mocksArentStubs.html

❖ http://php-and-symfony.matthiasnoback.nl/2014/07/test-doubles/

❖ http://www.jmock.org/oopsla2004.pdf

MUCHAS GRACIAS!!