Testes de unidade e bancos de dados: em que momento eu realmente me conecto ao banco de dados?

37

Há respostas para a pergunta sobre como as classes de teste que se conectam a um banco de dados, por exemplo, "As classes de teste de serviço devem se conectar ..." e "Teste de unidade - aplicativo acoplado ao banco de dados" .

Então, resumindo, vamos supor que você tenha uma classe A que precisa se conectar a um banco de dados. Em vez de permitir que A realmente se conecte, você fornece a A uma interface que A pode usar para se conectar. Para testar, você implementa essa interface com algumas coisas - sem conectar, é claro. Se a classe B instancia A, ela deve passar uma conexão de banco de dados "real" para A. Mas isso significa que B abre uma conexão de banco de dados. Isso significa que para testar B, você injeta a conexão em B. Mas B é instanciado na classe C e assim por diante.

Então, em que momento devo dizer "aqui busco dados de um banco de dados e não escreverei um teste de unidade para esse trecho de código"?

Em outras palavras: Em algum lugar no código de alguma classe I deve chamar sqlDB.connect()ou algo similar. Como faço para testar esta classe?

E é o mesmo com o código que tem que lidar com uma GUI ou um sistema de arquivos?


Eu quero fazer o teste de unidade. Qualquer outro tipo de teste não está relacionado à minha pergunta. Eu sei que só testarei uma classe com ela (eu concordo com você Kilian). Agora, algumas classes precisam se conectar a um banco de dados. Se eu quiser testar esta classe e perguntar "Como faço isso", muitos dizem: "Use Injeção de Dependência!" Mas isso apenas muda o problema para outra classe, não é? Então pergunto: como faço para testar a classe que realmente estabelece a conexão?

Pergunta bônus: algumas respostas aqui se resumem a "Usar objetos simulados!" O que isso significa? Eu zombo de classes das quais a classe em teste depende. Devo zombar da classe em teste agora e realmente testar a zombaria (que se aproxima da idéia de usar Métodos de Modelo, veja abaixo)?

TobiMcNamobi
fonte
É a conexão com o banco de dados que você está testando? Criar um banco de dados temporário na memória (como derby ) seria aceitável?
@ MichaelT Ainda tenho que substituir o banco de dados temporário na memória por um banco de dados real. Onde? Quando? Como é testado em unidade? Ou não há problema em testar esse código por unidade?
TobiMcNamobi
3
Não há nada para "teste de unidade" na base de dados. Ele é mantido por outras pessoas e, se houvesse um erro, você teria que deixá-lo consertá-lo, em vez de fazer você mesmo. A única coisa que deve diferir entre o uso real da sua classe e o uso durante os testes devem ser os parâmetros da conexão com o banco de dados. É improvável que o código de leitura do arquivo de propriedades ou o mecanismo de injeção do Spring ou o que você usa para tecer seu aplicativo esteja quebrado (e, se fosse, você não poderia consertar você mesmo, veja acima) - então eu considero aceitável para não testar esse pouco da funcionalidade do encanamento.
precisa
2
@KilianFoth que está puramente relacionado ao ambiente de trabalho e às funções dos funcionários. Realmente não tem nada a ver com a pergunta. E se não houver uma pessoa responsável pelo banco de dados?
Reactgular 30/07/2013
Algumas estruturas de simulação permitem injetar objetos simulados em praticamente qualquer coisa, mesmo em membros privados e estáticos. Isso facilita muito o teste com conexões falsas de banco de dados. Mockito + Powermock é o que funciona para mim hoje em dia (eles são Java, não sabem o que você está trabalhando).
FrustratedWithFormsDesigner

Respostas:

21

O objetivo de um teste de unidade é testar uma classe (na verdade, ele geralmente deve testar um método ).

Isso significa que, ao testar a classe A, você injeta um banco de dados de teste nela - algo auto-escrito ou um banco de dados na memória extremamente rápido, o que quer que seja feito.

No entanto, se você testar a classe B, que é cliente A, geralmente você zomba do Aobjeto inteiro com outra coisa, presumivelmente algo que faz seu trabalho de maneira primitiva e pré-programada - sem usar um Aobjeto real e certamente sem usar dados. base (a menos que Apasse toda a conexão do banco de dados de volta para o chamador - mas isso é tão horrível que não quero pensar nisso). Da mesma forma, ao escrever um teste de unidade para a classe C, que é um cliente B, você zombaria de algo que assume o papel Be esquece Acompletamente.

