fixture-factory_ Fixture-Factory Criando objetos para seus testes Como criar objetos através de templates para serem utilizados como massa de dados em seus testes. Começamos testando métodos simples, mas e quando dependemos de objetos com determinados valores para realizar o teste, como fazer? O Fixture-Factory é um framework que surgiu para auxiliar na criação e manutenção de objetos utilizados nos testes, tudo isto de uma maneira simples e intuitiva através da declaraçāo de templates. Já faz algum tempo que a comunidade de desenvolvimento de software discute a necessidade de garantir a qualidade do que está sendo desenvolvido. Em busca disto surgiram algumas soluções como o uso de testes automatizados e TDD. Aplicar estes conceitos pode parecer um pouco complicado no início e uma das maiores dificuldades encontradas é de como criar os dados necessários para realizar os testes. Uma das soluções mais utilizadas é o uso de Fixtures. Neste artigo apresentaremos uma solução alternativa/complementar às Fixtures, que é o Fixture- -Factory um framework que auxiliar na criação dos dados necessários quando e onde precisamos. Tudo isto através uma maneira programática, feito de programadores para programadores. Fixtures As Fixtures são utilizadas para especificar os dados que serão criados. Para realizar essa especificação podem ser utilizados arquivos xml, json, yaml etc. Além de criar estes arquivos que dizem quais valores os objetos criados terão é necessário realizar algumas configurações para que tudo funcione (acesso a base de dados, por exemplo). Após configurado, é possível utilizar esses dados para mockar resultados de métodos, consultas a base de dados, consultas webservices etc. O grande problema das Fixtures é que elas acabam tornando-se difíceis de manter em longo prazo. Com o passar do tempo, a tendência é termos diversos arquivos de Fixtures e em alguns casos até arquivos grandes demais, que causam grande dificuldade / 26
Nykolas Laurentino de Lima nykolas.lima@gmail.com É programador na Amil, trabalha com Java há mais de 4 anos. Certificado SCJP e SCWCD. na manutenção. Além disso, os dados configurados nas Fixtures são em sua grande maioria hard-coded, o que muitas vezes leva o programador a escrever testes baseados nesses dados específicos. O problema de testar baseado em dados fixos é que quando os dados mudarem, quando a aplicação for para produção, por exemplo, o código pode não funcionar. Exemplo de fixture usando YAML: valid_student: id: 1 nome: Nykolas Laurentino de Lima email: nykolas.lima@gmail.com datanascimento: 02/06/1991 mediafinal: 7 Fixture-Factory O Fixture-Factory é um framework que foi criado para facilitar a criação de objetos para serem utilizados nos testes. Diferente das Fixtures, não é necessária nenhuma configuração adicional. Só é necessário descrever quais valores queremos que nossos objetos tenham e pronto. Além de não precisar de nenhuma configuração adicional, a definição dos valores que serão utilizados nos nossos objetos é feita programaticamente, ou seja, utilizando código Java. Sem arquivos XML, YAML, JSON ou qualquer outro tipo. O framework funciona de maneira similar ao Factory-Girl utilizado na criação de testes em Ruby. Template Para definirmos os valores que nossos objetos gerados terão é necessário escrevermos um template para eles. O template irá definir quais valores serão gerados para cada propriedade do nosso objeto, mas, diferentemente das Fixtures, os templates podem definir diversos valores possíveis para a mesma propriedade. Podemos gerar valores aleatórios baseados em uma lista de valores possíveis, por meio de expressões regulares, períodos de data etc. Uma das grandes vantagens na utilização de Templates e não de Fixtures estáticas é que criamos objetos que possuem valores que variam constantemente, a cada execução do mesmo teste o objeto utilizado por ele pode ter valores diferentes. Desta maneira nossos testes tendem a ser menos atrelados a dados específicos e mais direcionados a padrões de dados que nós definimos como válidos. Um exemplo muito comum de testes criados baseados em dados é quando dependemos de um período de data. Com o uso de Templates podemos definir que o valor de determinado atributo deve ser de 6 meses atrás, isto pode ser feito de uma maneira simples e fácil, o que com o uso Fixtures poderia demandar uma quantidade considerável de refactoring e mocks. Criando o Template A criação do template é bem simples, dizemos para qual classe desejamos criar o template, qual o nome dele e então configuramos os valores para as propriedades da classe. Vale lembrar que uma mesma classe pode ter diversos templates diferentes. Listagem 1. A Listagem 1 exemplifica a codificação de um template para a classe Client. add( id, 1L); add( name, Nykolas Laurentino de Lima ); add( nickname, geek ); add( email, ${nickname@gmail.com ); add( birthday, new Date()); ).addtemplate( nerdclient, add( id, 2L); add( name, Anderson Parra ); add( nickname, nerd ); add( email, ${nickname@gmail.com ); add( birthday, new Date()); 27 \
Na Listagem 1 nós criamos dois templates para a classe Client, um deles se chama geekclient e o outro nerdclient. Note que podemos utilizar placeholders para criar valores de uma propriedade a partir de outra propriedade, como foi feito na propriedade e-mail. Mais abaixo será explicado o uso de funções para gerar valores dinamicamente na criação do objeto. Para obter uma instância do template criado só precisamos informar a classe desejada e o nome do template: Listagem 2. A Listagem 2 exemplifica a codificação para obter uma instância da classe Client através do template geekclient. Fixture.from(Client.class).gimme( geekclient ); Um exemplo do objeto gerado para o template geekclient pode ser visto na figura 1. Figura 1. Exemplo de objeto gerado para o template geekclient. Relacionamentos Tratar os relacionamentos entre os objetos também pode ser feito de maneira simples e fácil através das RelationFunctions. Listagem 3. A Listagem 3 exemplifica a codificação das Relation Functions one e has. Fixture.of(Address.class).addTemplate( brazilianaddress, add( id, 1L); add( street, Paulista Avenue ); add( city, São Paulo ); add( state, SP ); add( country, Brazil ); add( zipcode, 0660800 ); Fixture.of(Phone.class).addTemplate( brazilianphonenumber, add( number, 11 9999-9999 ); add( address, one(address.class, brazilianaddress )); add( phones, has(3).of(phone.class, brazilianphonenumber )); A função one irá criar uma instância da classe Address utilizando o template brazilianaddress que foi criado logo acima. A propriedade phones é uma lista de telefones, portanto utilizamos a função has e dizemos para ela quantas instâncias nós queremos, de qual classe e qual template será utilizado. Agora ao obtermos uma instância do template geekclient, automaticamente serão criados os relacionamentos com Address e Phone utilizando os templates declarados. Um exemplo dos relacionamentos criados pode ser visto na figura 2. Functions As Functions são utilizadas para gerar valores dinâmicos para as propriedades de um objeto. As RelationFunctions explicadas na seção anterior exemplificam a geração de objetos para os relacionamentos, mas existem outras funções para auxiliar na criação de valores simples. Random A função random é utilizada para escolher um valor aleatoriamente dentre uma lista de possíveis valores. Figura 2. Exemplo de objeto gerado utilizando as funções de relacionamento one e has. / 28
Listagem 4. A Listagem 4 exemplifica a codificação da função random para geração de valores aleatórios. add( name, random( Nykolas Laurentino de Lima, Anderson Parra )); Ao obter uma instância do template geekclient a propriedade name terá seu valor gerado aleatoriamente conforme configurado. Regex A função regex pode ser utilizada para gerar valores baseados em uma expressão regex. Listagem 5. Utilizando expressões regulares para gerar o valor da propriedade. Fixture.of(Phone.class). addtemplate( brazilianphonenumber, add( number, regex( (\\d{2) (\\d{4) - (\\d{4) )); new SimpleDateFormat( yyyy-mm-dd ))); add( contractendingdate, afterdate( 2011-04-15 ), new SimpleDateFormat( yyyy-mm-dd )); add( lastlogindate, instant( now )); Name As funções name auxiliam na criação de nomes aleatórios. Listagem 7. Utilizando as funções de nomes para gerar nomes aleatórios. new Rule(){{ add( name, firstname()); add( lastname, lastname()); Um exemplo dos valores gerados pelas funções name e lastname pode ser visto na figura 4. Um exemplo do resultado da função regex pode ser visto na figura 3. Figura 4. Exemplo de valores gerados através das funções name e lastname. Figura 3. Exemplo de valor gerado através da função regex. Date Para tratamento de datas podemos utilizar as funções beforedate, afterdate, randomdate e instant. Estas funções facilitam a manipulação de datas através de uma interface simples e intuitiva. Listagem 6. Utilizando as funções de datas para gerar o valor das propriedades. add( birthday, randomdate( 1980-04-15, 1985-11-07, new SimpleDateFormat( yyyy-mm-dd ))); add( contractbegginingdate, beforedate( 2011-04-15, Refatorando nossos templates Podemos agora refatorar os templates para Client, Address e Phone para utilizarem as functions. Deste modo os valores serão gerados dinamicamente no momento em que nossos objetos forem utilizados nos testes. Listagem 8.Template para Client refatorado. Fixture.of(Client.class).addTemplate( geekandnerdclient, add( id, random(long.class, range(1l, 200L))); add( name, random( Nykolas Laurentino de Lima, Anderson Parra )); add( lastname, lastname()); add( nickname, random( nerd, geek )); add( email, ${nickname@gmail.com ); add( birthday, instant( 18 years ago )); add( address, one(address.class, brazilianaddress )); 29 \
add( phones, has(3).of(phone.class, brazilianphonenumber )); // Client Listagem 9. Template para Address refatorado. Fixture.of(Address.class).addTemplate( brazilianaddress, add( id, random(long.class, range(1l, 100L))); add( street, random( Paulista Avenue, Ibirapuera avenue )); add( city, São Paulo ); add( state, SP ); add( country, Brazil ); add( zipcode, random( 0660800, 17720000 )); Listagem 10. Template para Phone refatorado. Fixture.of(Phone.class). addtemplate( brazilianphonenumber, add( number, regex( (\\d{2) \\d{4-\\d{4 )); Após o refactoring é possível notar a melhora no código, na definição dos valores que serão gerados e o ganho que isto traz na abrangência e diversidade de dados que serão testados. Organizando seus templates É possível criar os templates em qualquer lugar. No @Before de uma classe de testes ou até mesmo dentro do próprio método que realiza o teste. Entretanto, uma boa prática para a organização dos templates é a criação de classes separadas para declarar os templates de cada módulo do seu sistema. Nos meus projetos pessoais eu costumo criar uma classe TemplateLoader que contém uma inner class para cada entidade ou módulo do meu sistema. Listagem 11. Exemplo de TemplateLoader responsável pela criação dos templates. public class TemplateLoader { public static void loadtemplates() { TemplateLoader.ClientTemplate.loadTemplates(); private static class ClientTemplate { public static void loadtemplates() { //Declaração dos outros templates relacionados a Após criar o TemplateLoader é possível chamar o método TemplateLoader.loadTemplates para carregar todos os templates ou carregar somente os templates desejados através do método TemplateLoader. ClientTemplate.loadTemplates(). Considerações finais O Fixture-Factory oferece uma DSL (domain- -specific language) simples e intuitiva para criação de objetos para nossos testes. Com o uso das Relation Functions, configurar os relacionamentos entre as entidades torna-se uma tarefa fácil. Para relacionamentos one-to-one basta utilizar a função one dizendo a classe e o nome do template a ser utilizado. Para relacionamentos one- -to-many a função has recebe a classe, a quantidade de instâncias desejadas e o nome do template a ser utilizado. Comparado com as Fixtures, o Fixture-Factory possui a vantagem de sua utilização e configuração ser feita 100% com código Java, não precisando de arquivos XML ou JSON. Com isto a leitura, debug, identificação de erros e refactoring são muito melhores (caso você erre algo na sintaxe, o compilador vai te avisar). Quando utilizamos fixtures ou criamos nossos objetos na mão no momento do teste, estes objetos possuem sempre os mesmos valores fixos que foram configurados. Isto pode influenciar o programador a escrever testes baseados nesses dados hard-coded, o que pode acabar escondendo problemas na implementação do código que poderiam ser identificados no momento do teste caso os valores fossem gerados através de padrões configurados no Template do Fixture-Factory. Este é um exemplo claro da vantagem do uso de Templates e das Functions do framework para geração dos objetos. /referências > DO repositório do Fixture-Factory no github é: https:// github.com/aparra/fixture-factory > Dúvidas, sugestões ou qualquer outro assunto relacionado ao framework podem ser enviadas para a lista Mailing List(https://groups.google.com/ forum/?fromgroups#!forum/fixture-factory). > Fontes utilizados no artigo: https://github.com/ nykolaslima/fixture-factory-mundoj > Factory-Girl, solução parecida utilizada em Ruby: https://github.com/thoughtbot/factory_girl / 30