Em vez de se juntar
O Unittest é provavelmente a estrutura de escrita de teste mais famosa do Python. É muito fácil aprender e começar a usar no seu projeto. Mas nada é perfeito. Neste post, quero falar sobre um recurso que eu pessoalmente (acho que não um) carece de unittest.
Um pouco sobre testes de unidade
Antes de discutir (e condenar) a estrutura de teste, considero necessário falar um pouco sobre o teste em geral. Quando ouvi pela primeira vez a frase “teste de unidade”, pensei que era da responsabilidade de algum serviço de controle de qualidade que verifique os módulos de software quanto à conformidade com os requisitos. Imagine minha surpresa quando descobri que esses mesmos testes devem ser escritos por programadores. No começo eu não escrevi testes ... de jeito nenhum. Nada pode substituir essas sensações quando você acorda de manhã e lê com o usuário “O programa não funciona. Algo precisa ser feito com urgência. A princípio, pareceu-me que esse era um processo completamente normal, até que escrevi o primeiro teste de unidade, que encontrou um erro. Os testes de unidade geralmente parecem inúteis exatamente até detectar problemas. Agora não posso comprometer uma nova função sem escrever um teste nela.
Mas os testes não são usados apenas para verificar se o código está escrito corretamente. Aqui está uma lista de funções que, na minha opinião, realizam testes:
- Detecção de erros no código do programa
- Dando aos programadores a confiança de que seu código funciona
- Corolário do parágrafo anterior: a capacidade de modificar com segurança o programa
- Testes - um tipo de documentação que descreve mais corretamente o comportamento do sistema
Em certo sentido, os testes repetem a estrutura do programa. Os princípios de criação de programas são aplicáveis aos testes? Acredito que sim - este é o mesmo programa, embora teste outro.
Descrição do problema
Em algum momento, tive a ideia de escrever um teste abstrato. O que quero dizer com isso? Este é um teste que não se executa, mas declara métodos que dependem de parâmetros definidos nos herdeiros. E então eu descobri que não posso fazer isso humanamente da maneira mais unida. Aqui está um exemplo:
class SerializerChecker(TestCase): model = None serializer = None def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props)
Eu acho que, mesmo sem conhecer a implementação do TestObjectFactory e o método check_dict, fica claro que props é um dicionário de propriedades de um objeto, obj é um objeto para o qual estamos verificando o serializador. check_dict verifica recursivamente os dicionários em busca de uma correspondência. Acho que muitas pessoas familiarizadas com o unittest dirão imediatamente que esse teste não atende à minha definição de abstrato. Porque Como o método test_fields_creation será executado a partir desta classe, da qual absolutamente não precisamos. Após alguma pesquisa de informações, cheguei à conclusão de que a opção mais adequada não é herdar o SerializerChecker do TestCase, mas implementar os herdeiros de alguma forma:
class VehicleSerializerTest(SerializerChecker, RecursiveTestCase): model = Vehicle serializer = VehicleSerialize
RecursiveTestCase é um descendente de TestCase que implementa o método check_dict.
Esta solução é feia ao mesmo tempo em várias posições:
- Na classe SerializerChecker, precisamos saber que o filho deve herdar do TestCase. Essa dependência pode causar problemas para quem não conhece este código.
- O ambiente de desenvolvimento teimosamente acredita que estou errado, pois o SerializerChecker não possui um método check_dict
Erro que o ambiente de desenvolvimento lançaPode parecer que você pode simplesmente adicionar um esboço para check_dict e todos os problemas foram resolvidos:
class SerializerChecker: model = None serializer = None def check_dict(self, data, props): raise NotImplementedError def test_fields_creation(self): props = TestObjectFactory.get_properties_for_model(self.model) obj = TestObjectFactory.create_test_object_for_model(self.model) serialized = self.serializer(obj) self.check_dict(serialized.data, props)
Mas esta não é uma solução completa para o problema:
- De fato, criamos uma interface que implementa não um descendente dessa classe, mas o RecursiveTestCase, que cria perguntas razoáveis para a arquitetura.
- O TestCase possui muitos métodos de declaração *. Nós realmente precisamos escrever um esboço para cada um usado? Ainda parece ser uma boa solução?
Resumir
O Unittest não fornece a capacidade sensata de "desconectar" uma classe herdada do TestCase. Eu ficaria muito feliz se essa função fosse adicionada ao framework. Como você resolve esse problema?