Vantagens e desvantagens de criar código de andaime comum para teste de unidade


8

Para o projeto em que minha equipe e eu estamos trabalhando, geralmente descobrimos que precisamos de grandes partes do código do andaime. Criar objetos de domínio com valores corretos, configurar simulações para repositórios, lidar com o cache, ... são coisas que ocorrem geralmente durante os testes. Muitas vezes, trabalhamos com os mesmos objetos básicos que são centrais para o nosso domínio (pessoa, ...), portanto muitos testes criam instâncias desses objetos para outros objetos trabalharem. Como temos muitas soluções diferentes usando o domínio base, esse tipo de código geralmente se espalha por essas soluções.

Eu estive pensando em criar classes comuns que fazem muito desse andaime. Isso nos permitiria solicitar uma pessoa totalmente instanciada com tudo configurado (acesso via repositório, armazenamento em cache ...). Isso remove o código duplicado de nossos testes de unidade individuais, mas também significa que há uma grande quantidade de código que provavelmente faz 'muito' por teste (pois configuraria um objeto completo e não apenas as partes necessárias).

Alguém já fez isso? Existem insights, comentários, pensamentos ... você pode oferecer que possa validar ou invalidar essa abordagem?


Editei sua pergunta um pouco antes de migrá-la para aqui para remover coisas que normalmente seriam respondidas. Com você listando vantagens e desvantagens na própria pergunta, reduz a oportunidade de alguém dar uma resposta abrangente que não duplique o que você já disse. Dessa forma, os argumentos que você fez surgirão idealmente como parte das respostas. Caso contrário, sinta-se à vontade para repassá-las como resposta e a votação da comunidade poderá dizer se você está certo ou não.
Adam Lear

Respostas:


4
Creating domain objects with correct values

Eu uso o padrão do construtor para criar objetos de domínio para testes, conforme detalhado no livro " Growing Object-Oriented Software ". Os construtores têm métodos estáticos que geram valores padrão para objetos comuns com métodos para substituir os valores padrão.

User user = UserBuilder.anAdminUser().withEmail("test@example.com").build();

Você pode combiná-los para gerar objetos mais complexos.

Também usamos a herança de classe de caso de teste com zombarias comumente usadas para tipos específicos de testes de unidade - por exemplo, os testes do controlador mvc frequentemente exigem os mesmos zombarias / stubs para objetos de solicitação e resposta.


2

A configuração de todos os testes pode ser uma boa ideia, principalmente se você fizer uma implantação. Em vez de usar zombarias, criamos, implantamos e preenchemos um banco de dados de teste e, em seguida, apontamos o teste para a instância. Não exatamente da maneira que você deve fazer de acordo com o manual, mas funciona, ainda mais para nós, porque usamos o nosso código de implantação para fazê-lo.


Vamos supor que não haja dependência no banco de dados e os objetos sejam do POCO. Você ainda consideraria um banco de dados para recuperá-los? Ter que manter um banco de dados usado apenas para teste difere de ter que manter código para criar objetos que são usados ​​apenas para teste?
JDT

2

Bedwyr Humphreys está falando sobre uma solução Builder que eu usei algumas vezes, e realmente funciona bem. Quase nenhuma duplicação de código e construção de objetos de domínio de uma maneira limpa e agradável.

User user = UserBuilder.anAdminUser().withEmail("test@example.com").build();

Eu não sou tão fã de geração de código (andaimes, codesmith, lblgen, ..). Isso ajuda você a escrever códigos duplicados muito mais rapidamente, não sendo realmente de manutenção. Se uma estrutura estiver fazendo com que você escreva muitos códigos duplicados (de teste), talvez seja necessário encontrar uma maneira de afastar a base de código da estrutura.

Se você continuar escrevendo o código de acesso ao repositório, poderá resolvê-lo com um repositório básico genérico e ter uma classe de teste básica genérica da qual seus testes de unidade herdam.

Para evitar duplicação de código, o AOP também pode ajudar em coisas como log, cache, ...


1

Você pode reconsiderar o design do código que está testando. Talvez ele precise de algumas instâncias do padrão de design do Facade? Seu código de teste poderia usar as Fachadas e seria mais simples.