Se você não fizer isso, não será mais um teste de unidade, mas um teste de sistema ou integração. Esses são muito importantes também, mas uma chaleira totalmente diferente de peixe. Para começar, eles geralmente têm mais esforço para configurar e executar, não é praticável exigir que sejam passados ​​como pré-condição para check-ins etc.

Kilian Foth
fonte
11

Executar testes de unidade em uma conexão com o banco de dados é perfeitamente normal e é uma prática comum. Simplesmente não é possível criar uma puristabordagem em que tudo no seu sistema seja injetável em dependência.

A chave aqui é testar em um banco de dados temporário ou somente em teste e ter o processo de inicialização mais leve possível para criar esse banco de dados de teste.

Para testes de unidade no CakePHP, existem algumas coisas chamadas fixtures. As luminárias são tabelas de banco de dados temporárias criadas dinamicamente para um teste de unidade. O equipamento possui métodos de conveniência para criá-los. Eles podem recriar um esquema de um banco de dados de produção dentro do banco de dados de teste ou você pode definir o esquema usando uma notação simples.

A chave para o sucesso é não implementar o banco de dados comercial, mas focar apenas no aspecto do código que você está testando. Se você tiver um teste de unidade que verifique se um modelo de dados lê apenas documentos publicados, o esquema da tabela para esse teste deve ter apenas os campos exigidos por esse código. Você não precisa reimplementar um banco de dados inteiro de gerenciamento de conteúdo apenas para testar esse código.

Algumas referências adicionais.

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/en/database.html

http://book.cakephp.org/2.0/en/development/testing.html#fixtures

Reactgular
fonte
28
Discordo. Um teste que requer uma conexão com o banco de dados não é um teste de unidade, porque, por sua própria natureza, terá efeitos colaterais. Isso não significa que você não pode escrever um teste automatizado, mas esse teste é, por definição, um teste de integração, exercitando áreas do seu sistema além da sua base de código.
Keiths
5
Me chame de purista, mas mantenho o princípio de que um teste de unidade não deve executar nenhuma ação que saia da "caixa de areia" do ambiente de tempo de execução de teste. Eles não devem tocar em bancos de dados, sistemas de arquivos, soquetes de rede etc. Isso ocorre por vários motivos, entre os quais a dependência do teste em relação ao estado externo. Outro é desempenho; seu conjunto de testes de unidade deve ser executado rapidamente e a interface com esses armazenamentos de dados externos retarda os testes por ordens de magnitude. No meu próprio desenvolvimento, uso zombarias parciais para testar coisas como meus repositórios, e me sinto confortável em definir uma "borda" para minha caixa de areia.
30713 KeithS
2
@gbjbaanb - Parece bom no começo, mas na minha experiência é muito perigoso. Mesmo nos conjuntos e estruturas de teste com melhor arquitetura, o código para reverter essa transação pode não ser executado. Se o executor de teste travar ou for interrompido dentro de um teste, ou o teste lançar um SOE ou OOME, o melhor caso é que você tenha uma conexão e uma transação suspensas no banco de dados que bloquearão as tabelas que você tocou até que a conexão seja interrompida. As maneiras de evitar que isso cause problemas, como usar o SQLite como um banco de dados de teste, têm suas próprias desvantagens, por exemplo, o fato de você não estar realmente exercitando o banco de dados real .
30513 KeithS
5
@KeithS Acho que estamos debatendo sobre semântica. Não se trata da definição de um teste de unidade ou de um teste de integração. Eu uso equipamentos para testar o código dependente de uma conexão com o banco de dados. Se isso é um teste de integração, então eu estou bem com isso. Eu preciso saber que o teste passa. Eu não podia me importar com as dependências, desempenho ou riscos. Não saberei se esse código funciona, a menos que o teste seja aprovado. Para a maioria dos testes, não há dependências, mas para os que existem, essas dependências não podem ser desacopladas. É fácil dizer que deveriam, mas simplesmente não podem.
Reactgular 30/07/2013
4
Eu acho que também estamos. Também uso uma "estrutura de teste de unidade" (NUnit) para meus testes de integração, mas não se esqueça de separar essas duas categorias de testes (geralmente em bibliotecas separadas). O ponto que eu estava tentando enfatizar é que seu conjunto de testes de unidade, aquele que você executa várias vezes ao dia antes de cada check-in, ao seguir a metodologia iterativa de refator vermelho-verde, deve ser completamente isolável, para que você possa executar faça esses testes várias vezes ao dia sem pisar nos dedos dos colegas de trabalho.
30713 KeithS
4

