Como TDD que os resultados corretos são retornados


12

Estou iniciando um novo projeto e tentando muito com muito esforço usar o TDD para conduzir o design. Estou pressionando há anos e finalmente obtive aprovação para dedicar mais tempo a esse projeto para usá-lo enquanto aprendo como fazê-lo corretamente.

Este é um novo módulo, vinculado a um sistema existente. Atualmente, todo o acesso a dados acontece por meio de serviços da web, que na maioria das vezes são apenas um invólucro fino sobre os procedimentos armazenados no banco de dados.

Um requisito é que, para uma determinada loja, eu retorne todos os pedidos considerados válidos para este aplicativo. Um pedido de compra é considerado válido se a data de envio cair dentro de um determinado intervalo a partir da data de abertura das lojas (isto é para novas lojas).

Agora, não posso colocar essa lógica no código do aplicativo, pois não vou trazer de volta um milhão de POs apenas para que a dúzia que se aplica possa se aplicar a esse armazenamento, devido à restrição acima.

Eu estava pensando em poder passar o período para um processo GetValidPOs e fazer com que eles usem esses valores para retornar os POs válidos. Mas, e se adicionarmos outro requisito ao que é considerado um pedido válido?

E como faço para testar isso e verificar se continua funcionando? Não estamos usando um ORM, e é improvável que isso aconteça. E não posso ligar para o banco de dados no meu teste.

Estou preso.

Meu outro pensamento, é ter algumas simulações que retornam dados válidos, outras que retornam alguns dados inválidos e o repositório local lança uma exceção se ocorrerem dados incorretos e testa se a exceção é lançada se dados inválidos forem retornados pelo processo GetValidPOs (ou a simulação usada nos testes).

Isso faz sentido? Ou há um jeito melhor?

ATUALIZAÇÃO: Eu sou capaz de usar EF parece. Agora só preciso descobrir como usá-lo e torná-lo testável, enquanto ainda posso confiar nos procedimentos armazenados e na dificuldade de ter dados espalhados por vários bancos de dados.


Por curiosidade, por que você não pode selecionar apenas os pedidos válidos com uma instrução SQL simples? (Esta pergunta ou a resposta não implica uma solução.)
scarfridge

Respostas:


7

Esta é uma desvantagem importante dos procedimentos armazenados na era do TDD. Eles têm algumas vantagens reais, mesmo agora, mas, por definição, qualquer teste que exerça um procedimento armazenado não é um teste de unidade; é um teste de integração na melhor das hipóteses.

A solução usual, supondo que a arquitetura não possa ser alterada para usar um ORM, é não colocar esses testes no conjunto de testes de unidade; em vez disso, coloque os testes em um conjunto de integração. Você ainda pode executar o teste sempre que quiser verificar se ele funciona, mas como o custo inerente à configuração do teste (inicializando um banco de dados com os dados de teste adequados) é alto e afeta os recursos que o agente de teste de unidade do seu build-bot pode não ter acesso a, ele não deve estar no conjunto de testes de unidade.

Você ainda pode testar o código da unidade que requer os dados, abstraindo qualquer coisa que não puder testar na unidade (classes ADO.NET) em uma classe DAO que você pode zombar. Em seguida, você pode verificar se as chamadas esperadas são feitas consumindo código e reproduzindo o comportamento do mundo real (como não encontrar resultados), permitindo o teste de vários casos de uso. No entanto, a configuração real do SqlCommand para chamar o processo armazenado é praticamente a última coisa que você pode testar em unidade, cortando a criação de comandos da execução de comandos e zombando do executor de comandos. Se isso soa como muita separação de preocupações, pode ser; lembre-se: "não há problema que não possa ser resolvido por outra camada de indireção, exceto por ter muitas camadas de indireção". Em algum momento, você deve dizer "basta; eu simplesmente não posso testar isso, nós

Outras opções:

  • Teste o processo armazenado usando uma instância DBMS "de curta duração" como SQLite. Geralmente é mais fácil fazer isso ao usar um ORM, mas o teste pode ser feito "na memória" (ou com um arquivo de banco de dados predefinido incluído no conjunto de testes). Ainda não é um teste de unidade, mas pode ser executado com um alto grau de isolamento (o DBMS faz parte do processo em execução e não é algo ao qual você se conecta remotamente, que pode estar no meio do conjunto de testes conflitante de outra pessoa). A desvantagem é que as alterações no processo armazenado podem ocorrer na produção sem que o teste reflita a alteração; portanto, você deve ser disciplinado em garantir que a alteração seja feita primeiro em um ambiente de teste.

  • Considere atualizar para um ORM. Um ORM com um provedor Linq (praticamente todos os de uso comum têm um) permitiria definir a consulta como uma instrução Linq; essa declaração pode ser dada a um Repositório zombado que possui uma coleção de dados de teste na memória para aplicá-lo. Assim, você pode verificar se a consulta está correta sem sequer tocar no banco de dados (ainda deve executar a consulta em um ambiente de integração, para testar se o provedor Linq pode digerir corretamente a consulta).


2
-1 porque TDD! = Teste de unidade. Perfeitamente bem para incluir testes no nível de integração ao fazer TDD.
Steven A. Lowe

O teste de unidade é um subconjunto de desenvolvimento orientado a testes. Em um desenvolvimento orientado a testes, você criaria um esqueleto móvel do seu sistema e executaria testes de unidade, integração e funcionais nesse sistema. Seus testes de integração, unidade ou aceitação falham e você os faz passar e escrever outros testes.
CodeART

