A maioria dos tutoriais / exemplos de teste de unidade existentes geralmente envolve a definição dos dados a serem testados para cada teste individual. Eu acho que isso faz parte da teoria "tudo deve ser testado isoladamente".
No entanto, descobri que, ao lidar com aplicativos de várias camadas com muita DI , o código necessário para configurar cada teste fica muito longo. Em vez disso, criei várias classes de testbase que agora posso herdar, com muitos andaimes de teste pré-criados.
Como parte disso, também estou criando conjuntos de dados falsos que representam o banco de dados de um aplicativo em execução, embora geralmente apenas uma ou duas linhas em cada "tabela".
É uma prática aceita predefinir, se não todos, a maioria dos dados de teste em todos os testes de unidade?
Atualizar
A partir dos comentários abaixo, parece que estou fazendo mais integração do que testes de unidade.
Meu projeto atual é o ASP.NET MVC, usando o Unit of Work sobre Entity Framework Code First e o Moq para teste. Eu zombei da UoW e dos repositórios, mas estou usando as classes reais de lógica de negócios e testando as ações do controlador. Os testes costumam verificar se a UoW foi confirmada, por exemplo:
[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
[TestMethod]
public void UserInvite_ExistingUser_DoesntInsertNewUser() {
// Arrange
var model = new Mandy.App.Models.Setup.UserInvite() {
Email = userData.First().Email
};
// Act
setupController.UserInvite(model);
// Assert
mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
}
}
SetupControllerTestBase
está construindo o UoW falso e instanciando o userLogic
.
Muitos testes exigem a existência de um usuário ou produto existente no banco de dados, por isso, preenchi previamente o que a UoW simulada retorna, neste exemplo userData
, que é apenas um IList<User>
registro de usuário único.
fonte
Respostas:
Por fim, você deseja escrever o mínimo de código possível para obter o máximo de resultados possível. Ter muito do mesmo código em vários testes a) tende a resultar em codificação de copiar e colar eb) significa que, se uma assinatura de método for alterada, você poderá ter que corrigir muitos testes quebrados.
Eu uso a abordagem de ter classes TestHelper padrão que me fornecem muitos tipos de dados que uso rotineiramente, para que eu possa criar conjuntos de entidades padrão ou classes DTO para que meus testes consultem e saibam exatamente o que receberei a cada vez. Então, eu posso ligar
TestHelper.GetFooRange( 0, 100 )
para obter um intervalo de 100 objetos Foo com todas as suas classes / campos dependentes configurados.Especialmente quando existem relacionamentos complexos configurados em um sistema do tipo ORM que precisam estar presentes para que as coisas funcionem corretamente, mas não são necessariamente significativos para este teste que podem economizar muito tempo.
Nas situações em que estou testando próximo ao nível dos dados, às vezes crio uma versão de teste da minha classe de repositório que pode ser consultada de maneira semelhante (novamente, isso ocorre em um ambiente do tipo ORM e não seria relevante em relação a um banco de dados real), porque zombar das respostas exatas às consultas é muito trabalhoso e, muitas vezes, oferece apenas pequenos benefícios.
Existem alguns cuidados a serem tomados, embora em testes de unidade:
fonte
O que quer que torne a intenção do seu teste mais legível.
Como regra geral:
Se os dados fizerem parte do teste (por exemplo, não devem imprimir linhas com um estado 7), codifique-os no teste, para que fique claro o que o autor pretendia que acontecesse.
Se os dados forem apenas de preenchimento para garantir que eles tenham algo com que trabalhar (por exemplo, não deve marcar o registro como completo se o serviço de processamento gerar uma exceção), tenha um método BuildDummyData ou uma classe de teste que mantenha os dados irrelevantes fora do teste .
Mas note que estou lutando para pensar em um bom exemplo deste último. Se você tem muitos deles em um equipamento de teste de unidade, provavelmente tem um problema diferente a resolver ... talvez o método em teste seja muito complexo.
fonte
Diferentes métodos de teste
Primeiro defina o que você está fazendo: Teste de unidade ou teste de integração . O número de camadas é irrelevante para o teste de unidade, pois você provavelmente testa apenas uma classe. O resto você zomba. Para testes de integração, é inevitável que você teste várias camadas. Se você tiver bons testes de unidade, o truque é tornar os testes de integração não muito complexos.
Se seus testes de unidade forem bons, não será necessário repetir todos os detalhes ao realizar testes de integração.
Termos que usamos, esses são um pouco dependentes da plataforma, mas você pode encontrá-los em quase todas as plataformas de teste / desenvolvimento:
Exemplo de aplicação
Dependendo da tecnologia usada, os nomes podem diferir, mas usarei isso como um exemplo:
Se você possui um aplicativo CRUD simples com o modelo Product, ProductsController e uma visualização de índice que gera uma tabela HTML com produtos:
O resultado final do aplicativo está mostrando uma tabela HTML com uma lista de todos os produtos que estão ativos.
Teste de unidade
Modelo
O modelo que você pode testar com bastante facilidade. Existem métodos diferentes para isso; nós usamos luminárias. Eu acho que é isso que você chama de "conjuntos de dados falsos". Portanto, antes de cada teste ser executado, criamos a tabela e inserimos os dados originais. A maioria das plataformas possui métodos para isso. Por exemplo, na sua classe de teste, um método setUp () que é executado antes de cada teste.
Em seguida, executamos nosso teste, por exemplo: produtos testGetAllActive .
Então, testamos diretamente em um banco de dados de teste. Nós não zombamos da fonte de dados; nós sempre fazemos o mesmo. Isso nos permite, por exemplo, testar com uma nova versão do banco de dados, e quaisquer problemas de consulta surgirão.
No mundo real, você nem sempre pode seguir 100% de responsabilidade única . Se você quiser fazer isso ainda melhor, poderá usar uma fonte de dados que você zomba. Para nós (usamos um ORM) que parece testar a tecnologia já existente. Além disso, os testes se tornam muito mais complexos e realmente não testam as consultas. Então, mantemos assim.
Os dados codificados são armazenados separadamente nos equipamentos. Portanto, o equipamento é como um arquivo SQL com uma instrução create table e insere os registros que usamos. Nós os mantemos pequenos, a menos que haja uma necessidade real de testar com muitos registros.
Controlador
O controlador precisa de mais trabalho, porque não queremos testar o modelo com ele. Então, o que fazemos é zombar do modelo. Isso significa: Testamos: método index () que deve retornar uma lista de registros.
Portanto, zombamos do método getAllActive () e adicionamos dados fixos (dois registros, por exemplo). Agora testamos os dados que o controlador envia para a visualização e comparamos se realmente recuperamos esses dois registros.
É o bastante. Tentamos adicionar pouca funcionalidade ao controlador, pois isso dificulta o teste. Mas é claro que sempre há algum código nele. Por exemplo, testamos requisitos como: Mostrar esses dois registros apenas se você estiver conectado.
Portanto, o controlador precisa de um mock normalmente e de um pequeno pedaço de dados codificados. Para um sistema de login, talvez outro. Em nosso teste, temos um método auxiliar para isso: setLoggedIn (). Isso simplifica o teste com login ou sem login.
Visualizações
O teste de visualizações é difícil. Primeiro separamos a lógica que se repete. Colocamos em Helpers e testamos estritamente essas classes. Esperamos sempre a mesma saída. Por exemplo, generateHtmlTableFromArray ().
Depois, temos algumas visualizações específicas do projeto. Nós não testamos isso. Não é realmente desejado testá-los unitariamente. Nós os mantemos para testes de integração. Como colocamos grande parte do código em visualizações, temos um risco menor aqui.
Se você começar a testar aqueles, provavelmente precisará alterar seus testes sempre que alterar um pedaço de HTML que não é útil para a maioria dos projetos.
Teste de integração
Dependendo da sua plataforma, aqui você pode trabalhar com histórias de usuários, etc. Ele pode ser baseado na Web como o Selenium ou outras soluções comparáveis.
Geralmente, apenas carregamos o banco de dados com os equipamentos e afirmamos quais dados devem estar disponíveis. Para testes de integração total, geralmente usamos requisitos muito globais. Portanto: defina o produto como ativo e verifique se o produto fica disponível.
Não testamos tudo novamente, como se os campos corretos estão disponíveis. Testamos os requisitos maiores aqui. Como não queremos duplicar nossos testes do controlador ou da exibição. Se algo realmente for essencial / essencial do seu aplicativo ou por motivos de segurança (verifique a senha NÃO está disponível), nós os adicionamos para garantir que esteja certo.
Os dados codificados são armazenados nos equipamentos.
fonte
Se você estiver escrevendo testes que envolvem muita DI e fiação, até usar fontes de dados "reais", provavelmente saiu da área de teste de unidade simples e entrou no domínio dos testes de integração.
Para testes de integração, acho que não é uma má idéia ter uma lógica de configuração de dados comum. O principal objetivo desses testes é provar que tudo está configurado corretamente. Isso é bastante independente dos dados concretos enviados pelo seu sistema.
Por outro lado, para testes de unidade, eu recomendaria manter o alvo de uma classe de teste em uma única classe "real" e zombar de todo o resto. Em seguida, você deve realmente codificar os dados de teste para garantir a cobertura do maior número possível de caminhos especiais / de erros anteriores.
Para adicionar um elemento semi-codificado / aleatório aos testes, eu gosto de introduzir fábricas de modelos aleatórios. Em um teste que usa uma instância do meu modelo, eu uso essas fábricas para criar um objeto de modelo válido, mas completamente aleatório, e codifico apenas as propriedades que são de interesse para o teste em questão. Dessa forma, você especifica todos os dados relevantes diretamente em seu teste, poupando também a necessidade de especificar todos os dados irrelevantes e (até certo ponto) testar se não há dependências indesejadas em outros campos do modelo.
fonte
Eu acho que é bastante comum codificar a maioria dos dados para seus testes.
Considere uma situação simples em que um determinado conjunto de dados causa um erro. Você pode criar especificamente um teste de unidade para esses dados para exercer a correção e garantir que o bug não volte. Com o tempo, seus testes terão um conjunto de dados que abrangem vários casos de teste.
Os dados de teste predefinidos também permitem criar um conjunto de dados que abrange uma ampla e conhecida variedade de situações.
Dito isso, acho que também vale a pena ter alguns dados aleatórios em seus testes.
fonte