Repositorio de mi taller en WordCamp Catania 2019
Opcional, pero es posible que necesites instalar Docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
Supongo que tienes Composer instalado. Primero instalemos PHPUnit:
composer require --dev phpunit/phpunit ^8.3
¡Por favor, consulta también los requisitos!
¡PHPUnit 8.3 requiere al menos PHP 7.2! Por cierto, el soporte de seguridad para PHP 7.1 finaliza el 1 de diciembre de 2019.
Pista : ¿No tienes Composer instalado? ¡Prueba esto!
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
Hay al menos dos marcos válidos que resultan útiles cuando planeas probar extensiones de WordPress:
Probemos Brain Monkey :
composer require --dev brain/monkey:2.*`
Esto instalará automáticamente también Mockery y Patchwork. Simplemente ejecute composer install
y estará listo.
Cree un directorio que albergue una pequeña clase de prueba llamada WcctaTest.php :
mkdir -p tests/wccta
¡Excelente! Ahora creemos un archivo de configuración phpunit.xml en el directorio raíz.
También puede decidir ejecutar sus pruebas con los parámetros de configuración desde la línea de comandos. ¡Vea la siguiente parte (pista: 'guiones')!
¡Excelente! Agregue algunas secciones al archivo compositor.json :
composer test
Creemos un directorio que le dará un hogar a nuestro código fuente. Este es el lugar donde pondrás una primera clase que pondrás a prueba pronto.
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
Queremos probar algunos métodos de la clase Plugin
. Imagine un método llamado is_loaded
que devuelve true
en caso de éxito. Cuando estés listo ejecuta:
composer test
Sugerencia : ¿Su sistema o versión de PHP no está actualizado? Podrías saltarte este paso, pero ¡probemos algo [no tan] nuevo!
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
Probablemente puedas imaginar que algunos complementos tendrán muchas clases y que puedes olvidarte fácilmente de probar todas las funcionalidades que necesitan probarse.
Entonces, ¡hablemos de Cobertura !
Simplemente agregue un comando personalizado a la sección de scripts en su compositor.json :
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
y un filtro para tu phpunit.xml :
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
¡Ahora simplemente ejecute composer coverage
! Esto creará un directorio ./reports/php/coverage
junto con algunos archivos html. Bueno, no en todas las computadoras. Algunos seguirán recibiendo mensajes de error como:
Error: No code coverage driver is available
Arreglemos eso en nuestra imagen acoplable. Preparé un Dockerfile para que puedas ejecutar:
docker build -t coverage .
Y una vez finalizado el proceso de construcción:
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
¡Ahora ya sabes Kung Fu! ¡Por favor, abra el archivo ./reports/php/coverage/index.html en su navegador!
Conectemos nuestra clase Plugin
al complemento. Antes de comenzar realmente con las pruebas, le mostraré cómo declarar partes de sus códigos para que no se prueben.
@codeCoverageIgnore
Esta es una de las anotaciones importantes que están disponibles. Volveremos a esto más adelante, pero primero:
¡Ejecute las pruebas unitarias con el informe de cobertura nuevamente!
Quizás hayas notado la columna CRAP
en el informe de cobertura. CRAP es un acrónimo de antipatrones de riesgo de cambio . Indica qué tan riesgoso puede ser un cambio de código en una clase o método. Puede reducir el riesgo (y por lo tanto el índice) con un código menos complejo y una cobertura total con pruebas.
Empecemos a probar algo. ¿Pero qué? Todavía no hay más funciones escritas que deban probarse.
Aquí entra TDD (Test Driven Development) en el juego.
Incluso si decides no utilizar esta técnica, al menos debes saber de qué estamos hablando.
Primero creemos un Test CarTest
que debería probar si el método get_price
devuelve la cadena '€ 14.500'
. Luego cree un Class Car
y escriba el método get_price
que satisfaga la prueba. No empieces con la implementación.
En este punto, permítanme presentarles también el patrón de prueba AAA (Arrange Act Assert), que es ampliamente aceptado en TDD . Describe cómo organizar una prueba y es muy similar a GWT (Given When Then) de BDD (Behavior-driven Development).
Puede probar sus clases si arrojan una excepción en ciertas condiciones. Implementemos ahora el método get_price
.
Simplemente cree una clase Registry
que establezca un valor mixto como un elemento con nombre en una matriz interna. Utilice un método set()
o el método mágico __set()
para esto. En primer lugar, supongamos que podemos pasar un objeto JSON a nuestra clase Car
. Esto le dará a nuestra clase un poco más de valor.
Otro método get
o __get()
debería verificar si existe un elemento con un determinado y devolverlo en caso de éxito. Si no existe tal elemento, lanza una OutOfBoundsException
. Ahora escriba un constructor que maneje la entrada JSON y almacene el objeto en un miembro-var data
. El método get_price
debe tomar el precio de la var data
y encargarse de la salida formateada.
¡Consulte el paso 10 de la rama si tiene dificultades para escribir el código! El price
variable debe ser un número entero. Probablemente esto no sea un problema en este momento porque puede usar la función PHP number_format()
para crear la salida correcta. Pero en una instalación de WordPress , esperará tener configurada la configuración regional, por ejemplo, it_IT
(italiano).
La forma correcta de formatear números en WordPress es el uso de la función number_format_i18n()
.
Así que cambiemos eso y veamos qué pasa:
Error: Call to undefined function wcctanumber_format_i18n()
Arreglaremos esto en un segundo, pero primero preparémoslo un poco. Brain Monkey usa setUp()
y tearDown()
proporcionados por PHPUnit . Puede anular esos métodos. Creemos un TestCase
personalizado (llámelo WcctaCase
) que podamos ampliar porque probablemente lo haremos en cada clase de prueba.
Ahora incluyamos el espacio de nombres para las pruebas en la sección autoload-dev:
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
Finalmente, cambiemos el padre de nuestras clases de prueba.
class CarTest extends WcctaTestCase { // ... }
Estamos listos para burlarnos de nuestra primera función de WordPress con
Functionsexpect( $name_of_function )->andReturn( $value );
Escribir una prueba para una sola expectativa parece demasiado esfuerzo. ¿Qué sucede si desea realizar pruebas con diferentes valores?
Proveedor de datos al rescate. Ya hablé de las anotaciones en el paso 5. Esta también es muy útil:
@dataprovider method_that_returns_data
Echa un vistazo a mi ejemplo. getData
devuelve una matriz de matrices. Cada una de estas matrices contiene 3 valores. Nuestro método test_getPrice
no solo puede aceptar el proveedor de datos con la anotación, sino que también puede definir las variables de entrada como parámetros.
Puede probar sus clases si arrojan una excepción en ciertas condiciones.
Simplemente cree una clase Registry
que establezca un valor mixto como un elemento con nombre en una matriz interna. Utilice un método set()
o el método mágico __set()
para esto.
Otro método get
o __get()
debería verificar si existe un elemento con una clave determinada y devolverlo en caso de éxito. Si no existe tal elemento, lanza una OutOfBoundsException
.
¡Consulte el paso 10 de la rama si tiene dificultades para escribir el código!
Los últimos pasos nos llevaron a Fábricas . ¿Qué es una fábrica? A veces creas funciones o métodos que simplemente ocultan el proceso complejo para crear un objeto específico. Y a veces tienes que decidir qué tipo de objeto quieres crear.
En los complementos de WordPress prefiero agregar ganchos en fábricas a los objetos. Hay complementos que agregan enlaces en constructores de clases. Esto no es algo bueno (especialmente cuando todavía pruebas la forma clásica: crear un entorno completo con WordPress en funcionamiento).
Creemos una clase Factory
con una función estática llamada create
. Este método debería devolver un objeto Car
. Pero refactoricemos el constructor de Car
para que ya espere un objeto y ninguna cadena JSON. En su lugar, haremos esto en el método de creación de la clase Factory
.
Pruebe su complemento ahora con composer test
y verá algunos errores:
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
Deberíamos corregir nuestras pruebas también...
¡Excelente! Creemos una prueba para nuestra Fábrica. Dejaremos el método sin ningún contenido por ahora. ¡Realiza las pruebas nuevamente!
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
Las pruebas pasan pero recibes el mensaje de que hubo una prueba riesgosa. Por cierto: asigne un nombre a la función test_create
simplemente create
y use la anotación @test
. ¡Creo que el uso de esa anotación depende de tu gusto personal!
Ahora profundizaremos un poco más en esto.
Cree una interfaz FooterInterface
que defina info
de un método público que no esperará ningún valor de retorno. Implemente la interfaz en Car
, info
podría, por ejemplo, generar un mensaje divertido.
Defina el tipo de retorno FooterInterface
para el método create
de Factory
y agregue el método de info
de Car
a WordPress-Action wp_footer
.
Ahora probemos esto en FactoryTest
. Hay al menos dos formas de probar esto correctamente. Utilice has_action o ActionsexpectAdded()
. Una prueba de filtros sería similar y está bien descrita en la página vinculada.
Compruebe si composer test
aún pasa todas las pruebas.
¿Cómo está la cobertura ahora mismo? Ejecute composer coverage
y verifique la salida generada.
El método de info
de nuestra clase Car
no está cubierto por ninguna prueba. ¿Pero podemos probar el resultado de un método?
Resulta que es bastante fácil con expectOutputString.
¡Celebremos lo que aprendimos!
Cree una clase Locale
que tenga un método público get
que devuelva get_locale()
. ¡Excluya el método de la cobertura!
Ahora cree un constructor en nuestra clase Plugin
que acepte una instancia Locale
y guárdela en una var miembro $this->locale
. Luego cree un método get_region_code
que devuelva el valor de $this->locale->get()
. Ah, y elimine el método is_loaded
. ;)
En nuestra prueba, podríamos crear un objeto de tipo Locale
, simular la función get_locale
de WordPress y pasarlo al constructor Plugin
. Pero quiero tuse Mocker aquí:
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
¡Ahora puedes hacer que tus complementos de WordPress sean a prueba de balas!
¡Divertirse!