É uma abordagem válida - mas não para a base de código que temos. Atualmente, estou trabalhando com classes Builder que criam mensagens baseadas em XML a partir de nossos objetos de domínio. Nenhuma quantidade de classes de fachada me impedirá de conectar objetos totalmente construídos em algum momento - algo que tive que fazer várias vezes em diferentes soluções.
JDT

1

Eu posso me relacionar com essa pergunta. Na minha equipe, tentamos fazer o TDD antes e era a mesma história, muitos testes exigiam muitos métodos de andaimes / utilitários que simplesmente não tínhamos. Por causa das pressões do tempo e poucas outras decisões não tão corretas, acabamos com testes de unidade muito longos e reinventamos as coisas. Ao longo de um lançamento, esses testes de unidade tornaram-se tão complicados e difíceis de manter, que tivemos que descartá-los.

Atualmente, estou trabalhando com alguns outros desenvolvedores na reintrodução do TDD de volta à equipe, mas, em vez de simplesmente dizer às pessoas, fazer testes, queremos fazê-lo com cuidado dessa vez e as pessoas têm as habilidades e as ferramentas certas. Ausência do código de suporte comum é uma coisa que identificamos onde precisamos melhorar, para que, quando outros começarem a escrever testes, todo o código que eles escreverem seja pequeno e direto ao ponto.

Ainda não tenho tanta experiência nesta área quanto gostaria, mas poucas coisas que sugiro com base no trabalho que fizemos até agora e em alguns trabalhos de casa que fiz:

  1. Código comum é uma coisa boa. Fazer o mesmo trabalho 5 vezes não faz sentido e, com o tempo, as pressões pelo menos 4 vezes serão pela metade.
  2. Meu plano é criar uma estrutura comum e, em seguida, formar a equipe de liderança do TDD composta por 4-6 desenvolvedores. Em seguida, escreva testes de unidade, aprenda a estrutura e forneça feedback sobre ela. Em seguida, peça a esses caras que saiam para o resto do grupo e guiem outras pessoas na produção de testes de unidade sustentável que utilizam o poder da estrutura. Também peça a esses caras que coletem feedback e o tragam de volta à estrutura.
  3. Para os exemplos que você forneceu com a classe Person, estou investigando o uso de estruturas mockpp ou googlemock . Inclinando-se para o googlemock por enquanto, mas ainda não tentei. Nos dois casos, por que escrever algo que pode ser "emprestado"
  4. Tenha cuidado ao criar acessórios "god" que instanciam objetos para todos os testes, independentemente de esses testes realmente precisarem desses objetos. Isso é o que os padrões de teste do xUnit chamam de "padrão geral de fixação". Isso pode causar alguns problemas, como a) cada teste demorando muito para ser executado devido ao código de configuração excessivo eb) testes que acabam tendo muitas dependências desnecessárias. Em nossa estrutura, cada classe de teste escolhe exatamente quais recursos da estrutura precisa para executar, portanto, nenhuma bagagem desnecessária é incluída (no futuro, podemos alterá-la para cada método que escolhe independentemente). Eu me inspirei no Boost e no Modern C ++ Designe criou uma classe de modelo base que utiliza um mpl :: vector dos recursos necessários e cria uma hierarquia de classes personalizada especificamente para esse teste. Alguns realmente bacana capitão cruch decodificador anel super louco material modelo de magia negra :)

1

"Qualquer problema na ciência da computação pode ser resolvido com uma camada adicional de indireção. Mas isso geralmente cria outro problema". - David Wheeler

(aviso: faço parte da equipe da JDT, por isso tenho um conhecimento mais aprofundado do problema apresentado aqui)

O problema essencial aqui é que os próprios objetos na estrutura esperam uma conexão com o banco de dados, pois é vagamente baseado na estrutura CSLA de Rockford Lhotka. Isso torna quase impossível zombar deles (já que você precisaria alterar a estrutura para suportar isso) e também viola o princípio da caixa preta que um teste de unidade deve ter.

Sua solução proposta, embora não seja uma má idéia, exigirá não apenas muito trabalho para chegar a uma solução estável, mas também adicionará outra camada de classes que precisam ser mantidas e estendidas, adicionando ainda mais complexidade a uma solução já existente. situação complexa.

