Como aplico o TDD para funções de leitura / gravação?

10

Parece um problema de galinha e ovo.

Você pode gravar uma função de gravação em algum armazenamento de dados, mas nunca sabe que a salvou corretamente sem uma função de leitura testada.

Você pode fazer com que uma função de leitura seja lida em um armazenamento de dados, mas como você coloca coisas nesse armazenamento de dados para ler, sem uma função de gravação testada?

EDITAR:

Estou me conectando e fazendo transações com um banco de dados SQL para salvar e carregar objetos para uso. Não faz sentido testar as funções de acesso que o banco de dados fornece, mas envolvo essas funções para serializar / desserializar os objetos. Quero ter certeza de que estou escrevendo e lendo as coisas certas de e para o banco de dados corretamente.

Não é como adicionar / excluir, como o @snowman menciona. Quero saber que o conteúdo que escrevi está correto, mas isso requer uma função de leitura bem testada. Quando eu leio, quero ter certeza de que minha leitura criou corretamente um objeto igual ao que foi escrito; mas isso requer uma função de gravação bem testada.

user2738698
fonte
Você está escrevendo seu próprio armazenamento de dados ou usando um existente? Se você estiver usando um já existente, suponha que ele já funcione. Se você está escrevendo seu próprio, isso funciona da mesma maneira que qualquer outro software usando TDD.
Robert Harvey
1
Embora intimamente relacionado, não acho que seja uma duplicata dessa questão em particular. O alvo dupe está falando sobre adicionar / excluir, este é de leitura / gravação. A diferença é que uma dependência de leitura / gravação provavelmente depende do conteúdo dos objetos que estão sendo lidos / gravados, enquanto um simples teste de adição / exclusão provavelmente seria muito mais simples: o objeto existe ou não.
2
Você pode preparar um banco de dados com dados e nenhuma função de gravação para testar uma função de leitura.
JeffO 6/03

Respostas:

7

Comece com a função de leitura.

  • Na configuração de teste : crie o banco de dados e adicione dados de teste. via scripts de migração ou de um backup. Como este não é o seu código, ele não requer um teste no TDD

  • No teste : instanciar seu repositório, aponte para o seu banco de dados de teste e chame o método Read. Verifique se os dados de teste foram retornados.

Agora você tem uma função de leitura totalmente testada, pode passar para a função Write, que pode usar a leitura existente para verificar seus próprios resultados

Ewan
fonte
Eu acho que você pode criar um banco de dados na memória para acelerar as coisas, mas isso pode ser muito complexo. Por que não usar zombarias em testes de unidade?
21717
1
Um pouco do advogado do diabo aqui, mas como você testa se o banco de dados foi criado corretamente? Como OP afirmou, frango e ovo.
user949300
1
@ Ewan - eu concordo absolutamente que você não deve testar o código do banco de dados. Mas como você sabe que o código de configuração do banco de dados não esqueceu um INSERT em algum lugar ou colocou um valor errado em uma coluna?
user949300
1
de uma abordagem TDD pura, o teste é o requisito. então logicamente não pode estar errado. obvs no mundo real você tem que globo ocular
Ewan
1
Quis custodiet ipsos custodes? Ou "Quem testa os testes?" :-) Eu concordo com você que, em um mundo purista de TDD, essa seria a maneira terrivelmente tediosa e propensa a erros (especialmente se fosse uma estrutura complicada de tabelas múltiplas com 8 JOINS). Votado.
user949300
6

Costumo fazer uma gravação seguida de uma leitura. por exemplo (pseudocódigo)

Foo foo1 = setup some object to write
File tempfile = create a tempfile, possibly in memory 
writeFoo(foo1, tempfile) 
Foo foo2 = readFoo(tempfile) 
assertEquals(foo1, foo2); 
clean-up goes here

Adicionado mais tarde

Além de essa solução ser "prgamatic" e "good enough", pode-se argumentar que as outras soluções testam a coisa errada . Testar se as strings ou instruções SQL correspondem não é uma péssima idéia, eu mesmo a fiz, mas está testando um efeito colateral e é frágil. E se você alterar a capitalização, adicionar um campo ou atualizar um número de versão nos seus dados? E se o seu driver SQL alternar a ordem das chamadas de eficiência, ou o serializador XML atualizado adicionar um espaço extra ou alterar uma versão do esquema?

Agora, se você deve aderir estritamente a algumas especificações oficiais, concordo que a verificação dos detalhes é apropriada.

user949300
fonte
1
Porque é 90% pseudocódigo realmente denso? Não tenho certeza. Talvez destaque o texto e torne o código menos barulhento?
precisa
1
Sim @Ewan. O fanático desaprovaria isso, no entanto, o programador pragmático dizia "bom o suficiente" e seguia em frente.
precisa
1
eu li um bocado a questão .. "Supondo que eu siga TDD como um fanático ..."
Ewan
1
Minha interpretação do OP e do TDD é que seu teste deve ser escrito primeiro e não deve ser usado tanto para leitura quanto para gravação, a menos que um seja testado em outro lugar também.
Ewan
2
você está testando, 'leitura deve retornar o que eu escrevo', mas as exigências são 'lidas deve retornar dados do db' e 'write deve gravar dados no db'
Ewan
4

Não. Não faça E / S de teste de unidade. É uma perda de tempo.

Lógica de teste de unidade. Se houver muita lógica que você deseja testar no código de E / S, refate seu código para separar a lógica de como você faz E / S e o que você faz dos negócios reais de fazer E / S (o que é quase impossível de testar).

