Trabalhei em um grande sistema de transações financeiras para um banco que cuidava de pensões e investimentos. Após 15 anos de alterações nos recursos, o custo do teste de regressão manual subiu para US $ 200 mil por versão. (10M LOC, US $ 10M negociados por dia). Esse sistema também faz interface com outros 19 sistemas da empresa, movendo muitos dados. Este sistema foi implementado em Java.
O que observamos, no entanto, é que, quanto mais "reutilização" fazemos, mais custos de teste de regressão aumentam. (O motivo é que você precisa "testar o código em que toca" - e o código reutilizado / compartilhado afeta uma multiplicidade de lugares quando é tocado. Portanto, apesar de 'DRY - Não se repita' - por exemplo, não copie e cole o código - observamos um incentivo financeiro para copiar e colar código, reduzindo os custos do teste de regressão, porque não queremos modificar o código que pode ser compartilhado, porque isso causará um grande impacto no teste de regressão.
Minha pergunta é : existe um princípio de engenharia de software que descreva a relação entre reutilização e custos de teste de regressão?
A razão pela qual eu faria essa pergunta é que, sem dúvida, há um custo benefício em decompor o sistema em partes menores a serem testadas.
Premissas:
'Teste de regressão' significa 'teste de aceitação' - ou seja, outro grupo que gasta tempo para escrever novos e reutilizar testes antigos no sistema em nome da empresa, incluindo configurações de dados e ambiente.
Eu sei que a reação instintiva a um grande custo de teste de regressão é 'testes mais automatizados'. Este é um bom princípio. Nesse ambiente, existem alguns desafios.
(a) Testes automatizados são menos úteis além dos limites do sistema, a menos que esse sistema também tenha uma alta cobertura de testes automatizados. (Desafio esfera de influência).
(b) É culturalmente difícil obter impulso no tempo do programador ou no investimento de capital em alta cobertura automatizada de teste quando seu sistema já é grande e complexo.
(c) O custo de manutenção de testes automatizados está oculto em um projeto e, portanto, eles são facilmente descartados no nível do projeto.
(d) Essa é apenas a realidade cultural de trabalhar em um banco.
(e) estou trabalhando para resolver este problema de uma maneira diferente (decomposição).
fonte
Respostas:
Acima parece certo para mim. Quanto mais importante é o código, quanto mais ele é compartilhado, maiores são os requisitos de qualidade, mais garantia de qualidade deve estar envolvida quando ele é alterado.
Como seu sistema é implementado em Java, você pode ver um exemplo acima, nas bibliotecas padrão Java (JDK). Seus principais lançamentos não são frequentes e são acompanhados de muito esforço consumindo testes. E até versões menores são executadas por um conjunto de testes JCK muito abrangente para verificar a ausência de regressões.
Você pode pensar que isso de alguma forma impede a evolução do código compartilhado, e ... sim, isso é certo. Quanto mais impacto e risco estiverem associados à alteração do código, mais cuidadoso você deve ter ao fazê-lo, mais esforço precisará ser envolvido no teste de seus lançamentos.
Idealmente, a qualidade dos lançamentos de código amplamente compartilhado deve ser tal que não exija grandes alterações (exceto as melhorias pouco frequentes). Essa linha de pensamento é refletida em uma famosa citação de Joshua Bloch :
Porém, com o exposto acima, parece que alguns dos problemas que você descreve são causados por uma estratégia ineficiente de desenvolvimento de código compartilhado. Em particular, parece especialmente problemático que, para o código reutilizado, apenas duas opções sejam consideradas: duplicar esse código ou incluí-lo imediatamente nas bibliotecas compartilhadas "principais".
Limitar apenas essas duas opções é desnecessário e, novamente, você pode encontrar exemplos de como isso pode ser melhorado no próprio JDK que você usa. Dê uma olhada nos
java.util.concurrent
pacotes ( JSR 166 ) - até o lançamento do Java 5, eles eram uma biblioteca separada, não parte dos lançamentos principais do JDK.Pense nisso, essa é uma terceira opção que você ignorou, e bastante pragmática, a que você precisa considerar no "início" do novo código compartilhado. Quando você acaba de descobrir algum código que pode ser compartilhado entre 2 ou 3 componentes, nada obriga a incluí-lo imediatamente na API principal do sistema.
Você pode empacotar e liberar esse código compartilhado "imaturo" como uma biblioteca separada, assim como foi feito nos utilitários simultâneos Java. Dessa forma, você dispensa a necessidade de testes de regressão completos, porque você pode usar apenas uma quantidade relativamente pequena de componentes envolvidos. Como resultado, você tem mais liberdade para modificar e aprimorar esse código compartilhado e testar como ele funciona na produção.
Depois que sua biblioteca amadurecer e estabilizar o suficiente para dar a você a confiança de que mudanças adicionais são improváveis, considere sua inclusão nas bibliotecas principais do sistema, assim como os utilitários simultâneos foram eventualmente incluídos no JDK.
Um exemplo concreto de quanto esforço (incluindo testes) pode estar envolvido na alteração de código fortemente reutilizado pode ser encontrado, novamente, no JDK. No release 7u6, eles mudaram a
String
representação interna que envolvia uma mudança nosubstring
desempenho. Os comentários de um desenvolvedor de recursos no Reddit descrevem quanto esforço foi envolvido nessa mudança:fonte
Acho que não existem métricas para calcular o "custo dos testes de regressão / LOC do código reutilizado criado". E acho que ninguém jamais investiu tanto tempo e dinheiro para construir o mesmo sistema "grande" duas vezes, uma versão com muitos componentes resuable e uma sem, para fazer uma pesquisa séria sobre isso.
Mas já vi problemas causados pela reutilização como a sua e talvez você esteja interessado em algumas idéias sobre como lidar com isso melhor.
Primeiro, não é realmente a reutilização que é o seu problema - é a tentativa de criar seus próprios componentes reutilizáveis e usá-los em todo o sistema. Tenho certeza de que você está reutilizando muitos pacotes de software grandes onde seus problemas não surgem: pense em toda a pilha Java que você está usando ou talvez em alguns componentes de terceiros (presumindo que você esteja satisfeito com esses componentes). Mas o que há de diferente com esse software, por exemplo, as bibliotecas Java, enquanto seus próprios componentes reutilizáveis estão causando muitos custos adicionais de teste de regressão? Aqui estão alguns pontos que acho que poderiam ser diferentes:
esses componentes são muito maduros e estáveis
eles são desenvolvidos e totalmente testados isoladamente por uma organização completamente diferente
para (re) usá-los, você não precisa alterá-los (na verdade, você não poderia nem se quisesse, pois não mantém o código-fonte)
você não recebe diariamente uma nova versão, apenas pequenas atualizações (no máximo por mês) ou grandes atualizações em intervalos por ano
a maioria das atualizações é projetada para ser 100% compatível com versões anteriores, especialmente pequenas atualizações
Portanto, para tornar seus próprios componentes reutilizáveis mais bem-sucedidos, você deve adaptar algumas dessas coisas de cima para seu próprio desenvolvimento:
para qualquer componente reutilizável, tenha uma responsabilidade clara de quem faz a manutenção e verifique se todas as pessoas que reutilizam um componente podem obter uma correção de bug imediatamente se surgirem problemas.
estabelecer políticas rigorosas de controle de versão e versão. Ao desenvolver um componente reutilizável, não o libere "para todos" todos os dias (pelo menos, não se isso implicasse em executar um teste de regressão completo de US $ 200.000 no sistema). Em vez disso, deixe que novas versões sejam publicadas periodicamente e forneça mecanismos para permitir que o usuário desse componente adie a alteração para a nova versão.
quanto mais um componente é reutilizado, mais importante é que ele forneça uma interface estável e um comportamento compatível com versões anteriores.
componentes reutilizáveis precisam de conjuntos de testes muito completos para testá-los isoladamente.
Muitas dessas coisas significam que o custo de construção do componente em si aumentará, mas também diminuirá os custos de alterações causadas por falhas nas regressões.
fonte
Embora possa haver um aumento "observável" no custo devido à necessidade de mais testes, esse tipo de refatoração geralmente torna o código mais sustentável no futuro, à medida que você diminui a dívida técnica no sistema.
Esperamos que isso reduza erros futuros e facilite a implementação de novos recursos ou alterações nos recursos existentes.
Por mais fácil, quero dizer que eles devem levar menos tempo e, portanto, custam menos.
Reduzir, mais fácil e menos são termos bastante nebulosos aqui e qualquer economia futura (ou melhor, esperada por economia) é impossível de calcular, pois ainda não aconteceu.
Uma base de código mais simples deve permitir que novos funcionários ou funcionários existentes no projeto acelerem mais rapidamente, especialmente em sistemas grandes.
Isso também pode reduzir a rotatividade de pessoal, pois o moral dos membros do projeto pode ser melhorado.
Obviamente, não é garantido que você obterá esses benefícios, mas essas são coisas que devem ser consideradas juntamente com os custos (como o aumento dos testes) que podem ser medidos.
De fato, um código melhor acabará por reduzir os custos de teste ao longo do tempo, mesmo se houver um aumento inicial devido ao que você descreve.
fonte