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)?
fonte
Respostas:
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 é clienteA
, geralmente você zomba doA
objeto inteiro com outra coisa, presumivelmente algo que faz seu trabalho de maneira primitiva e pré-programada - sem usar umA
objeto real e certamente sem usar dados. base (a menos queA
passe 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 classeC
, que é um clienteB
, você zombaria de algo que assume o papelB
e esqueceA
completamente.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.
fonte
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
purist
abordagem 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
fonte
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.
fonte
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:
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í.
fonte
O padrão do método de modelo pode ajudar.
Você quebra as chamadas para um banco de dados nos
protected
mé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.
fonte
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í.
fonte