1
Eu entendo tudo isso, vocês dois. Onde eu disse que ter que ser um teste de integração significava que você não podia TDD? Meu argumento era que um procedimento armazenado não pode ser testado isoladamente, o que você deseja fazer com o máximo de sua base de código possível. Testar SPs requer testes de integração mais complexos e de execução mais longa; Embora ainda sejam melhores que os testes manuais, um conjunto de testes com integração pesada pode levar horas para ser executado e pode ter um efeito prejudicial nos esforços de IC.
KeithS 7/12/16

O teste de SP geralmente também requer um conjunto de dados específico no banco de dados de teste; o código para colocar o banco de dados no estado adequado para alcançar os resultados esperados é muitas vezes mais LoC e várias vezes mais demorado que o código que você está realmente exercitando. Isso agrava ainda mais a complexidade do tempo do conjunto de testes e, geralmente, a configuração deve ser repetida para cada teste individual (e provavelmente deve haver vários para cada SP, para testar se todos os requisitos funcionais da consulta são atendidos).
KeithS 7/12/12

Os procedimentos armazenados podem ser testados isoladamente. De que outra forma eles seriam validados? Para Transact SQL há tSQLt ( tsqlt.org )
kevin Cline

4

Meu conselho é dividir e conquistar . Por enquanto, esqueça o banco de dados e a persistência e concentre-se em testar implementações falsas de seus repositórios ou objetos de acesso a dados.

Agora, não posso colocar essa lógica no código do aplicativo, pois não vou trazer de volta um milhão de POs apenas para que a dúzia que se aplica possa se aplicar a esse armazenamento, devido à restrição acima.

Eu zombaria do repositório que retorna pedidos de compra. Crie uma simulação com vinte pedidos de compra ímpares.

Eu estava pensando em poder passar o período para um processo GetValidPOs e fazer com que ele use esses valores para retornar os pedidos válidos. Mas, e se adicionarmos outro requisito ao que é considerado um pedido válido?

Stub uma chamada para GetValidPOs para que ele chame seu procedimento simulado, em vez de banco de dados.

E como faço para testar isso e verificar se continua funcionando? Não estamos usando um ORM, e é improvável que isso aconteça. E não posso ligar para o banco de dados no meu teste.

Você precisa de um teste de unidade para garantir que os dados corretos sejam retornados de uma simulação.

Você também precisa de um teste de integração para garantir que os dados corretos sejam retornados de um banco de dados. O teste de integração exigiria alguma configuração e limpeza. Por exemplo, antes de executar o teste de integração, propague seu banco de dados executando um script. Verifique se o seu script funcionou. Consulte o banco de dados chamando seus procedimentos armazenados. Verifique se seus resultados estão corretos. Limpe o banco de dados.

Meu outro pensamento, é ter algumas simulações que retornam dados válidos, outras que retornam alguns dados inválidos e o repositório local lança uma exceção se ocorrerem dados incorretos e testa se a exceção é lançada se dados inválidos forem retornados pelo processo GetValidPOs (ou a simulação usada nos testes).

Como eu já disse, você precisa de uma simulação que retorne pelo menos alguns dados que você possa consultar.

Ao consultar dados, você deseja garantir que seu sistema possa lidar com exceções normalmente. Portanto, você zomba do comportamento para que ele gere exceções em determinados cenários. Você então escreve testes para garantir que seu sistema possa lidar com essas exceções normalmente.


É isso que estou tentando fazer. Apenas tendo dificuldade em escrever uma implementação real que funcionará da mesma forma que a simulação, já que nosso acesso a dados não é propício ao uso de um ORM. A maioria dos dados de que preciso está em vários sistemas e deve ser acessada através de serviços da Web ... mesmo durante a atualização.
CaffGeek

0

Assim como testar a unidade Java ou Javascript significa escrever testes de unidade usando a linguagem Java para java, e testar funções de Javascript com Javascript, escrever testes automatizados para levá-lo a escrever procedimentos armazenados significa que a biblioteca de testes unitários que você está procurando se baseia procedimentos.

Dito de outra maneira, use procedimentos armazenados para testar procedimentos armazenados porque:

  • como você está desenvolvendo na linguagem de procedimentos, você deve ter a habilidade de escrever seus testes na linguagem de procedimentos
  • escrever testes na linguagem do procedimento aumentará sua habilidade na linguagem do procedimento, o que, por sua vez, ajudará o desenvolvimento do seu produto
  • você terá acesso direto a todas as ferramentas fornecidas pelo seu banco de dados e poderá usá-las para manter os testes de unidade o mais simples possível
  • os testes de unidade armazenados no mesmo banco de dados que os procedimentos que eles estão testando serão rápidos (o mais próximo do teste de unidade, como velocidades), porque você não está ultrapassando os limites do sistema

Assim como o TDD em uma linguagem OO, você deseja que seu teste de unidade configure apenas uma linha ou mais de dados para testar o que é necessário para o procedimento (minimalismo, apenas ter o que seus testes simples precisam). O resultado disso é que você terá vários testes de unidade simples para cada procedimento armazenado. Esses testes simples serão mais fáceis de manter do que os testes complicados que dependem de um grande conjunto de dados que não é facilmente mapeado de volta para o que o teste realmente precisa.

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.