La kata Gilded Rose en PL/SQL: escribiendo los tests
Publicado por Fran Reyes el 07/07/2019
Contexto.
En uno de nuestros actuales clientes, Mutua Tinerfeña, estamos trabajando diferentes técnicas para construir software de manera progresiva y confiable. Aunque el equipo es pequeño, sus miembros usan y conocen tecnologías muy diferentes, por lo que necesitábamos practicar dichas técnicas usando como vehículo un lenguaje que dominaran todos los miembros del equipo. Con esto podíamos evitar que algunos miembros del equipo tuvieran que aprender otros paradigmas para poder practicar las nuevas técnicas. Así que elegimos PL/SQL, que era el lenguaje que todos tenían en común, como vehículo de aprendizaje.
Este ejercicio es parte de un curso sobre refactoring de base de datos que estamos preparando con mucho amor. Este curso está pensado para equipos que trabajan con un legacy en el que la mayoría del código se encuentra en la base de datos y puedan incorporar técnicas como testing y empezar a trabajar su legacy con confianza y de forma más sostenible. Esta primera versión del curso está orientado fundamentalmente a Oracle como SGBD, aunque muchas de las ideas pueden ser trasladadas a otros SGBD como SQLServer.
Aprendiendo refactoring y TDD en PL/SQL
Una de las prácticas que hicimos fue resolver la kata Gilded Rose en PL/SQL para practicar refactoring y TDD. En esta kata lo primero que se debe hacer, antes de añadir la funcionalidad que nos piden, es cubrir el código de tests. Estos tests nos permiten refactorizar el código para hacer que, finalmente, sea fácil añadir la nueva funcionalidad usando TDD. En este post contaremos cómo escribimos los tests para la versión PL/SQL de la kata.
Testeando la kata Gilded Rose en PL/SQL usando utPSQL
La herramienta que usamos para testear el código PL/SQL fue utPSQL que es un framework de testing open-source para PL/SQL and SQL. utPSQL nos permite lanzar los tests de manera muy fácil.
Para escribir los tests hay que crear un paquete[1]. En la especificación del paquete hay que añadir una serie de anotaciones, y, por último, escribir el propio test en el cuerpo del paquete.
Para lanzar todos los tests desde la base de datos[2] hay que hacer la siguiente llamada a la “librería”:
begin ut.run(); end;
Esta llamada buscará en el schema todos los paquetes que contengan las anotaciones y lanzará sus tests. Existe también la posibilidad de lanzar sólo los tests de un determinado paquete indicándolo como un argumento.
Estos son los tests para la kata Gilded Rose en PL/SQL:
CREATE OR REPLACE PACKAGE test_update_quality | |
IS | |
--%suite(Update quality) | |
--%beforeeach | |
PROCEDURE setup; | |
--%test(sell in decreases every day) | |
PROCEDURE sell_in_decreases_every_day; | |
--%test(quality decreases every day) | |
PROCEDURE quality_decreases_every_day; | |
--%test(quality decreases twice as fast one sell in has passed) | |
PROCEDURE quality_decreases_twice; | |
--%test(quality is never negative) | |
PROCEDURE quality_is_never_negative; | |
--%test(aged brie increases quality) | |
PROCEDURE aged_brie_increases_quality; | |
--%test(item never increase quality when has reached the maximum) | |
PROCEDURE quality_has_a_maximun; | |
--%test(sulfuras never changes the quality) | |
PROCEDURE sulfuras_never_changes_quality; | |
--%test(sulfuras never changes the sell in) | |
PROCEDURE sulfuras_never_changes_sell_in; | |
--%test(backstage increase quality) | |
PROCEDURE backstage_increase_quality; | |
--%test(backstage increase quality by 2 when sell in is 10 or less) | |
PROCEDURE bkstg_q_by_2_sellin_is_10_less; | |
--%test(backstage increase quality by 3 when sell in is 5 or less) | |
PROCEDURE bkstg_q_by_3_sellin_is_5_less; | |
--%test(backstage drops to 0 quality after the concert) | |
PROCEDURE bkstg_q_drops_0_after_concert; | |
--%test(aged brie expired gets the maximum quality when has the quality minus 1) | |
PROCEDURE aged_brie_expired_the_max_q; | |
END test_update_quality; | |
/ |
CREATE OR REPLACE PACKAGE BODY test_update_quality | |
IS | |
expired_sellin CONSTANT int := 0; | |
minimun_quality CONSTANT int := 0; | |
maximum_quality CONSTANT int := 50; | |
PROCEDURE expectQualityToBe(qualityExpected IN int) IS | |
quality item.quality%TYPE; | |
BEGIN | |
SELECT QUALITY INTO quality FROM item; | |
ut.expect(quality).to_equal(qualityExpected); | |
END; | |
PROCEDURE expectSellinToBe(sellinExpected IN int) IS | |
sell_in item.SELL_IN%TYPE; | |
BEGIN | |
SELECT SELL_IN INTO sell_in FROM item; | |
ut.expect(sell_in).to_equal(sellinExpected); | |
END; | |
PROCEDURE add_regular_product_with(sell_in item.sell_in%TYPE, | |
quality item.quality%TYPE) | |
IS | |
BEGIN | |
new_item('any_product', sell_in, quality); | |
END; | |
PROCEDURE add_aged_brie_with(sell_in item.sell_in%TYPE, | |
quality item.quality%TYPE) | |
IS | |
BEGIN | |
new_item('Aged Brie', sell_in, quality); | |
END; | |
PROCEDURE add_sulfuras_with(sell_in item.sell_in%TYPE, | |
quality item.quality%TYPE) | |
IS | |
BEGIN | |
new_item('Sulfuras, Hand of Ragnaros', sell_in, quality); | |
END; | |
PROCEDURE add_backstage_with(sell_in item.sell_in%TYPE, | |
quality item.quality%TYPE) | |
IS | |
BEGIN | |
new_item('Backstage passes to a TAFKAL80ETC concert', sell_in, quality); | |
END; | |
------------ TESTS ---------- | |
PROCEDURE setup IS | |
BEGIN | |
DELETE FROM item; | |
END; | |
PROCEDURE sell_in_decreases_every_day | |
IS | |
BEGIN | |
add_regular_product_with(sell_in => 5, quality => 15); | |
update_quality(); | |
expectSellinToBe(4); | |
END; | |
PROCEDURE quality_decreases_every_day | |
IS | |
BEGIN | |
add_regular_product_with(sell_in => 5, quality => 15); | |
update_quality(); | |
expectQualityToBe(14); | |
END; | |
PROCEDURE quality_decreases_twice | |
IS | |
BEGIN | |
add_regular_product_with(sell_in => expired_sellin, quality => 15); | |
update_quality(); | |
expectQualityToBe(13); | |
END; | |
PROCEDURE quality_is_never_negative | |
IS | |
BEGIN | |
add_regular_product_with(sell_in => expired_sellin, quality => minimun_quality); | |
update_quality(); | |
expectQualityToBe(minimun_quality); | |
END; | |
PROCEDURE aged_brie_increases_quality | |
IS | |
BEGIN | |
add_aged_brie_with(sell_in => 5, quality => 15); | |
update_quality(); | |
expectQualityToBe(16); | |
END; | |
PROCEDURE quality_has_a_maximun | |
IS | |
BEGIN | |
add_aged_brie_with(sell_in => 5, quality => maximum_quality); | |
update_quality(); | |
expectQualityToBe(50); | |
END; | |
PROCEDURE sulfuras_never_changes_quality | |
IS | |
BEGIN | |
add_sulfuras_with(sell_in => 4, quality => 30); | |
update_quality(); | |
expectQualityToBe(30); | |
END; | |
PROCEDURE sulfuras_never_changes_sell_in | |
IS | |
BEGIN | |
add_sulfuras_with(sell_in => 4, quality => 30); | |
update_quality(); | |
expectSellinToBe(4); | |
END; | |
PROCEDURE backstage_increase_quality | |
IS | |
BEGIN | |
add_backstage_with(sell_in => 15, quality => 30); | |
update_quality(); | |
expectQualityToBe(31); | |
END; | |
PROCEDURE bkstg_q_by_2_sellin_is_10_less | |
IS | |
BEGIN | |
add_backstage_with(sell_in => 10, quality => 30); | |
update_quality(); | |
expectQualityToBe(32); | |
END; | |
PROCEDURE bkstg_q_by_3_sellin_is_5_less | |
IS | |
BEGIN | |
add_backstage_with(sell_in => 5, quality => 30); | |
update_quality(); | |
expectQualityToBe(33); | |
END; | |
PROCEDURE bkstg_q_drops_0_after_concert | |
IS | |
BEGIN | |
add_backstage_with(sell_in => expired_sellin, quality => 30); | |
update_quality(); | |
expectQualityToBe(0); | |
END; | |
PROCEDURE aged_brie_expired_the_max_q | |
IS | |
BEGIN | |
add_aged_brie_with(sell_in => expired_sellin, quality => maximum_quality - 1); | |
update_quality(); | |
expectQualityToBe(maximum_quality); | |
END; | |
END test_update_quality; | |
/ |
Aunque pueda parecer sorprendente, los tests resultantes son bastante legibles. La legibilidad de los tests, en comparación con otras librerías similares, es uno de los puntos a favor de utPSQL.
Conclusiones
Hemos visto como es posible hacer testing en PL/SQL usando utPSQL. Estos tests crearán una red de seguridad que nos permitirá refactorizar el código, o lo que es lo mismo, mejorar su diseño preservando su comportamiento.
En el próximo post de esta serie enseñaremos como se pueden aplicar técnicas de refactoring y de diseño para mejorar el código PL/SQL de la kata Gilded Rose en pequeños pasos manteniendo los tests en verde en todo momento.
Agradecimientos
Me gustaría agradecer a mi compañero Manuel Rivero por ayudarme a revisar y editar este post, y al equipo de Mutua Tinerfeña por las aportaciones ofrecidas desde su amplia experiencia en este entorno.