Para elaborar um pouco, se você quiser testar um servidor HTTP, faça-o através de dois tipos de testes: testes de integração e testes de unidade. Os testes de unidade não devem interagir com a E / S. Isso é lento e apresenta muitas condições de erro que nada têm a ver com a correção do seu código. Os testes de unidade não devem estar sujeitos ao estado da sua rede!

Seu código deve separar:

  • A lógica de determinar quais informações enviar
  • A lógica de determinar quais bytes enviar para enviar um bit específico de informação (como codifico uma resposta etc. em bytes brutos) e
  • O mecanismo de gravar esses bytes em um soquete.

Os dois primeiros envolvem lógica e decisões e precisam de testes de unidade. O último não envolve tomar muitas decisões, se houver, e pode ser testado maravilhosamente usando o teste de integração.

Na verdade, esse é apenas um bom design, na verdade, mas uma das razões é que facilita o teste.


aqui estão alguns exemplos:

  • Se você estiver escrevendo um código que obtém dados de um banco de dados relacional, pode fazer um teste de unidade como mapeia os dados retornados de consultas relacionais para o seu modelo de aplicativo.
  • Se você estiver escrevendo um código que grava dados em um banco de dados relacional, é possível testar por unidade quais dados você deseja gravar no banco de dados sem realmente testar as consultas SQL específicas usadas. Por exemplo, você pode manter duas cópias do estado do aplicativo na memória: uma cópia representando a aparência do banco de dados e a cópia de trabalho. Quando você deseja sincronizar com o banco de dados, é necessário diferenciá-los e gravar as diferenças no banco de dados. Você pode facilmente testar esse código diff.
  • Se você estiver escrevendo um código que lê algo de um arquivo de configuração, deseja testar o analisador de formato do arquivo de configuração, mas com cadeias de caracteres do arquivo de origem de teste, em vez de cadeias de caracteres obtidas do disco.
Rota das milhas
fonte
2

Eu não sei se isso é uma prática padrão ou não, mas funciona bem para mim.

Nas minhas implementações de método de gravação e leitura que não são do banco de dados, eu uso meus próprios métodos toString()e tipos específicos fromString()como detalhes de implementação.

Estes podem ser facilmente testados isoladamente:

 assertEquals("<xml><car type='porsche'>....", new Car("porsche").toString());

Para os métodos reais de leitura e gravação, eu tenho um teste de integração que lê e grava fisicamente em um teste

A propósito: Há algo errado em ter um teste que lê / escreve juntos?

k3b
fonte
Pode não parecer bom ou "puro", mas esta é a solução pragmática.
precisa
Eu também gosto da ideia de testar a leitura e gravação em conjunto. Seu toString () é um bom compromisso pragmático.
user949300
1

Os dados conhecidos devem ser formatados de maneira conhecida. A maneira mais fácil de implementar isso é usar uma string constante e comparar o resultado, como @ k3b descrito.

Você não está limitado a constantes. Pode haver várias propriedades dos dados gravados que você pode extrair usando um tipo diferente de analisador, como expressões regulares, ou mesmo análises ad hoc que procuram recursos dos dados.

Quanto à leitura ou gravação dos dados, pode ser útil ter um sistema de arquivos na memória que permita executar seus testes sem a possibilidade de interferência de outras partes do sistema. Se você não tiver acesso a um bom sistema de arquivos na memória, use uma árvore de diretórios temporária.

BobDalgleish
fonte
1

Use injeção de dependência e zombaria.

Você não deseja testar seu driver SQL e não deseja testar se o banco de dados SQL está online e configurado corretamente. Isso faria parte de um teste de integração ou sistema. Você deseja testar se o seu código envia as instruções SQL que ele deve enviar e se ele interpreta as respostas da maneira que deveria.

Portanto, quando você tem um método / classe que deve fazer algo com um banco de dados, não obtenha essa conexão com o banco de dados por si só. Altere-o para que o objeto que representa a conexão com o banco de dados seja passado para ele.

No seu código de produção, passe o objeto de banco de dados real.

Nos testes de unidade, passe um objeto simulado que se comporta como um banco de dados real e não entra em contato com um servidor de banco de dados. Basta verificar se ele recebe as instruções SQL que deveria receber e, em seguida, responde com respostas codificadas.

Dessa forma, você pode testar a camada de abstração do banco de dados sem precisar de um banco de dados real.

Philipp
fonte
Advogado do Diabo: Como você sabe quais instruções SQL "devem receber"? E se o driver do banco de dados otimizar a ordem do que aparece no código?
user949300
@ user949300 Um objeto simulado de banco de dados geralmente substitui o driver do banco de dados.
Philipp
quando você está testando um repositório, não faz sentido injetar um cliente de banco de dados simulado. Você precisa testar se o seu código executa sql, que funciona no banco de dados. caso contrário, você terminará uo apenas testando seu mock
Ewan
@ Ewan Não é disso que se trata o teste de unidade. Um teste de unidade testa uma unidade de código, isolada do resto do mundo. Você não está testando as interações entre componentes, como seu código e o banco de dados. É para isso que serve o teste de integração.
Philipp
sim. estou dizendo que não há nenhuma unidade pontual testando um repositório db. teste de integração é a única coisa que vale a pena fazer
Ewan
0

Se você estiver usando um mapeador relacional de objetos, normalmente há uma biblioteca associada que pode ser usada para testar se seus mapeamentos funcionam corretamente criando um agregado, persistindo e recarregando-o de uma nova sessão, seguido pela verificação do estado em relação a o objeto original.

O NHibernate oferece teste de especificação de persistência . Ele pode ser configurado para funcionar com um armazenamento na memória para testes rápidos de unidade.

Se você seguir a versão mais simples dos padrões Repositório e Unidade de Trabalho e testar todos os seus mapeamentos, poderá contar com as coisas praticamente funcionando.

pnschofield
fonte