Na minha máquina funciona, e na sua? Testes, TDD e build automatizado
Nunca mais repita "na minha máquina funciona". O que todo desenvolvedor precisar saber sobre testes automatizados
Um dos ditados mais comuns na área de desenvolvimento de software é "Na minha máquina funciona!". Certamente você, desenvolvedor, já ouviu isso de algum colega ou mesmo proclamou em voz alta. Embora esta frase seja engraçada, ela é de certa forma constrangedora, principalmente se ouvida pelo seu cliente. Pense assim, você é pago para desenvolver um software e ele funciona somente na sua máquina, mas não na do cliente. Será que isso faz algum sentido? Claro que não! Enquanto não funcionar na máquina do seu cliente você não pode dizer que o software funciona!
Na minha máquina funciona! - Programador Anônimo
Embora as ferramentas e tecnologias para desenvolver sistemas estejam mais simples e fáceis de aprender e usar hoje em dia, os problemas que devemos resolver não estão, eles estão bem mais complexos! Como desenvolvedores, passamos maior parte do nosso tempo lendo código e raciocinando sobre os problemas de nossos clientes, problemas de todos os tipos e todas as áreas. Escrevemos software para RH, gestão hospitalar, automação comercial, e-commerce e pagamento eletrônico, entre outros. Se você perceber, criamos programas para resolver os problemas de outras pessoas, mas por que não conseguimos garantir que nosso código, um simples if
, funciona?
public double calculaSalarioDo(Funcionario funcionario) {
if (funcionario.getTipo() == Tipo.DBA) {
return funcionario.getSalario() * 0.85; // aplica 15%
}
return funcionario.getSalario() * 0.9; // aplica 10%
}
Podemos até fazer alguns testes manuais na aplicação para ver se o que acabamos de codificar realmente funciona, mas será que é suficiente? Fazer um teste manual para o if
acima não é das coisas mais difíceis, contudo cedo ou tarde esse código muda e tende a ficar mais complexo, com mais ifs
e muito mais lógicas de negócio. Será que o desenvolvedor continuará fazendo os mesmos testes para cada if
e cada lógica e, vou mais longe, com o mesmo entusiasmo que ele fizera no inicio?
Provavelmente não. Se torna enfadonho ter que testar TUDO novamente a cada modificação de código. As chances são de que o desenvolvedor teste apenas o tal do caminho feliz (happy path). Nenhum desenvolvedor gosta de gastar tempo testando algo que um dia funcionou. Mas será que ainda funciona? Será que aquele novo if
que coloquei não quebrou outra parte do sistema? É difícil garantir sem testar cada ponto da aplicação, ainda mais se tivermos que fazer manualmente.
Testes Automatizados
Ainda assim, será que os testes manuais serão sempre feitos com qualidade? Imagina repetir os mesmos testes todos os dias... É, eu diria que não! Embora o desenvolvedor tenha todo cuidado e carinho com seus testes manuais, a verdade é que tudo que é repetitivo e feito por ser humano é propício a erros.
Não é à toa que há alguns bons anos a industria de software tem utilizado ferramentas para ajudar cada desenvolvedor a escrever programas para testar seu programas, mais conhecidos como testes automatizados.
Basicamente, a ideia de um teste automatizado é escrevemos uma classe parar testar de forma automatizada nossas classes de produção. Por ser automatizado e executado por uma máquina ele rodará MUITO rápido, o que nos permite rodá-lo diversas vezes ao dia. O que estou querendo dizer é que você escreve o teste somente uma vez e pode rodá-lo quantas vezes quiser. Um bateria com milhares de testes pode rodar em menos de 5 segs.
No mundo Java, para escrevermos nossos testes automatizados utilizamos o jUnit. Ele é muito simples e suportado por todas as IDEs do mercado. Por exemplo, para testar o código anterior poderíamos escrever um teste como este:
public class CalculadoraDeSalarioTest {
@Test
public void deveAplicarDescontoDe15PorcentoQuandoDBA() {
Funcionario dba = new Funcionario("Rommel Costa", Tipo.DBA);
dba.setSalario(1000.0);
CalculadoraDeSalario calculadora = new CalculadoraDeSalario();
double salario = calculadora.calculaSalarioDo(dba);
assertEquals("salario de dba", 850.0, salario, 0000.1);
}
}
Resolvemos o nosso if
para o caso do funcionário ser DBA. Mas para o cenário na qual ele não é DBA? De forma similar ao que fizemos, agora escrevemos mais um método de teste para cobrir este cenário:
@Test
public void deveAplicarDescontoDe10PorcentoQuandoDesenvolvedor() {
Funcionario dev = new Funcionario("Handerson", Tipo.DESENVOLVEDOR);
dev.setSalario(3500.0);
CalculadoraDeSalario calculadora = new CalculadoraDeSalario();
double salario = calculadora.calculaSalarioDo(dev);
assertEquals("salario de dev", 3150.0, salario, 0000.1);
}
Por fim, se rodarmos essa classe de testes do jUnit no Eclipse teremos como resultado uma linda e maravilhosa barra verde:
Repare que a classe rodou MUITO mas MUITO rápido. Isso faz com que você queira rodar sua bateria com centenas de testes sempre que puder, assim você sabe se um simples if
ou chamada de método setter quebrou alguma parte do sistema. Se você codifica uma nova funcionalidade você cobre ela com testes; se evolui o código de uma funcionalidade antiga você também deve cobri-la com testes; se você refatora código ruim você só precisa rodar a bateria para ter certeza que não quebrou nada. Quanto mais código coberto com testes mais conseguimos diminuir a incidência de bugs na aplicação.
No final, quanto mais testes você escreve mais segurança você tem de mexer em qualquer parte do sistema.
Falando em segurança, você pode dar um passo além e refatorar aquele código difícil de ler e manter que todo mundo tem MEDO de mexer. Os testes te ajudam exatamente aí! Isso é uma das melhores sensações que um desenvolvedor tem: coragem de refatorar código "alheio" e ter certeza que não quebrou nada.
Mas não pára por aí...
E se eu te dissesse que você pode escrever os testes antes do código de produção?
TDD - Test Driven Development
Com TDD você escreve o teste antes do código de produção, antes de criar sua classe; antes mesmo de adicionar aquele novo if
no código. Parece surreal, mas não é! Com TDD começamos pensando sobre o problema e os cenários que queremos resolver. Isso nos leva refletir a todo instante sobre o projeto (design) das classes e métodos que implementamos. Logo temos maiores chances de acabar com classes com menor acoplamento, maior coesão e com responsabilidades bem definidas.
Difícil acreditar não é? Vamos praticar TDD no código anterior...
Digamos que agora precisamos calcular o salário de um Gerente e que devemos aplicar o desconto de 5%. Utilizando TDD começamos implementando o teste antes de adicionar o if
dentro da classe CalculadoraDeSalario
:
@Test
public void deveAplicarDescontoDe5PorcentoQuandoGerente() {
Funcionario gerente = new Funcionario("Josy", Tipo.GERENTE);
gerente.setSalario(4000.0);
CalculadoraDeSalario calculadora = new CalculadoraDeSalario();
double salario = calculadora.calculaSalarioDo(gerente);
assertEquals("salario de gerente", 3800.0, salario, 0000.1);
}
Se rodarmos esse teste no Eclipse teremos como resultado:
O teste quebrou? Claro, nós não escrevemos a regra ainda! Agora, com o teste implementado e quebrando podemos alterar nossa lógica de negócio para tratar este cenário:
public double calculaSalarioDo(Funcionario funcionario) {
if (funcionario.getTipo() == Tipo.GERENTE) {
return funcionario.getSalario() * 0.95; // aplica 5%
}
// restante do código
}
Pronto! Basta rodar novamente os testes e ver o resultado (barra verde) da nossa alteração de código:
Para cada novo cenário você pode escrever um novo teste, vê-lo quebrando e, por fim, vê-lo passando após implementar ou refatorar o código de produção.
Sem dúvida não é algo fácil começar pelos testes, não estamos acostumado a isso! Requer um pouco de prática, mas não é algo de outro mundo.
Quando você menos perceber você consegue vislumbrar a maioria se não todos os cenários e casos importantes para testar sem ter escrito uma única linha de código de produção. Isso se torna ainda mais divertido na hora de encontrar bugs no sistema: escreve o teste para encontrar o bug, corrige o código e roda a bateria de testes.
Build Automatizado
Outro detalhe importante no processo de desenvolvimento de software é gerar o artefato final para seu cliente, seja ele JAR, WAR ou executável. É muito comum essa responsabilidade ficar na mão de um único programador, normalmente o líder técnico. No caso de uma aplicação Web, este mesmo líder técnico é o responsável por gerar o .war
da própria IDE instalada na sua máquina na empresa. O problema dessa abordagem é que todos as pré-configurações e passos para gerar o WAR estão amarradas a máquina dele (se não na cabeça dele), desde uma variável de ambiente até um diretório que fica na Área de Trabalho.
Quem já exportou o .war
de um projeto web do Eclipse sabe exatamente do que estou falando. Se você gera o WAR da sua IDE você está fazendo errado! O pior é quando você exporta de outra máquina, daí o medo te consome:
Deixar este processo manual e ainda mais na mão de um único desenvolvedor na equipe nunca acaba bem. Se este profissional entra de férias, sofre um acidente ou falta trabalho no dia da implantanção da aplicação no cliente? Você pode acessar a máquina dele e gerar o WAR, certo? Opa! Ele usa notebook pessoal no dia a dia...
Ainda assim, as chances são de que outro desenvolvedor da equipe consiga gerar o WAR final seguindo algum script (txt) escrito 2 anos atrás pelo líder técnico. Mas será que o artefato final está correto? Será que não havia um passo manual na qual só o líder técnico sabia? Muito provável!
Para evitar este tipo de problema a boa prática recomenda que o projeto tenha um Build Automatizado. Dessa forma qualquer desenvolvedor pode rodar o build da própria máquina e ter o WAR final sem qualquer surpresa. Em vez de ter um script passo-a-passo em um .txt
nós usamos um script executável.
Para não ter que usar um .sh
no Linux ou .bat
no Windows, no mundo Java usamos ferramentas como Maven e Ant. Com elas executamos de forma automatizado o seguinte processo:
- Compila as classes;
- Roda bateria de testes;
- Gerar .WAR;
Estes são os passos básicos, mas podemos ter outros passos dependendo do projeto ou mesmo do cliente. A idéia é não depender de interação humana, máquina ou membro específico da equipe para gerar um simples WAR. Enfim, qualquer um da equipe deve ser capaz de gerar o artefato final.
Fuja do manual. Automatize o que for possível
Práticas como TDD, testes e build automatizado deveriam ser o cotidiano de qualquer desenvolvedor e equipe que almeja entregar código e software de qualidade. Existem muitas outras técnicas, mas estas já trazem um benefício imenso ao projeto e a produtividade geral de toda a equipe.
Prestamos consultoria por muitos anos em diversas empresas e aprendemos que automatizar os processos de desenvolvimento é fundamental para se ter um desenvolvimento sustentável com qualidade mínima para o cliente. Implantar estas práticas nos clientes e treinar a equipe sempre foram as principais tarefas que realizamos e nos orgulhamos muito disso. Não existe sensação melhor do que ver toda a equipe escrevendo testes e, o melhor, acreditando em todos seu benefícios. Como dito pelo Daniel Baccin uma vez:
Não consigo mais programar sem testes. Não dá para confiar no código! - Daniel Baccin
Mas agora, é hora de darmos um passo mais audacioso e compartilhar toda essa experiência e conhecimento com outros desenvolvedores, profissionais e empresas. Não mais como consultoria, mas na forma de um cursos e treinamentos com o padrão de qualidade da TriadWorks que você já conhece.
É com prazer que anuncio o novo novo curso de TDD e Testes Automatizados com Java da TriadWorks. Este curso passa por tudo que vimos nesse post e muito mais. Através de um conteúdo enxuto, didático e direto ensinaremos vários profissionais e empresas a terem a chance de entregar software de qualidade a seus clientes.
A primeira turma inicia no dia 11 de Abril em Fortaleza/CE com descontos especiais de lançamento. Não perca a chance e aprenda como você pode se tornar um profissional melhor com o uso de testes automatizados!
E você, escreve testes e build automatizado? Tem interesse de aprender?
You might also be interested in these articles...
Desenvolvedor e instrutor na TriadWorks