Vamos começar com um exemplo.
Digamos, eu tenho um método chamado export
que depende muito do esquema do banco de dados. E por "depende muito", quero dizer, sei que adicionar uma nova coluna a uma determinada tabela frequentemente (com muita frequência) leva à export
alteração do método correspondente (geralmente você também deve adicionar o novo campo aos dados de exportação).
Os programadores costumam esquecer de mudar de export
método, pois não está claro se você deve olhar para isso. Meu objetivo é forçar o programador a tomar uma decisão explícita para determinar se ele esqueceu de observar o export
método ou se não deseja adicionar um campo aos dados de exportação. E estou procurando a solução de design para esse problema.
Eu tenho duas idéias, mas as duas têm falhas.
Wrapper inteligente "Ler tudo"
Posso criar o wrapper inteligente que garante que todos os dados sejam lidos explicitamente.
Algo assim:
def export():
checker = AllReadChecker.new(table_row)
name = checker.get('name')
surname = checker.get('surname')
checker.ignore('age') # explicitly ignore the "age" field
result = [name, surname] # or whatever
checker.check_now() # check all is read
return result
Portanto, checker
afirma se table_row
contém outros campos que não foram lidos. Mas tudo isso parece meio pesado e (talvez) afeta o desempenho.
"Verifique esse método" mais unittest
Posso apenas criar o mais unittest que se lembra do último esquema da tabela e falha sempre que a tabela é alterada. Nesse caso, o programador verá algo como "não se esqueça de verificar o export
método". Para ocultar o programador de aviso (ou não - isso é um problema), faça o check-out export
e manualmente (esse é outro problema) corrija o teste adicionando novos campos a ele.
Tenho algumas outras idéias, mas elas são muito problemáticas para implementar ou difíceis de entender (e não quero que o projeto se torne um quebra-cabeça).
O problema acima é apenas um exemplo da classe mais ampla de problemas que encontro de tempos em tempos. Quero vincular alguns trechos de código e / ou infraestrutura, portanto, alterar um deles alerta imediatamente o programador para verificar outro. Normalmente, você tem algumas ferramentas simples, como extrair lógica comum ou escrever de maneira confiável, mas estou procurando a ferramenta para casos mais complexos: talvez alguns padrões de design dos quais eu conheço agora.
fonte
export
base no esquema?export
tem tudo o que você precisa realisticamente?Respostas:
Você está no caminho certo com sua ideia de teste de unidade, mas sua implementação está errada.
Se
export
estiver relacionado ao esquema e o esquema for alterado, há dois casos possíveis:Ou o
export
ainda funciona perfeitamente bem, porque não foi afetado por uma ligeira alteração no esquema,Ou quebra.
Nos dois casos, o objetivo da compilação é rastrear essa possível regressão. Vários testes - sejam testes de integração ou de sistema, funcionais ou qualquer outra coisa - garantem que seu
export
procedimento funcione com o esquema atual , independentemente do fato de ter sido alterado desde o commit anterior. Se esses testes passarem, ótimo. Se eles falharem, isso é um sinal para o desenvolvedor de que ele pode ter perdido alguma coisa e uma indicação clara de onde procurar.Por que sua implementação está errada? Bem, por várias razões.
Não tem nada a ver com testes de unidade ...
... e, na verdade, nem é um teste.
A pior parte é que a correção do "teste" requer, na verdade, alterar o "teste", que está executando uma operação que não tem nenhuma relação com o
export
.Em vez disso, fazendo testes reais para o
export
procedimento, verifique se o desenvolvedor corrigirá o erroexport
.De maneira mais geral, quando você encontra uma situação em que uma mudança em uma classe sempre ou geralmente exige uma mudança em uma classe completamente diferente, este é um bom sinal de que você fez seu projeto errado e está violando o Princípio de Responsabilidade Única.
Enquanto falo especificamente sobre aulas, isso se aplica mais ou menos a outras entidades também. Por exemplo, uma alteração no esquema do banco de dados deve ser refletida automaticamente no seu código, por exemplo, através de geradores de código usados por muitos ORMs ou pelo menos ser facilmente localizada: se eu adicionar
Description
coluna àProduct
tabela e não usar ORMs ou geradores de código, Eu pelo menos espero fazer uma única alteração dentro daData.Product
classe do DAL, sem a necessidade de pesquisar em toda a base de código e encontrar algumas ocorrências deProduct
classe na, por exemplo, camada de apresentação.Se você não puder restringir razoavelmente a alteração a um local (porque você está em um caso em que simplesmente não funciona ou porque exige uma enorme quantidade de desenvolvimento), cria um risco de regressões . Quando eu mudo de classe
A
, e a classeB
em algum lugar da base de código para de funcionar, é uma regressão.O teste reduz o risco de regressões e, o que é muito mais importante, mostra a localização de uma regressão. É por isso que, quando você sabe que alterações em um local causam problemas em uma parte completamente diferente da base de código, verifique se você tem testes suficientes que acionam alarmes assim que uma regressão aparece nesse nível.
Em todos os casos, evite confiar nesses casos apenas nos comentários. Algo como:
nunca funciona. Não apenas os desenvolvedores não o leem na maioria dos casos, mas muitas vezes terminam removidos ou se afastam da linha em questão e se tornam impossíveis de entender.
fonte
If you change the following line...
que funciona automaticamente e não pode ser simplesmente ignorado. Eu nunca vi alguém realmente usar essas armadilhas, daí a dúvida.B
não para de funcionar, talvez comece a funcionar incorretamente. Mas não posso prever o futuro e não posso escrever testes que prevejam o futuro, então tento lembrar o desenvolvedor para escrever esse novo teste.Parece-me que suas alterações estão subespecificadas. Digamos que você mora em algum lugar que não tenha códigos postais e, portanto, não tenha uma coluna de código postal na tabela de endereços. Em seguida, os códigos postais são introduzidos ou você começa a lidar com clientes que moram onde há códigos postais e é necessário adicionar essa coluna à tabela.
Se o item de trabalho disser apenas "adicionar coluna de código postal à tabela de endereços", sim, a exportação será interrompida ou, pelo menos, não exportará códigos postais. Mas e a tela de entrada usada para inserir códigos postais? O relatório que lista todos os clientes e seus endereços? Há muitas coisas que precisam mudar quando você adiciona esta coluna. O trabalho de lembrar essas coisas é importante - você não deve contar com artefatos de código aleatórios para "prender" os desenvolvedores a se lembrarem.
Quando é tomada a decisão de adicionar uma coluna significativa (ou seja, não apenas uma pesquisa total ou desnormalizada em cache ou outro valor que não pertença a uma exportação ou relatório ou em uma tela de entrada), os itens de trabalho criados devem incluir TODAS as alterações necessário - adicionando a coluna, atualizando o script de preenchimento, atualizando os testes, atualizando a exportação, os relatórios, as telas de entrada e assim por diante. Nem todos podem ser atribuídos à (ou escolhidos pela) mesma pessoa, mas todos devem ser concluídos.
Às vezes, os desenvolvedores optam por adicionar as próprias colunas como parte da implementação de algumas mudanças maiores. Por exemplo, alguém pode ter escrito um item de trabalho para adicionar algo a uma tela de entrada e a um relatório, sem pensar em como ele é implementado. Se isso acontecer muito, você precisará decidir se o adicionador de item de trabalho precisa conhecer os detalhes da implementação (para poder adicionar todos os itens de trabalho corretos) ou se os desenvolvedores precisam estar cientes de que o item de trabalho- adicionador às vezes deixa as coisas de fora. Se for o último, você precisará de uma cultura de "não apenas mude o esquema; pare e pense no que mais isso afeta".
Se houvesse muitos desenvolvedores e isso acontecesse mais de uma vez, eu configuraria um alerta de check-in para que o líder da equipe ou outra pessoa sênior fosse alertada sobre alterações de esquema. Essa pessoa poderia procurar itens de trabalho relacionados para lidar com as consequências da mudança de esquema e, se os itens estivessem ausentes, não só poderia adicioná-los, mas também educar quem os deixasse de fora do plano.
fonte
Quase sempre, ao criar uma exportação, também crio uma importação correspondente. Como tenho outros testes que preenchem totalmente a estrutura de dados que está sendo exportada, posso criar um teste de unidade de ida e volta que compara um original totalmente preenchido com uma cópia exportada e importada. Se forem iguais, a exportação / importação estará concluída; se não forem iguais, o teste de unidade falhará e eu sei que o mecanismo de exportação precisa ser atualizado.
fonte