Teste de unidade no Django

12

Estou realmente lutando para escrever testes de unidade eficazes para um grande projeto do Django. Tenho uma cobertura de teste razoavelmente boa, mas percebi que os testes que escrevi são definitivamente testes de integração / aceitação, e não de unidade, e tenho partes críticas do meu aplicativo que não estão sendo testadas efetivamente. Eu quero corrigir isso o mais rápido possível.

Aqui está o meu problema. Meu esquema é profundamente relacional e muito orientado para o tempo, dando ao meu objeto de modelo alto acoplamento interno e muito estado. Muitos dos meus métodos de modelo consultam com base em intervalos de tempo, e eu tenho muita coisa auto_now_addacontecendo nos campos com registro de data e hora. Portanto, use um método parecido com este, por exemplo:

def summary(self, startTime=None, endTime=None):
    # ... logic to assign a proper start and end time 
    # if none was provided, probably using datetime.now()

    objects = self.related_model_set.manager_method.filter(...)

    return sum(object.key_method(startTime, endTime) for object in objects)

Como alguém se aproxima de testar algo assim?

Aqui é onde estou tão longe. Ocorre-me que o objetivo do teste de unidade deve receber um comportamento zombado by key_methodde seus argumentos, está summaryfiltrando / agregando corretamente para produzir um resultado correto?

Zombar de datetime.now () é bastante direto, mas como posso zombar do resto do comportamento?

  • Eu poderia usar acessórios, mas ouvi prós e contras do uso de acessórios para criar meus dados (a má manutenção é um golpe que chega em casa para mim).
  • Eu também poderia configurar meus dados através do ORM, mas isso pode ser limitador, porque também tenho que criar objetos relacionados. E o ORM não permite que você mexa com os auto_now_addcampos manualmente.
  • Zombar do ORM é outra opção, mas não é apenas complicado zombar de métodos ORM profundamente aninhados, mas a lógica no código ORM é zombada do teste, e a zombaria parece tornar o teste realmente dependente dos internos e dependências do função em teste.

As porcas mais difíceis de quebrar parecem ser as funções como esta, que ficam em algumas camadas de modelos e funções de nível inferior e são muito dependentes do tempo, mesmo que essas funções possam não ser super complicadas. Meu problema geral é que, por mais que pareça cortá-lo, meus testes parecem muito mais complexos do que as funções que estão testando.

acjay
fonte
Você deve escrever testes de unidade primeiro a partir de agora um que o ajude a detectar problemas de testabilidade em seu design antes que o código de produção real seja gravado.
Chedy2149
2
Isso é útil, mas realmente não aborda a questão de como testar melhor os aplicativos com inerência de estado e pesados ​​com ORM.
acjay
Você tem que longe abstrair a camada de persistência
Chedy2149
1
Parece ótimo, hipoteticamente, mas quando se trata de manutenção de projetos, acho que há um custo não trivial para inserir uma camada de persistência personalizada entre a lógica de negócios e o Django ORM extremamente bem documentado. De repente, as classes ficam repletas de minúsculos métodos intermediários que precisam ser refatorados ao longo do tempo. Mas talvez isso seja justificado em lugares onde a testabilidade é crítica.
acjay

Respostas:

6

Vou seguir em frente e registrar uma resposta para o que eu criei até agora.

Minha hipótese é que, para uma função com profundo acoplamento e estado, a realidade é que ela simplesmente precisará de muitas linhas para controlar seu contexto externo.

Aqui está a aparência do meu caso de teste, contando com a biblioteca Mock padrão:

  1. Eu uso o ORM padrão para configurar a sequência de eventos
  2. Crio meu próprio começo datetimee subverto os auto_now_addhorários para ajustar uma linha do tempo fixa do meu design. Eu pensei que o ORM não permitia isso, mas funciona bem.
  3. Certifico-me de que a função em teste seja usada from datetime import datetimepara que eu possa corrigir datetime.now()apenas essa função (se eu zombar de toda a datetimeclasse, o ORM é adequado).
  4. Crio meu próprio substituto para object.key_method(), com funcionalidade simples, mas bem definida, que depende dos argumentos. Quero que dependa dos argumentos, porque, caso contrário, talvez eu não saiba se a lógica da função em teste está funcionando. No meu caso, ele simplesmente retorna o número de segundos entre startTimee endTime. Eu object.key_method()o new_callablecolo envolto em um lambda e o colo diretamente no uso do kwarg de patch.
  5. Por fim, eu executo uma série de afirmações em várias chamadas summarycom argumentos diferentes para verificar a igualdade com os resultados calculados à mão esperados, respondendo pelo comportamento determinado da simulaçãokey_method

Escusado será dizer que isso é significativamente mais longo e mais complicado do que a própria função. Depende do banco de dados e não parece realmente um teste de unidade. Mas também é bastante dissociado dos elementos internos da função - apenas sua assinatura e dependências. Então eu acho que ainda pode ser um teste de unidade.

No meu aplicativo, a função é bastante essencial e sujeita a refatoração para otimizar seu desempenho. Então, acho que o problema vale a pena, apesar da complexidade. Mas ainda estou aberto a melhores idéias sobre como abordar isso. Tudo parte da minha longa jornada em direção a um estilo de desenvolvimento mais orientado a testes ...

acjay
fonte