Externalizar literales en los test

8 views
Skip to first unread message

yeraycaballero

unread,
Oct 25, 2010, 3:05:17 AM10/25/10
to Artesanos de Software

En ocasiones me encuentro en situaciones en la que incluyo literales
en los test unitarios. Así por ejemplo cuando implemento un parser de
un xml, tendría un test de la siguiente manera:

test {
xml = "<definition name="field_text"></<definition>" // literal
parser = new Parser();
definition = parse.parse(xml)

asserts...
}

Con el tiempo los xml cambian y aunque este cambio se captura en los
test de integración, donde se usan los archivos xml's reales, dejan
obsoletos los test unitarios.

¿Existe o se les ocurre alguna manera de externalizar estos literales?

Gracias.
Yeray Caballero

José Manuel Beas

unread,
Oct 25, 2010, 4:09:08 PM10/25/10
to artesanos-...@googlegroups.com
Quizás te estás acoplando con los literales o con el formato del xml. Quizás si está cambiando el xml también tiene que cambiar tu test.

¿Qué estás tratando de probar exactamente?

yeraycaballero

unread,
Oct 25, 2010, 6:56:14 PM10/25/10
to Artesanos de Software
Hola Jose Manuel, gracias por tu respuesta, intentaré explicarme un
poco mejor.

Lo que trato de probar depende del tipo del test. Con los test
unitarios lo que quiero es probar que el objeto se reconstruya
correctamente. Los literales en los test unitarios me permiten
especificar los casos concretos en los que puede venir el xml y
comprobar así, si el parseador se comporta correctamente.

Con los tests de integración trato de protegerme de aquellos cambios
en la estructura de los archivos xmls. Teniendo en cuenta que pueden
cambiar sin yo enterarme.

Desde luego, existe una dependencia fuerte entre el parseador y la
estructura del xml y por eso me pregunto si existe una forma de
mantener los test unitarios de alguna manera más automática.

¿Quizás, en estos casos no se deberían usar literales?, aunque no me
encaja ya que me resultan realmente prácticos.

Un saludo,
Yeray Caballero

José Manuel Beas

unread,
Oct 25, 2010, 7:11:48 PM10/25/10
to artesanos-...@googlegroups.com
No entiendo del todo. Entiendo que estás escribiendo trozos de XML en cadenas que luego pasas al parseador en tus tests y que, cuando cambia el formato de tus XML te molesta el tener que recorrer el código de tus tests para modificar las cadenas. Se me ocurren varias alternativas:
1) externalizar los xml en ficheros (hay un hilo creo que en la lista de TDD-es justamente sobre esto) pero así perderás la riqueza de ver el contenido del fichero en el propio test
2) que los nombres de los elementos y atributos los escribas como constantes (probablemente pierdas en fluidez a la hora de leer y escribir los tests)
3) hacerte una suerte de builders que te permitan construir la cadena que representa el XML (es un compromiso entre ambas soluciones y es la que probablemente te recomendaría Alfredo Casado en ese mismo hilo que te nombraba antes)

Supongo que en el fondo se trata de un compromiso entre la claridad de los tests y la facilidad para asumir cambios y que tendrá mucho que ver con lo estable/inestable que sea el formato del XML que quieres probar. Cuanto más cambien más peso le darás a que sean fáciles de cambiar.

Un saludo,
Jose Manuel Beas

Eduardo Ferrández

unread,
Oct 26, 2010, 5:06:54 AM10/26/10
to artesanos-...@googlegroups.com
Yo en este caso construiría el xml desde lo pequeño hasta lo grande usando clases independientes para obtener el xml de cada nodo. Esto es:

Si quiero probar un xml de este tipo:
<nodoRaiz>
    <nodoHijo>texto</nodoHijo>
</nodoRaiz>

haría lo siguiente

class NodoRaiz {
   getXML() {
       return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoHijo";
   }
}

class NodoHijo {
   NodoHijo(texto) {
      this.texto = texto;
   }
   getXML() {
      return "<nodoHijo>" + texto +"</nodoHijo>";
   }
}


Esto te permite probar tu parser poco a poco (primero el nodo hijo y luego el nodo raíz,....) y localizar los cambios en un único lugar sin que afecte al resto. El código es muy mejorable, pero creo que refleja bien la idea.