Em algum lugar da sua base de código, há uma linha de código que executa a ação real de conexão com o banco de dados remoto. Essa linha de código é, 9 vezes em 10, uma chamada para um método "interno" fornecido pelas bibliotecas de tempo de execução específicas para seu idioma e ambiente. Como tal, não é o código "your" e você não precisa testá-lo; para fins de um teste de unidade, você pode confiar que esta chamada de método será executada corretamente. O que você pode e deve ainda testar em seu conjunto de testes unitários é como garantir que os parâmetros que serão usados ​​para esta chamada sejam o que você espera que sejam, como garantir que a cadeia de conexão esteja correta ou a instrução SQL ou nome do procedimento armazenado.

Esse é um dos propósitos por trás da restrição de que os testes de unidade não devem deixar seu "sandbox" de tempo de execução e depender do estado externo. Na verdade, é bastante prático; o objetivo de um teste de unidade é verificar se o código que você escreveu (ou está prestes a escrever, no TDD) se comporta da maneira que você pensava. O código que você não escreveu, como a biblioteca que você está usando para executar suas operações de banco de dados, não deve fazer parte do escopo de qualquer teste de unidade, pelo motivo muito simples de você não ter escrito.

No seu conjunto de testes de integração , essas restrições são relaxadas. Agora você podetestes de design que tocam o banco de dados, para garantir que o código que você escreveu seja muito bom com o código que você não escreveu. Esses dois conjuntos de testes devem permanecer segregados, no entanto, porque seu conjunto de testes de unidade é mais eficaz quanto mais rápido ele é executado (para que você possa verificar rapidamente se todas as asserções feitas pelos desenvolvedores sobre seu código ainda são válidas) e, quase por definição, um teste de integração é mais lento em ordens de grandeza devido às dependências adicionais de recursos externos. Deixe o build-bot manipular a execução de seu conjunto completo de integração a cada poucas horas, executando os testes que bloqueiam recursos externos, para que os desenvolvedores não pisem nos dedos dos outros executando esses mesmos testes localmente. E se a construção quebrar, e daí? É dada muito mais importância à garantia de que o build-bot nunca falha em uma build do que provavelmente deveria.


