Estou apenas começando com testes de unidade e TDD em geral. Eu já brinquei antes, mas agora estou determinado a adicioná-lo ao meu fluxo de trabalho e escrever um software melhor.
Ontem fiz uma pergunta que incluía isso, mas parece ser uma pergunta por si só. Sentei-me para começar a implementar uma classe de serviço que usarei para abstrair a lógica de negócios dos controladores e mapear modelos específicos e interações de dados usando o EF6.
O problema é que eu já me bloqueei porque não queria abstrair a EF em um repositório (ele ainda estará disponível fora dos serviços para consultas específicas, etc.) e gostaria de testar meus serviços (o Contexto da EF será usado) .
Aqui eu acho que é a pergunta, há um ponto para fazer isso? Em caso afirmativo, como as pessoas estão fazendo isso de maneira natural à luz das abstrações com vazamentos causadas pelo IQueryable e das muitas ótimas postagens de Ladislav Mrnka sobre o assunto de testes de unidade não serem diretas devido às diferenças nos provedores Linq ao trabalhar com uma memória implementação, ao contrário de um banco de dados específico.
O código que quero testar parece bastante simples. (este é apenas um código fictício para tentar entender o que estou fazendo, quero conduzir a criação usando TDD)
Contexto
public interface IContext
{
IDbSet<Product> Products { get; set; }
IDbSet<Category> Categories { get; set; }
int SaveChanges();
}
public class DataContext : DbContext, IContext
{
public IDbSet<Product> Products { get; set; }
public IDbSet<Category> Categories { get; set; }
public DataContext(string connectionString)
: base(connectionString)
{
}
}
Serviço
public class ProductService : IProductService
{
private IContext _context;
public ProductService(IContext dbContext)
{
_context = dbContext;
}
public IEnumerable<Product> GetAll()
{
var query = from p in _context.Products
select p;
return query;
}
}
Atualmente, estou pensando em fazer algumas coisas:
- Zombando do EF com algo parecido com esta abordagem - Zombando do EF ao testar unidades ou usando diretamente uma estrutura de zombaria na interface como moq - sentindo a dor que os testes de unidade podem passar, mas não necessariamente trabalhar de ponta a ponta e fazer backup deles com testes de integração?
- Talvez usando algo como o esforço para zombar de EF - eu nunca o usei e não tenho certeza se alguém mais está usando isso na natureza?
- Não se preocupe em testar qualquer coisa que simplesmente chame de volta à EF - então, essencialmente, os métodos de serviço que chamam a EF diretamente (getAll etc.) não são testados em unidade, mas apenas testados em integração?
Alguém aí realmente está fazendo isso por aí sem um Repo e tendo sucesso?
Respostas:
Este é um tópico no qual estou muito interessado. Há muitos puristas que dizem que você não deve testar tecnologias como EF e NHibernate. Eles estão certos, eles já foram rigorosamente testados e, como uma resposta anterior afirmou, muitas vezes não faz sentido gastar muito tempo testando o que você não possui.
No entanto, você possui o banco de dados abaixo! É aqui que, na minha opinião, essa abordagem é quebrada, não é necessário testar se a EF / NH está fazendo o trabalho corretamente. Você precisa testar se seus mapeamentos / implementações estão funcionando com seu banco de dados. Na minha opinião, essa é uma das partes mais importantes de um sistema que você pode testar.
A rigor, porém, estamos saindo do domínio do teste de unidade para o teste de integração, mas os princípios permanecem os mesmos.
A primeira coisa que você precisa fazer é poder zombar do seu DAL para que seu BLL possa ser testado independentemente do EF e do SQL. Estes são os seus testes de unidade. Em seguida, você precisa projetar seus testes de integração para provar seu DAL. Na minha opinião, eles são igualmente importantes.
Há algumas coisas a considerar:
Existem duas abordagens principais para configurar seu banco de dados, a primeira é executar um script de criação de banco de dados do UnitTest. Isso garante que seu banco de dados de teste de unidade esteja sempre no mesmo estado no início de cada teste (você pode redefinir isso ou executar cada teste em uma transação para garantir isso).
Sua outra opção é o que eu faço, execute configurações específicas para cada teste individual. Eu acredito que esta é a melhor abordagem por duas razões principais:
Infelizmente, seu compromisso aqui é a velocidade. Leva tempo para executar todos esses testes, para executar todos esses scripts de configuração / desmontagem.
Um ponto final, pode ser um trabalho muito difícil escrever uma quantidade tão grande de SQL para testar seu ORM. É aqui que tomo uma abordagem muito desagradável (os puristas aqui vão discordar de mim). Eu uso meu ORM para criar meu teste! Em vez de ter um script separado para cada teste de DAL no meu sistema, tenho uma fase de configuração de teste que cria os objetos, os anexa ao contexto e os salva. Eu então executo meu teste.
Isso está longe de ser a solução ideal, no entanto, na prática, acho muito mais fácil gerenciar (especialmente quando você tem vários milhares de testes); caso contrário, você está criando um grande número de scripts. Praticidade sobre pureza.
Sem dúvida, analisarei essa resposta em alguns anos (meses / dias) e discordarei de mim mesmo à medida que minhas abordagens mudaram - no entanto, essa é minha abordagem atual.
Para tentar resumir tudo o que eu disse acima, este é meu teste de integração de banco de dados típico:
O principal a notar aqui é que as sessões dos dois loops são completamente independentes. Na sua implementação do RunTest, você deve garantir que o contexto seja confirmado e destruído e seus dados só possam vir do seu banco de dados para a segunda parte.
Editar 13/10/2014
Eu disse que provavelmente revisaria esse modelo nos próximos meses. Embora eu mantenha a abordagem que defendi acima, atualizei levemente meu mecanismo de teste. Agora, tenho a tendência de criar as entidades no TestSetup e TestTearDown.
Em seguida, teste cada propriedade individualmente
Existem várias razões para essa abordagem:
Eu sinto que isso torna a classe de teste mais simples e os testes mais granulares ( declarações simples são boas )
Editar 03/05/2015
Outra revisão sobre essa abordagem. Embora as configurações em nível de classe sejam muito úteis para testes, como carregar propriedades, elas são menos úteis quando são necessárias diferentes configurações. Nesse caso, a criação de uma nova classe para cada caso é um exagero.
Para ajudar com isso, agora tenho duas classes base
SetupPerTest
eSingleSetup
. Essas duas classes expõem a estrutura conforme necessário.No
SingleSetup
temos um mecanismo muito semelhante ao descrito na minha primeira edição. Um exemplo seriaNo entanto, referências que garantem que apenas as entidades corretas sejam carregadas podem usar uma abordagem SetupPerTest
Em resumo, ambas as abordagens funcionam dependendo do que você está tentando testar.
fonte
Feedback da experiência do esforço aqui
Depois de muita leitura, tenho usado o Effort em meus testes: durante os testes, o Contexto é construído por uma fábrica que retorna uma versão em memória, o que me permite testar contra uma folha em branco a cada vez. Fora dos testes, a fábrica é resolvida para uma que retorne todo o contexto.
No entanto, tenho a sensação de que o teste contra uma simulação completa do banco de dados tende a arrastar os testes para baixo; você percebe que precisa cuidar de configurar um monte de dependências para testar uma parte do sistema. Você também tende a organizar testes juntos que podem não estar relacionados, apenas porque existe apenas um objeto enorme que lida com tudo. Se você não prestar atenção, poderá fazer testes de integração em vez de testes de unidade
Eu preferiria testar contra algo mais abstrato do que um grande DBContext, mas não consegui encontrar o ponto ideal entre testes significativos e testes simples. Giz até minha inexperiência.
Então, acho o esforço interessante; se você precisar entrar em ação, é uma boa ferramenta para começar rapidamente e obter resultados. No entanto, acho que algo um pouco mais elegante e abstrato deve ser o próximo passo e é isso que vou investigar a seguir. Favoritar este post para ver para onde ele vai a seguir :)
Edite para adicionar : o esforço leva algum tempo para se aquecer; você está olhando aprox. 5 segundos no início do teste. Isso pode ser um problema para você, se você precisar que seu conjunto de testes seja muito eficiente.
Editado para esclarecimentos:
Eu usei o Effort para testar um aplicativo de serviço da web. Cada mensagem M que entra é encaminhada para uma
IHandlerOf<M>
via Windsor. Castle.Windsor resolve oIHandlerOf<M>
que resolve novamente as dependências do componente. Uma dessas dependências é aDataContextFactory
, que permite que o manipulador solicite a fábricaNos meus testes, instancio o componente IHandlerOf diretamente, zomba de todos os subcomponentes do SUT e lida com o envoltório de esforço
DataContextFactory
do manipulador.Isso significa que eu não teste unitário em sentido estrito, pois o banco de dados é atingido pelos meus testes. No entanto, como eu disse acima, deixe-me bater no chão correndo e eu poderia testar rapidamente alguns pontos no aplicativo
fonte
Se você deseja testar o código da unidade , é necessário isolar o código que deseja testar (neste caso, o seu serviço) de recursos externos (por exemplo, bancos de dados). Você provavelmente poderia fazer isso com algum tipo de provedor EF na memória , no entanto, uma maneira muito mais comum é abstrair sua implementação EF, por exemplo, com algum tipo de padrão de repositório. Sem esse isolamento, qualquer teste que você escrever será de integração, não de unidade.
Quanto ao teste do código EF - escrevo testes de integração automatizados para meus repositórios que gravam várias linhas no banco de dados durante sua inicialização e, em seguida, chamo minhas implementações de repositório para garantir que elas se comportem conforme o esperado (por exemplo, para garantir que os resultados sejam filtrados corretamente ou que eles estão classificados na ordem correta).
Esses são testes de integração e não testes de unidade, pois os testes contam com a presença de uma conexão com o banco de dados e o banco de dados de destino já possui o esquema atualizado mais recente.
fonte
Então, o Entity Framework é uma implementação. Apesar de abstrair a complexidade da interação do banco de dados, interagir diretamente ainda é um acoplamento rígido e é por isso que é confuso testar.
O teste de unidade consiste em testar a lógica de uma função e cada um de seus possíveis resultados isoladamente de quaisquer dependências externas, que neste caso são o armazenamento de dados. Para fazer isso, você precisa poder controlar o comportamento do armazenamento de dados. Por exemplo, se você deseja afirmar que sua função retorna false se o usuário buscado não atender a algum conjunto de critérios, seu armazenamento de dados [ridicularizado] deve ser configurado para sempre retornar um usuário que não atenda aos critérios e vice-versa. versa para a afirmação oposta.
Com isso dito, e aceitando o fato de que a EF é uma implementação, eu provavelmente favoreceria a idéia de abstrair um repositório. Parece um pouco redundante? Não é, porque você está resolvendo um problema que está isolando seu código da implementação de dados.
No DDD, os repositórios sempre retornam raízes agregadas, não o DAO. Dessa forma, o consumidor do repositório nunca precisa saber sobre a implementação de dados (como não deveria) e podemos usá-lo como um exemplo de como resolver esse problema. Nesse caso, o objeto gerado pelo EF é um DAO e, como tal, deve estar oculto do seu aplicativo. Esse é outro benefício do repositório que você define. Você pode definir um objeto de negócios como seu tipo de retorno em vez do objeto EF. Agora, o que o repo faz é ocultar as chamadas para o EF e mapeia a resposta do EF para esse objeto de negócios definido na assinatura do repos. Agora você pode usar esse repositório no lugar da dependência do DbContext que você injeta em suas classes e, consequentemente, agora pode simular essa interface para fornecer o controle necessário para testar seu código isoladamente.
É um pouco mais trabalhoso e muitos dão o que falar, mas resolve um problema real. Existe um provedor na memória mencionado em uma resposta diferente que pode ser uma opção (eu ainda não tentei), e sua própria existência é uma evidência da necessidade da prática.
Eu discordo completamente da resposta principal, porque ela contorna o problema real que está isolando seu código e, em seguida, continua tangente ao testar seu mapeamento. Teste sempre o seu mapeamento, se quiser, mas resolva o problema real aqui e obtenha uma cobertura de código real.
fonte
Eu não código de teste de unidade que não possuo. O que você está testando aqui, que o compilador MSFT funciona?
Dito isso, para tornar esse código testável, você quase precisa separar sua camada de acesso a dados do código lógico de negócios. O que faço é pegar todo o meu material EF e colocá-lo em uma (ou várias) classes DAO ou DAL, que também possui uma interface correspondente. Em seguida, escrevo meu serviço, que terá o objeto DAO ou DAL injetado como uma dependência (preferencialmente a injeção do construtor) referenciada como interface. Agora, a parte que precisa ser testada (seu código) pode ser facilmente testada zombando da interface do DAO e injetando-a na sua instância de serviço dentro do seu teste de unidade.
Eu consideraria as Camadas de Acesso a Dados ao vivo como parte dos testes de integração, não dos testes de unidade. Eu já vi caras verificando quantas viagens ao hibernate de banco de dados faz antes, mas eles estavam em um projeto que envolvia bilhões de registros em seu armazenamento de dados e essas viagens extras realmente importavam.
fonte
Eu me atrapalhei em algum momento para chegar a estas considerações:
1- Se meu aplicativo acessa o banco de dados, por que o teste não deve? E se houver algo errado com o acesso a dados? Os testes devem conhecê-lo de antemão e me alertar sobre o problema.
2- O Padrão do Repositório é um tanto difícil e demorado.
Então, criei essa abordagem, que não acho a melhor, mas atendi minhas expectativas:
Para isso é necessário:
1- Instale o EntityFramework no projeto de teste. 2- Coloque a string de conexão no arquivo app.config do Test Project. 3- Faça referência à dll System.Transactions no projeto de teste.
O efeito colateral exclusivo é que a semente de identidade será incrementada ao tentar inserir, mesmo quando a transação for abortada. Mas como os testes são feitos em um banco de dados de desenvolvimento, isso não deve ser problema.
Código de amostra:
fonte
Em resumo, eu diria que não, o suco não vale a pena testar um método de serviço com uma única linha que recupera dados do modelo. Na minha experiência, as pessoas que são novas no TDD querem testar absolutamente tudo. A velha castanha de abstrair uma fachada para uma estrutura de terceiros apenas para que você possa criar uma simulação dessa API de estruturas com a qual você bastardiza / estende para poder injetar dados fictícios tem pouco valor em minha mente. Todo mundo tem uma visão diferente de quanto é o melhor teste de unidade. Atualmente, sou mais pragmático e me pergunto se meu teste está realmente agregando valor ao produto final e a que custo.
fonte
Quero compartilhar uma abordagem comentada e discutida brevemente, mas mostrar um exemplo real que estou usando atualmente para ajudar a testar os serviços baseados em EF.
Primeiro, eu adoraria usar o provedor de memória do EF Core, mas isso é sobre o EF 6. Além disso, para outros sistemas de armazenamento como o RavenDB, eu também seria um proponente dos testes por meio do provedor de banco de dados na memória. Novamente - isso é especificamente para ajudar a testar o código baseado em EF sem muita cerimônia .
Aqui estão os objetivos que eu tinha ao criar um padrão:
Concordo com as afirmações anteriores de que a EF ainda é um detalhe de implementação e não há problema em sentir que você precisa abstraí-lo para fazer um teste de unidade "puro". Também concordo que, idealmente, eu gostaria de garantir que o próprio código EF funcione - mas isso envolve um banco de dados sandbox, um provedor de memória etc. Minha abordagem resolve os dois problemas - você pode testar com segurança o código dependente de EF e criar testes de integração para testar seu código EF especificamente.
A maneira como consegui isso foi simplesmente encapsular o código EF em classes dedicadas de consulta e comando. A ideia é simples: basta agrupar qualquer código EF em uma classe e depender de uma interface nas classes que o teriam usado originalmente. O principal problema que precisei resolver foi evitar adicionar inúmeras dependências às classes e configurar muito código nos meus testes.
É aqui que entra uma biblioteca útil e simples: Mediatr . Ele permite mensagens simples no processo e faz isso dissociando "solicitações" dos manipuladores que implementam o código. Isso tem um benefício adicional de dissociar o "quê" do "como". Por exemplo, ao encapsular o código EF em pequenos blocos, você pode substituir as implementações por outro provedor ou mecanismo totalmente diferente, porque tudo o que você está fazendo é enviar uma solicitação para executar uma ação.
Utilizando injeção de dependência (com ou sem uma estrutura - sua preferência), podemos facilmente zombar do mediador e controlar os mecanismos de solicitação / resposta para ativar o código EF de teste de unidade.
Primeiro, digamos que temos um serviço com lógica de negócios que precisamos testar:
Você começa a ver os benefícios dessa abordagem? Você não é apenas explicitamente encapsula todo o código relacionado ao EF em classes descritivas, como também permite extensibilidade, removendo a preocupação de implementação de "como" essa solicitação é manipulada - essa classe não se importa se os objetos relevantes vêm do EF, MongoDB, ou um arquivo de texto.
Agora, para a solicitação e o manipulador, via MediatR:
Como você pode ver, a abstração é simples e encapsulada. Também é absolutamente testável porque, em um teste de integração, você pode testar essa classe individualmente - não há preocupações comerciais misturadas aqui.
Então, como é um teste de unidade do nosso serviço de recursos? É muito simples. Nesse caso, estou usando o Moq para fazer zombaria (use o que te faz feliz):
Você pode ver tudo o que precisamos é de uma configuração única e nem precisamos configurar nada extra - é um teste de unidade muito simples. Sejamos claros: isso é totalmente possível sem algo como o Mediatr (você simplesmente implementaria uma interface e a zombaria para testes, por exemplo
IGetRelevantDbObjectsQuery
), mas na prática para uma grande base de código com muitos recursos e consultas / comandos, eu amo o encapsulamento e suporte de DI inato que o Mediatr oferece.Se você está se perguntando como eu organizo essas aulas, é bem simples:
A organização por fatias de recursos não vem ao caso, mas isso mantém todos os códigos relevantes / dependentes juntos e facilmente detectáveis. Mais importante, eu separo as consultas versus os comandos - seguindo o princípio de separação de comandos / consultas .
Isso atende a todos os meus critérios: é de cerimônia baixa, é fácil de entender e há benefícios ocultos extras. Por exemplo, como você lida com salvar alterações? Agora você pode simplificar seu contexto de banco de dados usando uma interface de função (
IUnitOfWork.SaveChangesAsync()
) e chamadas simuladas para a interface de função única ou você pode encapsular a confirmação / reversão dentro de seus RequestHandlers - no entanto, você prefere fazer isso com você, desde que seja sustentável. Por exemplo, fiquei tentado a criar uma única solicitação / manipulador genérico onde você passaria um objeto EF e o salvaria / atualizaria / removeria - mas é necessário perguntar qual é a sua intenção e lembrar que, se você quiser Para trocar o manipulador por outro provedor / implementação de armazenamento, você provavelmente deve criar comandos / consultas explícitos que representam o que você pretende fazer. Na maioria das vezes, um único serviço ou recurso precisará de algo específico - não crie itens genéricos antes que você precise.Obviamente, existem advertências para esse padrão - você pode ir longe demais com um simples mecanismo de publicação / publicação. Limitei minha implementação a apenas abstrair códigos relacionados à EF, mas desenvolvedores aventureiros poderiam começar a usar o MediatR para exagerar e colocar mensagens em itens - algo que boas práticas de revisão de código e revisões por pares devem capturar. Esse é um problema de processo, não do MediatR, portanto, fique ciente de como você está usando esse padrão.
Você queria um exemplo concreto de como as pessoas estão testando / zombando da EF e esta é uma abordagem que está funcionando com sucesso para nós em nosso projeto - e a equipe está super feliz com a facilidade de adoção. Eu espero que isso ajude! Como em todas as coisas da programação, existem várias abordagens e tudo depende do que você deseja alcançar. Valorizo a simplicidade, a facilidade de uso, a capacidade de manutenção e a descoberta - e essa solução atende a todas essas demandas.
fonte
Existe um esforço que é um provedor de banco de dados da estrutura da entidade na memória. Na verdade, eu ainda não tentei ... Haa acabou de descobrir que isso foi mencionado na pergunta!
Como alternativa, você pode alternar para EntityFrameworkCore, que possui um provedor de banco de dados de memória embutido.
https://blog.goyello.com/2016/07/14/save-time-mocking-use-your-real-entity-framework-dbcontext-in-unit-tests/
https://github.com/tamasflamich/effort
Eu usei uma fábrica para obter um contexto, para que eu possa criar o contexto próximo ao seu uso. Isso parece funcionar localmente no visual studio, mas não no servidor de criação do TeamCity, ainda não sei por que.
fonte
Gosto de separar meus filtros de outras partes do código e testá-los conforme descrito em meu blog aqui http://coding.grax.com/2013/08/testing-custom-linq-filter-operators.html
Dito isto, a lógica do filtro que está sendo testada não é idêntica à lógica do filtro executada quando o programa é executado devido à conversão entre a expressão LINQ e a linguagem de consulta subjacente, como o T-SQL. Ainda assim, isso me permite validar a lógica do filtro. Não me preocupo muito com as traduções que acontecem e coisas como diferenciação entre maiúsculas e minúsculas até testar a integração entre as camadas.
fonte
É importante testar o que você espera que a estrutura da entidade faça (ou seja, validar suas expectativas). Uma maneira de fazer isso que usei com êxito é usar o moq, como mostrado neste exemplo (por muito tempo para copiar nesta resposta):
https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking
No entanto, tenha cuidado ... Não é garantido que um contexto SQL retorne as coisas em uma ordem específica, a menos que você tenha um "OrderBy" apropriado em sua consulta linq, portanto, é possível escrever as coisas que passam quando você testa usando uma lista na memória ( linq para entidades), mas falha em seu ambiente uat / live quando (linq para sql) é usado.
fonte