Eduardo Ferrández

unread,
Oct 26, 2010, 5:08:47 AM10/26/10
to artesanos-...@googlegroups.com
Perdón, donde dice 
return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoHijo";
debería decir
return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoRaiz>";

jcesarperez

unread,
Oct 26, 2010, 12:03:16 PM10/26/10
to Artesanos de Software
¿El XML no se construye y valida contra un schema XSD?

On 26 oct, 11:08, Eduardo Ferrández <eduardoferrand...@gmail.com>
wrote:
> Perdón, donde dice
> return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoHijo";
> debería decir
> return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoRaiz>";
>
> El 26 de octubre de 2010 11:06, Eduardo Ferrández <
> eduardoferrand...@gmail.com> escribió:
>
> > Yo en este caso construiría el xml desde lo pequeño hasta lo grande usando
> > clases independientes para obtener el xml de cada nodo. Esto es:
>
> > Si quiero probar un xml de este tipo:
> > <nodoRaiz>
> >     <nodoHijo>texto</nodoHijo>
> > </nodoRaiz>
>
> > haría lo siguiente
>
> > class NodoRaiz {
> >    getXML() {
> >        return "<nodoRaiz>" + new NodoHijo("texto").getXML() + "</nodoHijo";
> >    }
> > }
>
> > class NodoHijo {
> >    NodoHijo(texto) {
> >       this.texto = texto;
> >    }
> >    getXML() {
> >       return "<nodoHijo>" + texto +"</nodoHijo>";
> >    }
> > }
>
> > Esto te permite probar tu parser poco a poco (primero el nodo hijo y luego
> > el nodo raíz,....) y localizar los cambios en un único lugar sin que afecte
> > al resto. El código es muy mejorable, pero creo que refleja bien la idea.
>

Eduardo Ferrández

unread,
Oct 26, 2010, 12:20:28 PM10/26/10
to artesanos-...@googlegroups.com
Por lo que he entendido lo que se quiere es probar algo que se alimenta de XML, no la validez del XML.

Esto es:

testParseaNodoHijo() {
   xml = new NodoHijo().getXml();
   definition = parse.parse(xml)

    asserts sobre definition
}

testParseaRaiz() {
   xml = new NodoRaiz().getXml();
   definition = parse.parse(xml)

    asserts sobre definition
}

Creo que es una ventaja frente a guardarlo en literales o en ficheros porque así te permite probar desde los elementos hoja hasta el document root de forma independiente (el nodo raíz desconoce el xml del nodo hijo y no necesita saberlo porque se prueba en otro lugar) y evitas duplicar código.

yeraycaballero

unread,
Oct 26, 2010, 6:45:06 PM10/26/10
to Artesanos de Software
Eduardo, estoy totalmente de acuerdo contigo y ahora te comento como
abordo este problema normalmente, pero antes quería explicar lo que se
pretende probar porque no lo he dejado claro.

El problema consiste en deserializar un xml para obtener un objeto y
lo que se pretende probar es que la deserialización se realiza bien.
El cómo se crea la cadena
xml que se quiere deserializar no tiene sentido y para mi lo más
práctico es escribir un literal en xml frente a usar builders o
ficheros externos.

Te planteo un ejemplo sencillo para que lo veas más claro en código.
Vamos a suponer que tenemos una revista que se compone de artículos y
que estos artículos
pueden contener referencias a otros artículos:

Su representación en xml podría ser la siguiente:

<magazine>
<title></title>
<description></description>
<articles>
<article>
<title></title>
<text></text>
<reference ref=""></>
</article>
</articles>
</magazine>

Para deserializar este ejemplo plantearísmos tres clases:
MagazineParser, AttributeParser y ReferenceParser todos cumpliendo la
interfaz de un IParser tener la siguiente forma.

interface IParser<T> {
T parser(String xml);
}

Como bien comentabas se deberían repartir la responsabilidad entre
objetos, así cada uno tiene una sola responsabilidad y se pueden
probar y mantener por separado. Así por ejemplo, el magazineParser
delega al ArticleParser la descerialización de un artículo y este
delega al ReferenceParser la descerialización de una referencia. Si
quieres, esta colaboración la puedes especificar en los test antes de
implementar los parsers.

