This time I’ll try to show you a proposal of testing on SQL by using this clear sections:
Ok… hands on:
This time, the problem we have to solve is to get all the movies in which they work together a range of actors.
To make it, in a «bdd way» the first step is to describe some samples to assure that all the stakeholders understand the behavior:
«If I use an actor’s id that appears on 21 movies, when I query the movies of this actor, then I must receive the 21 movie’s ids where the actor appears on.»
Another example could be: «If I use the identifiers of two actors who appear in a single film in common, when I query the common movies of that actors, then I must receive one movie identifier, and that movie is the only one where these actors acts in common».
The last example will be that: «If I use the identifiers of two actors who appear in NO film in common, when I query the common movies of that actors, then I do not must receive no film identifier»
Now I’ll try to show a way to describe the samples by using SQL.
Important note: If you wanna test the code, you’ll need to obtain the »Sakila Sample Database’ (http://dev.mysql.com/doc/sakila/en/) and the assertions and example routines are hosted on my github account (https://github.com/pvergara/Tests4Sql).
First example:
/*****************************************************************/ SET @actorKevinBloom_id=25; SELECT @expected:=COUNT(*) FROM `sakila`.`film_actor` WHERE actor_id = @actorKevinBloom_id; SELECT concat('One actor that appears in ',@expected,' movies') as "Given", @actorKevinBloom_id; /*****************************************************************/ /*****************************************************************/ SET @actors_id=concat(@actorKevinBloom_id) ; SELECT 'I query the films of that actor' as "When", @actors_id as "(Actor id)"; CALL `sakila`.`GetTheCommonFilmsOf`(@actors_id); /*****************************************************************/ /*****************************************************************/ SELECT @actual:=COUNT(*) FROM `sakila`.`ResultGetTheCommonFilmsOf`; SELECT concat('It returns ',@expected,' films') as "Then", @expected as "Expected", @actual as "Actual", `Tests4Sql`.`AssertEquals_DECIMAL`(@expected,@actual) as "Assert"; /*****************************************************************/
Second example:
/*****************************************************************/ SET @actorKevinBloom_id=25, @actorChristianAKROYD_id=58; SELECT 'Two actors that appears in the same movie' as "Given", @actorKevinBloom_id, @actorChristianAKROYD_id; /*****************************************************************/ /*****************************************************************/ SET @actors_id=concat(@actorKevinBloom_id, ',', @actorChristianAKROYD_id) ; SELECT 'I query the common films of that two actors' as "When", @actors_id as "(two actors)"; CALL `sakila`.`GetTheCommonFilmsOf`(@actors_id); /*****************************************************************/ /*****************************************************************/ SELECT @actual:=COUNT(*) FROM `sakila`.`ResultGetTheCommonFilmsOf`; SELECT 'It returns the exact number of common films' as "Then", @expected:=1 as "Expected", @actual as "Actual", `Tests4Sql`.`AssertEquals_DECIMAL`(@expected,@actual) as "Assert"; /*****************************************************************/ /*****************************************************************/ SELECT 'The common films are exactly ''Sabrina Midnight'' and no more' as "Then"; CALL `Tests4Sql`.`spAssertSameResult` ( '`sakila`.`ResultGetTheCommonFilmsOf`', '(SELECT * FROM `sakila`.`film` WHERE film_id = 755) qryNew', 'qryTheOldOne.`film_id` = qryTheNewOne.`film_id`' ) /*****************************************************************/
Third example:
/*****************************************************************/ SET @actorKevinBloom_id=25, @actressPenelopeGUINNESS_id=1; SELECT 'Given two actors who DO NOT appear in any common film' as "Given", @actorKevinBloom_id, @actressPenelopeGUINNESS_id; /*****************************************************************/ /*****************************************************************/ SET @actors_id=concat(@actorKevinBloom_id, ',', @actressPenelopeGUINNESS_id) ; SELECT 'I query the common films of that two actors' as "When", @actors_id as "(two actors)"; CALL `sakila`.`GetTheCommonFilmsOf`(@actors_id); /*****************************************************************/ /*****************************************************************/ SELECT @actual:=COUNT(*) FROM `sakila`.`ResultGetTheCommonFilmsOf`; SELECT 'It returns the exact number of common films (no movie)' as "Then", @expected:=0 as "Expected", @actual as "Actual", `Tests4Sql`.`AssertEquals_DECIMAL`(@expected,@actual) as "Assert"; /*****************************************************************/
Hope this helps (maybe someone who want to refactor her sql routines 😉 )
Now I’m going to show you more complex code and a sample case.
This time the SQL was written using MySQL.
At this time I’m gonna use two different schemas:
Ok…hands on:
First the code for the test functionallity:
DROP PROCEDURE `Tests4Sql`.`spAssertSameResult`; DELIMITER // CREATE PROCEDURE `Tests4Sql`.`spAssertSameResult` ( oldQryName varchar(256), newQryName varchar(256) ) BEGIN SET @dyn_sql=CONCAT(' SELECT ( CASE WHEN count(*) = (select count(*) from ',oldQryName,') THEN ''GREEN'' ELSE ''RED!!!'' END ) as "Assert_SqlCodeSeemsSimilar" FROM ( SELECT * FROM ',newQryName,' ) qryTheNewOne INNER JOIN ( SELECT * FROM ',oldQryName,' ) qryTheOldOne ON ( qryTheNewOne.city_id = qryTheOldOne.city_id );'); PREPARE s1 from @dyn_sql; EXECUTE s1; DEALLOCATE PREPARE s1; END // DELIMITER ;
Second the same functionality written in different ways:
DROP PROCEDURE `sakila`.`sp_add_some_spanish_cities_hard_to_mantain_version`; DELIMITER // CREATE PROCEDURE `sakila`.`sp_add_some_spanish_cities_hard_to_mantain_version`() BEGIN /* List extracted from the wikipedia: http://es.wikipedia.org/wiki/Anexo:Provincias_de_Espa%C3%B1a_por_c%C3%B3digo_postal */ IF NOT EXISTS(select * from `sakila`.`city` WHERE `sakila`.`city`.`city_id` = 8701) THEN INSERT INTO `sakila`.`city` ( city_id, city, country_id ) VALUES ( 8701, 'Álava (Vitoria)', 87 ); ELSE UPDATE `sakila`.`city` SET city = 'Álava (Vitoria)', country_id = 87 WHERE city_id = 8701; END IF; IF NOT EXISTS(select * from `sakila`.`city` WHERE `sakila`.`city`.`city_id` = 8702) THEN INSERT INTO `sakila`.`city` ( city_id, city, country_id ) VALUES ( 8702, 'Albacete', 87 ); ELSE UPDATE `sakila`.`city` SET city = 'Albacete', country_id = 87 WHERE city_id = 8702; END IF; IF NOT EXISTS(select * from `sakila`.`city` WHERE `sakila`.`city`.`city_id` = 8703) THEN INSERT INTO `sakila`.`city` ( city_id, city, country_id ) VALUES ( 8703, 'Alicante', 87 ); ELSE UPDATE `sakila`.`city` SET city = 'Alicante', country_id = 87 WHERE city_id = 8703; END IF; IF NOT EXISTS(select * from `sakila`.`city` WHERE `sakila`.`city`.`city_id` = 8704) THEN INSERT INTO `sakila`.`city` ( city_id, city, country_id ) VALUES ( 8704, 'Almería', 87 ); ELSE UPDATE `sakila`.`city` SET city = 'Almería', country_id = 87 WHERE city_id = 8704; END IF; IF NOT EXISTS(select * from `sakila`.`city` WHERE `sakila`.`city`.`city_id` = 8705) THEN INSERT INTO `sakila`.`city` ( city_id, city, country_id ) VALUES ( 8705, 'Ávila', 87 ); ELSE UPDATE `sakila`.`city` SET city = 'Ávila', country_id = 87 WHERE city_id = 8705; END IF; /*... and so on*/ END // DELIMITER ;
DROP PROCEDURE `sakila`.`sp_add_some_spanish_cities_another_way_to_make_it_version`; DELIMITER // CREATE PROCEDURE `sakila`.`sp_add_some_spanish_cities_another_way_to_make_it_version`() BEGIN /* List extracted from the wikipedia: http://es.wikipedia.org/wiki/Anexo:Provincias_de_Espa%C3%B1a_por_c%C3%B3digo_postal */ CREATE TABLE in_memory_aux_table ENGINE = MEMORY select 8701 as id, 'Álava (Vitoria)' as name, 87 as country_id UNION ALL select 8702 as id, 'Albacete' as name, 87 as country_id UNION ALL select 8703 as id, 'Alicante' as name, 87 as country_id UNION ALL select 8704 as id, 'Almería' as name, 87 as country_id UNION ALL select 8705 as id, 'Ávila' as name, 87 as country_id ; /*... and so on*/ UPDATE `sakila`.`city` INNER JOIN in_memory_aux_table as qry ON (`sakila`.`city`.`city_id` = qry.id) SET `sakila`.`city`.city = qry.name, `sakila`.`city`.country_id = qry.country_id ; INSERT INTO `sakila`.`city` ( city_id, city, country_id ) SELECT qry.id, qry.name, qry.country_id FROM in_memory_aux_table as qry LEFT JOIN `sakila`.`city` as cities ON (qry.id = cities.city_id) WHERE cities.city_id IS NULL; DROP TABLE in_memory_aux_table; END // DELIMITER ;
And third… the test!!!:
USE `sakila`; CALL `sakila`.`sp_add_some_spanish_cities_hard_to_mantain_version`(); CREATE TABLE first_attempt ENGINE=MEMORY SELECT `city`.`city_id`, `city`.`city`, `city`.`country_id`, `city`.`last_update` FROM `sakila`.`city` WHERE `sakila`.`city`.country_id = 87 AND `sakila`.`city`.city_id >=8701; CALL `sakila`.`sp_add_some_spanish_cities_another_way_to_make_it_version`(); CREATE TABLE second_attempt ENGINE=MEMORY SELECT `city`.`city_id`, `city`.`city`, `city`.`country_id`, `city`.`last_update` FROM `sakila`.`city` WHERE `sakila`.`city`.country_id = 87 AND `sakila`.`city`.city_id >=8701; CALL `Tests4Sql`.`spAssertSameResult`('`sakila`.`first_attempt`','`sakila`.`second_attempt`'); DROP TABLE first_attempt; DROP TABLE second_attempt;
Hope this helps!!!!.
Try this one:
SELECT [Assert_SqlCodeSeemsSimilar] = --Maybe this part must be encapsulated on some kind of "fnTest4SQL_SameResult" scalar-function ( CASE WHEN count(*) = <qryTheOldOne_Counter> THEN 'GREEN' ELSE 'RED!!!' END ) FROM ( <The new code> ) qryTheNewOne INNER JOIN ( <The old code> ) qryTheOldOne ON ( qryTheNewOne.SomeField1 = qryTheOldOne.TheSameField1 AND --The number of "AND statements" depends on the number of fields you want to join. -- Is important to think that, in some cases, you only need <Id_Fields> (for example -- if the queries only returns fields related with well-normalized database tables) BUT -- ALSO you must have to be careful with calculated fields. qryTheNewOne.SomeField2 = qryTheOldOne.TheSameField2 ... ... ... AND --Null values sample ( qryTheNewOne.SomeFieldN_NumberWithNullValues = qryTheOldOne.SomeFieldN_NumberWithNullValues OR ( qryTheNewOne.SomeFieldN_NumberWithNullValues IS NULL AND qryTheOldOne.SomeFieldN_NumberWithNullValues IS NULL ) ) )
Hope this helps
An early implementation of what, I think, should be an assertEquals on «SQL code»
Please try to «translate it» to another SQL-Syntax (just like ANSI-SQL), because I will write using de T-SQL syntax:
CREATE FUNCTION dbo.fnTest4SQL_AssertTrue ( @Expected as bit, @Actual as bit ) RETURNS NVARCHAR(256) AS BEGIN DECLARE @RESULT AS nvarchar(256) SELECT @RESULT = ( CASE WHEN @Expected=@Actual THEN 'GREEN' ELSE '______RED______' END ) RETURN @RESULT END GO
Hope this helps
Este fin de semana iba yo a ponerme a hacer cosas con Linux, y me dio una pereza enorme. Después de mucho pensar (y darme un poco de lástima porque yo siempre fui un «linuxero» bastante radical) llegué a la conclusión de que hace tiempo empecé a perder el interés (y las ganas de usar linux) por un único motivo: La distro que llevaba años usando, había cambiado de tal forma que me generaba cierta aversión.
El problema no era sólo la parte visual aunque seguramente fue lo que mas influyó, también había problemas con drivers y no sé, otros «»»pequeños detalles»»» que en el día a día van restando puntos y al final, generan una sensación de desasosiego muy grande al ir a utilizarla.
Conclusión: UBUNTU AL CARAJO!!!!.
Paso a enseñarnos el mail que envíe a unos expertos en la materia para que me aconsejasen qué distro usar:
(La versión original del mail era un poco, vehemente, y he decidido «rebajarla»)
A las buenas nenos.
Mirad, estoy cansado… hastiado … vamos HASTA ARRIBA, del puñetero Ubuntu, quiero un linux de verdad!!!! y necesito que me recomendéis.
Estaba a punto de descargarme el Debian pero me acordé de «su lado oscuro»: ‘Mais pechado que unha porta que sempre se abre polo outro lado’, super radical de la muerte (como me dé por mirar flash… voy de fastidiado… supongo), y tardaré en actualizar a siguientes versiones AÑOS (entre forzen… pre-frozen y post-frozen…jajaja).
Pero bueno si no hay mas remedio tiro por él… lo que si me gustaría es que la distro que elija tenga en cuenta mas o menos estos puntos:
Bueno ahí va todo, a ver si me podéis ayudar… y sino… volveré a Debian…jajaja.
Quiero volver a enamorarme de Linux…y una buena distro ayuda bastante (y dejar ubuntu… mas!!!!!)
Fué un mail muy cartártico, y muy útil porque ese mismo día estaba instalando una nueva distribución de linux con la que estoy muuuuy contento…. pero de eso hablaré en el siguiente post.
Un saludo!!!
Por cierto gracias a los sabios que me recomendaron la distro :-).
This is the first entity-relationship diagram (ERD) of a series of posts I want to publish with the ERD of the most famous open-source projects nowadays.
This is my first post in English too so, even if I hope to not make any mistakes I’d feel very thankful if anyone found an error and mail me (pablocristobal@gmail.com) to improve these publications.
Visual Architect (Professional edition): This is the tool I’m using to make the diagrams. I do know open-source tools like mysql-workbench (I’m working with that one too), dia, and commercial software like Microsoft Visio (the company one I’m working on are Microsoft Partners and I’m using this one too) to make entity-relationship diagrams, but I’m pretty sure that Visual Architect is better than the others.
Hope this helps someone.
Quique I wanna thank you for the review 🙂
Llevo mucho tiempo sin escribir en el blog y, sinceramente, cuando lo cree, no estaba dentro de mis planes escribir entradas «de opinión» sobre productos (y menos aun, si estos productos no tienen relación directa con el mundo del desarrollo del software), pero, es que veo la necesidad de expresarme, y carajo, no voy a crear otro blog sólo para esto… digo yo :-|.
Bueno al tema, esta nueva entrada se centra en comentar mi experiencia (triste, traumática, y sin ningún «final feliz») con la nueva versión EN BETA (primer chiste), del navegador web gratuíto (pero no para todo el mundo) de la compañía de Redmon: Internet Explorer 9 (al que le gustaría que aquí hubiera puesto un enlace… que espere sentadito, que no voy a facilitarle las cosas a ningún martir).
A ver, antes que nada indicar que lo que pongo en la cabecera, para mi, no es coña alguna, que la peña de Microsoft tenga el valor de llamar a este producto Beta, ya de por si no tiene desperdicio. O eso, o es que para mi el concepto de Beta es mas estricto que alguno de estos puntos:
AAAAAgggghh…. carajo, que agusto me he quedado.
Bueno, ahora, la coña es que, este supermegaproducto, que, parece ser que es la leche en el AcidTest3, que es el mas estándar del mundo con el html5, además que «es una mooonaaadaaaaa» (¿donde carajo están las fotos de Kitty cuando las necesito?)… sooooloo tiene, nada, un detalle sin importancia… una minucia de nada que lo hace… como menos asequible que los demás… y por eso muuuucho mas interesante: EL CARAJO DEL NAVEGADOR SOLO PARECE QUE SERVIRÁ PARA LAS VERSIONES DE VISTA 32Bits (ni la peña de MS, se dió cuenta que había otra versión del vista… ironías de la vida) O PARA WIN7… ahora si 32/64Bits… y yo me pregunto… no no, de verdad, ¿Porqué un producto de software tan imprescindible como es un navegador, en vez de ser lo mas abierto posible, sólo sirve para dos de las versiones del S.O.?…a ver, que soy informático, y puedo entender los motivos técnicos (tecnología .NET… digo yo), potenciar las últimas versiones y enterrar de una vez el XP, etc. Pero, es que la pregunta la hago desde «mi lado» cliente o mejor dicho, consumidor de un software y, sinceramente, cuando chrome, Firefox u otros navegadores lleguen a los niveles de puntos cumplidos en el AcidTest3 como lo hace elIE9 (bueno… o eso dicen, porque, si no ha quedado claro aun, el menda no ha podido probarlo 😦 )… seguirán funcionando para todo tipo de versiones de Windows, ADEMÁS, de para otros sistemas operativos, tanto de escritorio como de móviles… o sea que, sinceramente, no sé para qué carajo se matan en sacar un navegador con tan buena pinta, y ponerlo tan difícil para que la peña lo pruebe… y lo que es mas, yo recomendaría que, DE VERDAD, dejen de llamar a ESO «Beta».
Un saludo.
P.D. Moi, tío, te lo juro que lo he intentado, pero no hay huev… de probar el navegador de marras… por cierto, esta entrada «va por ti».
Hace un par de días me encontraba con mi padre comentando un «cambio de rumbo» que ha dado mi vida en el entorno laboral. Uno de los temas que tocamos fue relativo al hecho de que, recientemente estoy empezando a replantearme algunas «ideas puristas» a la hora de desarrollar software… dicho de otra forma, le doy muchas vueltas a las cosas para intentar que los sistemas que desarrollo sean lo mas estructurados posible, lo mejor comentados posible, lo mejor diseñados posible, lo mas separados en capas posible…. y eso lleva su tiempo, y claro, si lo que uno tiene que hacer es una «web chorra» o un programa, así como muy simplón, el tiempo que tardo en hacer las cosas no siempre es el esperado por el cliente.
Cuando le estaba contando esto a mi progenitor, el me comentó que «en el desarrollo de software, al igual que en cualquier ingeniería, hay que intentar que los diseños y los desarrollos, sean eficaces, pero también eficientes»… sobretodo en la empresa privada con mucha competencia.
Antes que comentar mis pensamientos sobre el tema, os recomiendo que leáis esta entrada de la Wikipedia… la parte que mas me gusta de esa Wiki es la frase que dice: «Ejemplo: matar una mosca de un cañonazo es eficaz (o efectivo: conseguimos el objetivo) pero poco eficiente (se gastan recursos desmesurados para la meta buscada). Pero acabar con su vida con un matamoscas, aparte de ser eficaz es eficiente.»
Ahora, ¿que tienen que ver las definiciones de eficaz y eficiente con el desarrollo del software?, muy simple, un desarrollo eficaz en un desarrollo que cumple con los requisitos del cliente, y un desarrollo eficiente, además, ha consumido «pocos recursos»; dicho de otra forma, hemos necesitado un equipo reducido de personas para hacerlo y lo hemos desarrollado en poco tiempo.
En el mundo del desarrollo de software (como en otras disciplinas, como la arquitectura, la ingeniería de minas, la ingeniería industrial, etc.) tenemos que tender a que nuestros desarrollos sean eficaces, pero también eficientes. Esta máxima cobra especial importancia si nuestros trabajos se realizan dentro de empresas privadas, y mas aun, cuando dichas empresas tienen mucha competencia. El motivo es obvio, cuanta mas gente participe en un proyecto mas coste tiene dicho proyecto, cuantas mas horas se invierten en un proyecto, mas coste tiene.
Después de toda esta exposición llega la hora de hacerse preguntas:
¿Es mas eficiente un desarrollo de un código «cogido con pinzas» (en el que está todo mezclado, la mitad de los datos están en base de datos, y la otra mitad en código, etc) que un desarrollo «»»»»bien hecho»»»»» (con su código bien separado en capas, con sus pruebas unitarias y de integración, con su documentación impoluta… y en inglés, su base de datos diseñada desde una herramienta CASE, su ORM bien puestecito, etc.)?… pues, me guste o no, he de admitir que si, es un desarrollo mas eficiente.
Ahora, ¿cual de los dos desarrollos es mas mantenible?, puede parecernos que la segunda forma de desarrollar sin lugar a dudas es mas mantenible, y seguramente lo sea, pero: 1º Seguirá siendo mucho mas costosa (menos eficiente) y 2º quizás no tardemos mucho menos en mantener el sistema que el otro, puesto que, cada nuevo campo en la tabla, cada nuevo requisito, cada cambio en el diseño, requiere que estemos varias horas manteniendo toda la estructura con tantos elementos alrededor del programa, pero que no son necesarios para que el programa funcione (pruebas, documentación, diagramas, …). Ojo, aquí no estoy discutiendo que los elementos «»prescindibles»» no tengan su valor por ejemplo, asegurar la calidad del producto y protegerlo frente a los cambios (pruebas), o para mejorar la comunicación entre los miembros de un equipo (documentación y diagramas), lo que digo es que, si lo hacemos todo, y todo a la perfección, quizás no vamos a tardar mucho menos en mantener un sistema tan completo (y complejo) que un sistema de tipo «código espagueti», puesto que, además de realizar los cambios en el código (eso será muuuuy rápido), también tenemos que mantener todos los demás elementos (nuevas pruebas unitarias, modificaciones en diagramas, etc.).
Después de estas reflexiones, quiero exponer mis conclusiones personales (que intentaré acatar a partir de ahora):
Con el tiempo, nuestra forma de desarrollar será eficaz y, también será eficiente, tanto a corto plazo (puesto que tardaremos poco en entregar nuestros desarrollos), como a largo plazo (puesto que tardaremos poco en hacer frente a los cambios requeridos, o osea, lo haremos mas mantenible).
Estas son mis reflexiones, espero algún comentario.
Actualmente me encuentro escribiendo una serie de post dedicado a una de mis mayores pasiones dentro del mundo del desarrollo de software: Las bases de datos, y bueno, ayer estuve un buen rato preparando una serie de diagramas entidad-relación de uno de los proyectos de software libre mas populares del mundo el Mediawiki (el proyecto detrás de la Wikipedia).
El caso es que, para mi sorpresa, el DDL utilizado para crear la base de datos en su versión para MySQL no cuenta con claves foráneas, lo mas curioso es que no es la primera vez que me encuentro proyectos, ya sean opensource, ya sean privativos, que cuentan con bases de datos que no utilizan con integridad referencial. Otro ejemplo que me tocó revisar hace unos años que creo recordar que no utilizaba esta característica es el «SugarCRM«. A lo largo de mi vida profesional he tenido que trabajar/mantener/revisar diversos sistemas que tampoco contaban con FK’s, incluso el propio MySQL cuenta con varias tecnologías de almacenamiento y, creo recordar que sólo InnoDB cuenta con la posibilidad de trabajar con integridad referencial
Después de esta reflexión, me gustaría saber qué pensáis vosotros sobre el tema ¿está sobrevalorada la integridad referencial? ¿pasa «algo malo» si no la utilizamos? (total, la podemos emular en nuestro sistema, y es mas fácil migrar la información sin utilizarla).
Personalmente soy demasiado «pureta» por lo que yo me decanto por utilizarla, pero ¿qué opinión tenéis al respecto?.
Un saludo.