Embora eu concorde que você deve sempre procurar a melhor solução, em uma situação do mundo real como essa, ser prático também tem seus valores.

E praticidade sugere a seguinte opção:

Use um banco de dados.

Sim, eu sei que tecnicamente falando não é mais um teste de unidade "real", mas estritamente falando no momento em que você ultrapassa um limite de classe em seu teste, também não é mais um teste de unidade real. O que precisamos nos perguntar aqui é: queremos testes de unidade 'puros' que exijam grande quantidade de andaimes e códigos de cola, ou queremos código testável ?

Você também pode usar o fato de que a estrutura usada encapsula todo o acesso ao banco de dados e executar seus testes em um banco de dados de sandbox limpo. Se você executar cada teste em sua própria transação e reverter no final de cada conjunto "lógico" de testes, não deverá ter problemas de interdependência ou ordem de teste.

Não tente extrair um banco de dados de um banco de dados.


Sam, veja meu comentário na resposta de Tony Hopkinson. Você faria o mesmo se estivermos lidando com um projeto que é apenas aulas de POCO?
JDT

A menos que você esteja testando um algoritmo que vive sozinho, sem recursos externos, com certeza. A idéia seria que o banco de dados 'teste' contenha apenas dados mínimos e que quaisquer dados de teste que não sejam de infraestrutura precisariam ser inseridos pelos próprios testes, reduzindo assim o custo de manutenção. Mas é certo que, com objetos POCO 'claros', o caso de um banco de dados de teste não é tão forte.
Sam

0

Eu alertaria por ter muito código que faz muita 'magia negra' sem que os desenvolvedores saibam exatamente o que está sendo configurado. Além disso, com o tempo, você se tornará muito dependente desse código / dados. É por isso que não sou fã de basear seus testes em bancos de dados de teste. Acredito que os bancos de dados de teste são para teste como usuário, não para testes automatizados.

Dito isto, seu problema é real. Eu faria alguns métodos comuns para configurar os dados necessários regularmente. Idealmente, tente parametrizar os métodos, porque determinados testes precisam de dados diferentes.

Mas eu evitaria criar grandes gráficos de objetos. É melhor executar testes com o mínimo de dados que eles precisam. Se houver muitos outros dados, você poderá influenciar inesperadamente os resultados do teste (pior cenário).

Eu gosto de manter meus testes o mais "possível". É uma questão subjetiva, no entanto. Mas eu usaria métodos comuns com parâmetros para que você possa configurar seus dados de teste em apenas algumas linhas de código.


0

Todas as possibilidades resumidas nas respostas:

Use o padrão Builder (Bedwyr Humphreys, DXM e Pascal Mestdach)

Vantagens:

  • Sem duplicação de código nos testes
  • Pode combinar diferentes construtores para testes complexos
  • Interface limpa
  • Gráficos de objetos grandes de construtores podem influenciar o resultado do teste

Desvantagens:

  • Os construtores devem ser escritos e mantidos
  • O código do construtor deve ser testado também
  • O código do construtor precisará de cuidados e atenção constantes

Use um banco de dados de teste (Tony Hopkinson e Sam)

Vantagens:

  • Simplicidade
  • Todos os dados em um local central
  • Pode usar código de acesso a dados já desenvolvido
  • Utilizável para objetos de domínio que não são POCO (por exemplo, Active Record, ...)

Desvantagens:

  • Requer o uso do DAL que possa interferir nos testes
  • TDD não 'puro'
  • 'Caixa preta', é necessário dar uma olhada no banco de dados para ver quais dados seus objetos contêm

Criar objetos de domínio por teste (Peter)

Vantagens:

  • Código de teste isolado
  • Sem 'caixa preta', o que você vê no código de teste é o que obtém

Desvantagens:

  • Duplicação de código para objetos comuns

Usar Fachadas para objetos de domínio (Raedwald)

Vantagens:

  • Testes simples

Desvantagens:

  • Não é utilizável em todos os casos
  • O código oculto atrás do Facade também precisa ser testado
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.