Y Bueno un test quedaría así.

testSerializeArticleWithoutReferences() {
xml = "<article><title>abc</title><text></text></article>";
Article article =
parser.parse(xml);testSerializeArticleWithOneReference() {
xml = "<article> ... <referemce ref=""></reference></article>";
Article article = parser.parse(xml);
referenceParser = mock(ReferenceParser.class);

assertEquals("abc", article.getTitle());
}

Donde la variable xml es un literal concreto que quiero pasar al
parser.

Espero haberme explicado un poco mejor.
Gracias por la ayuda

Un saludo,
Yeray Caballero























On 26 oct, 17:20, Eduardo Ferrández <eduardoferrand...@gmail.com>
wrote:
> Por lo que he entendido lo que se quiere es probar algo que se alimenta de
> XML, no la validez del XML.
>
> Esto es:
>
> testParseaNodoHijo() {
>    xml = new NodoHijo().getXml();
>    definition = parse.parse(xml)
>
>     asserts sobre definition
>
> }
>
> testParseaRaiz() {
>    xml = new NodoRaiz().getXml();
>    definition = parse.parse(xml)
>
>     asserts sobre definition
>
> }
>
> Creo que es una ventaja frente a guardarlo en literales o en ficheros porque
> así te permite probar desde los elementos hoja hasta el document root de
> forma independiente (el nodo raíz desconoce el xml del nodo hijo y no
> necesita saberlo porque se prueba en otro lugar) y evitas duplicar código.
>
> El 26 de octubre de 2010 18:03, jcesarperez <
> julio.cesar.perez.arq...@gmail.com> escribió:

Eduardo Ferrández

unread,
Oct 27, 2010, 3:19:56 AM10/27/10
to artesanos-...@googlegroups.com
Te he entendido perfectamente Yeray. Quien no se ha explicado bien en este caso he sido yo. Era un poco en la línea de lo que comentaba Jose Manuel en el punto 3 de su respuesta. Lo que quería decir es que yo no metería XML en literales ni en ficheros, si no que me apoyaría en clases de apoyo para construirlo. Estas clases no son código de producción, si no código elaborado específicamente para apoyar tus tests. ¿Por qué soy contrario a mantener xml como literal? Creo que un XML es una representación textual de una estructura compleja de objetos. Es más difícil de leer y de mantener que una estructura de clases. Siguiendo con tu ejemplo, dentro de mi carpeta de tests crearía estas clase, como por ejemplo:

class ArticleNode {
    TitleNode title;
    TextNode text;
    List<ReferenceNode> references;

    .............


    String generateXml() {
        return "<article>" + title.generateXml() + text.generateXml() + toXml(references) + "</article>";
    }
}

usándolo en tu código:

testSerializeArticleWithoutReferences() {
  xml = new ArticleNode()
               .withTitle(title)
               .withText(text)
               .generateXml();
 Article article = parser.parse(xml);
....

testSerializeArticleWithOneReference() {
  xml = new ArticleNode()
              .withTitle(title)
              .withText(text)
              .addReference(reference)
              .generateXml();

 Article article =  parser.parse(xml);
 referenceParser = mock(ReferenceParser.class);

 assertEquals("abc", article.getTitle());
}

La ventaja de esto es que cuando quieras probar el parser del title no tienes que escribir un nuevo xml, sólo tienes que pedir a tu TitleNode que genere su xml.

Por supuesto deberías escribir tests para las clases *Node, para asegurarte que devuelven el XML que tú quieres, pero no pasa nada por hacer un código de test con la misma calidad que tu código de producción.

Espero haberme explicado mejor ;)

yeraycaballero

unread,
Oct 27, 2010, 3:58:59 AM10/27/10
to Artesanos de Software
Eduardo la verdad es que los tests quedan bastante legibles y veo
clara tu propuesta, que si no estoy equivocado coincide con los
builders que comentaba Jose Manuel.
Tengo que reconocer que builders son mejor opción que el uso de
literales. Se pueden reutilizar y componer perfectamente y el posible
cambio en el fututro estaría localizado.

Gracias a Eduardo y Jose Manuel por la propuestas, han sido muy
reveladoras.

Un saludo,
Yeray Caballero








On Oct 27, 8:19 am, Eduardo Ferrández <eduardoferrand...@gmail.com>
wrote:
> Te he entendido perfectamente Yeray. Quien no se ha explicado bien en este
> caso he sido yo. Era un poco en la línea de lo que comentaba Jose Manuel en
> el punto 3 de su respuesta. Lo que quería decir es que yo no metería XML en
> literales ni en ficheros, si no que me apoyaría en clases de apoyo para
> construirlo. Estas clases no son código de producción, si no código
> elaborado específicamente para apoyar tus tests. ¿Por qué soy contrario a
> mantener xml como literal? Creo que un XML es una representación textual de
> una estructura compleja de objetos. Es más difícil de leer y de mantener que
> una estructura de clases. Siguiendo con tu ejemplo, dentro de mi carpeta de
> tests crearía estas clase, como por ejemplo:
>
> class ArticleNode {
>     TitleNode title;
>     TextNode text;
>     List<ReferenceNode> references;
>
>     .............
>
>     String generateXml() {
>         return "<article>" + title.generateXml() + text.generateXml() +
> toXml(references) + "</article>";
>     }
>
> }
>
> usándolo en tu código:
>
> testSerializeArticleWithoutReferences() {
>   *xml = new ArticleNode()*
> *               .withTitle(title)*
> *               .withText(text)*
> *               .generateXml();*
>  Article article = parser.parse(xml);
> ....
>
> testSerializeArticleWithOneReference() {
>  * xml = new ArticleNode()*
> *              .withTitle(title)*
> *              .withText(text)*
> *              .addReference(reference)*
> *              .generateXml();*

Eduardo Ferrández

unread,
Oct 27, 2010, 4:07:59 AM10/27/10
to artesanos-...@googlegroups.com
si no estoy equivocado coincide con los
builders que comentaba Jose Manuel.

Sí, exactamente. Era un poco como complemento a su respuesta. Aunque yo mismo me planteo (con muchas dudas) usar ficheros externos para otras cosas (como en el hilo de TDD-sp al que hace referencia), creo que en este caso es mejor así por la riqueza del formato y el grado de reutilización que vas a necesitar. 

Eduardo Ferrández

unread,
Oct 27, 2010, 5:24:23 AM10/27/10
to artesanos-...@googlegroups.com
Refactoring :)
Pensando en cómo probar las clases *Node y lo feos que podrían quedar esos tests he visto que podíamos crear una clase abstracta Node:

abstract class Node {
    List<Node> children;
    abstract String openNodeXml();
    abstract String closeNodeXml();
    
    Node addChild(Node child) {
        children.add(child);
    }    

    String generateXml() {
        String xml = openNodeXml();
        for (Node child: children) {
            xml += child.generateXml();
        }
        xml += closeNodeXml();
        return xml;
    }
}

class ArticleNode extends Node {
    String openNodeXml() {
         return "<article>";
    }

    String closeNodeXml() {
         return "</article>";
    }
}

class ReferenceNode extends Node {
    String reference;
    String openNodeXml() {
         return "<reference ref='" + reference +"'>";
    }

    String closeNodeXml() {
         return "</reference>";
    }

}

Y ahora para construir un artículo:

xml = new ArticleNode()
              .addChild(title)
              .addChild(text)
              .addChild(reference)
              .generateXml();

Mucho más sencillo de probar ;)

yeraycaballero

unread,
Oct 26, 2010, 5:52:22 PM10/26/10
to Artesanos de Software
Jose Manuel, quizás no lo he explicado bien ;-) pero lo has entendido
perfectamente.

Se agradece las sugerencias que planteas y parece que estamos de
acuerdo en que el uso de los literales hacen los test más sencillos.
En cuanto al mantenimiento parece que no queda otra que modificar los
literales xml de los tests unitarios cuando cambia la estructura del
xml.

No he encontrado el hilo que comentas en TDD-es, pero tampoco veo la
utilidad del Builder en este caso. ¿Le ves alguna ventaja al usar un
builder para crear la cadena de texto xml en lugar de usar la cadena
de texto en si?

Un saludo,
Yeray Caballero
Reply all
Reply to author
Forward
0 new messages