Agora, a aderência estrita a isso depende de sua estratégia exata para conectar e consultar o banco de dados. Em muitos casos em que você deve usar a estrutura de acesso a dados "básicos", como os objetos SqlConnection e SqlStatement do ADO.NET, um método inteiro desenvolvido por você pode consistir em chamadas de método internas e outro código que depende de um conexão com o banco de dados e, portanto, o melhor que você pode fazer nessa situação é zombar de toda a função e confiar em seus conjuntos de testes de integração. Também depende de como você está disposto a projetar suas classes para permitir que linhas de código específicas sejam substituídas para fins de teste (como a sugestão de Tobi do padrão Template Method, que é boa porque permite "zombarias parciais"

Se o modelo de persistência de dados se basear no código da camada de dados (como gatilhos, procs armazenados, etc.), simplesmente não há outra maneira de exercitar o código que você está escrevendo, além de desenvolver testes que residam na camada de dados ou cruzam a camada de dados. limite entre o tempo de execução do aplicativo e o DBMS. Um purista diria que esse padrão, por esse motivo, deve ser evitado em favor de algo como um ORM. Eu não acho que iria tão longe; mesmo na era das consultas integradas ao idioma e outras operações de persistência dependentes do domínio, verificadas pelo compilador, vejo o valor em bloquear o banco de dados apenas nas operações expostas pelo procedimento armazenado e, é claro, esses procedimentos armazenados devem ser verificados usando automação testes. Mas, esses testes não são testes de unidade . Eles são integração testes.

Se você tiver um problema com essa distinção, ela geralmente é baseada em uma importância alta colocada na "cobertura de código" completa, também conhecida como "cobertura de teste de unidade". Você deseja garantir que todas as linhas do seu código sejam cobertas por um teste de unidade. Um objetivo nobre em seu rosto, mas eu digo besteira; essa mentalidade se presta a antipadrões que vão muito além desse caso específico, como escrever testes sem asserção que executam, mas não exercemseu código. Esses tipos de execuções finais exclusivamente para obter números de cobertura são mais prejudiciais do que relaxar a cobertura mínima. Se você deseja garantir que todas as linhas da sua base de código sejam executadas por algum teste automatizado, isso é fácil; ao calcular métricas de cobertura de código, inclua os testes de integração. Você pode até dar um passo adiante e isolar esses testes "Itino" disputados ("Integração apenas no nome") e, entre o seu conjunto de testes de unidade e esta subcategoria de testes de integração (que ainda deve executar razoavelmente rápido), você deve se danar quase perto da cobertura total.

KeithS
fonte
2

Os testes de unidade nunca devem se conectar a um banco de dados. Por definição, eles devem testar uma única unidade de código cada (um método) em total isolamento do resto do seu sistema. Caso contrário, não são um teste de unidade.

Semântica à parte, há uma infinidade de razões pelas quais isso é benéfico:

  • Testes executam ordens de magnitude mais rapidamente
  • O loop de feedback se torna instantâneo (<1s de feedback para TDD, como exemplo)
  • Os testes podem ser executados em paralelo para criar / implantar sistemas
  • Os testes não precisam de um banco de dados para serem executados (torna a construção muito mais fácil, ou pelo menos mais rápida)

Os testes de unidade são uma maneira de verificar seu trabalho. Eles devem descrever todos os cenários de um determinado método, o que normalmente significa todos os diferentes caminhos através de um método. É a sua especificação que você está construindo, semelhante à contabilidade de entrada dupla.

O que você está descrevendo é outro tipo de teste automatizado: um teste de integração. Enquanto eles também são muito importantes, idealmente, você terá muito menos deles. Eles devem verificar se um grupo de unidades se integra adequadamente.

Então, como você testa as coisas com acesso ao banco de dados? Todo o seu código de acesso a dados deve estar em uma camada específica, para que o código do seu aplicativo possa interagir com serviços simuláveis ​​em vez do banco de dados real. Não importa se esses serviços são apoiados por qualquer tipo de banco de dados SQL, dados de teste na memória ou mesmo dados remotos de serviço da web. Essa não é a preocupação deles.

Idealmente (e isso é muito subjetivo), você deseja que a maior parte do seu código seja coberta por testes de unidade. Isso lhe dá confiança de que cada peça funciona de forma independente. Uma vez que as peças são construídas, você precisa juntá-las. Exemplo - quando eu faço o hash da senha do usuário, devo obter essa saída exata.

Digamos que cada componente é composto por aproximadamente 5 classes - você deseja testar todos os pontos de falha dentro deles. Isso equivale a muito menos testes apenas para garantir que tudo esteja conectado corretamente. Exemplo - teste, você pode encontrar o usuário no banco de dados com um nome de usuário / senha.

Por fim, você deseja que alguns testes de aceitação garantam que você está cumprindo os objetivos de negócios. Há ainda menos desses; eles podem garantir que o aplicativo esteja em execução e faça o que foi criado para fazer. Exemplo - dados esses dados de teste, eu devo conseguir fazer login.

Pense nesses três tipos de testes como uma pirâmide. Você precisa de muitos testes de unidade para dar suporte a tudo e, em seguida, avança a partir daí.

Adrian Schneider
fonte
1

O padrão do método de modelo pode ajudar.

Você quebra as chamadas para um banco de dados nos protectedmétodos. Para testar essa classe, você na verdade testa um objeto falso que herda da classe de conexão do banco de dados real e substitui os métodos protegidos.

Dessa forma, as chamadas reais ao banco de dados nunca estão sob testes de unidade, isso mesmo. Mas são apenas essas poucas linhas de código. E isso é aceitável.

TobiMcNamobi
fonte
1
Caso você se pergunte por que respondo minha própria pergunta: Sim, essa poderia ser uma resposta, mas não tenho certeza se é a correta.
TobiMcNamobi 30/07/2013
-1

Testar com dados externos é um teste de integração. Teste de unidade significa que você está testando apenas a unidade. É feito principalmente com sua lógica de negócios. Para tornar sua unidade de código testável, você deve seguir algumas diretrizes, como se você fosse tornar sua unidade independente de outras partes do seu código. Durante o teste de unidade, se você precisar de dados, precisará injetar esses dados à força com injeção de dependência. Existem algumas estruturas de zombaria e stubbing por aí.

DesenvolvedorArnab
fonte