Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Categoria de Estratégia
Postado em 14 março 2024
Atualizado em 14 março 2024
Palavras-chave: tdd,testes,programacao,java,desenvolvimento,tecnologia
Visualizações: 1214
O funcionamento de um software pode ser afetado negativamente após o lançamento de uma nova versão. Por mais que um software tenha uma estrutura de código eficiente, ele continua sendo suscetível a bugs gerados em novas versões. Grande parte dos bugs podem ser descobertos com testes e reparados antes do lançamento da nova versão, diminuindo ou até anulando a existência de bugs. Entretanto, a alteração de código existente pode trazer grandes problemas, pois isso afeta funcionalidades que já estão em produção. Aplicando o princípio aberto/fechado pode acabar resolvendo esse problema, porém nem sempre isso é possível.
Queremos um método que nos de certeza de que todas as funcionalidades do software continuem funcionando exatamente como estavam antes do lançamento de uma nova versão. Ou seja, um método que permita alterações de grande escala no código sem interferir no que sempre funcionou. Esse método existe e se chama TDD.
O TDD é um método de desenvolvimento baseado em testes. Os testes são escritos em código e cobrem todos os cenários possíveis de uma funcionalidade. Toda funcionalidade deve ter testes e todo teste deve ser independente de outros testes.
Dado os valores das variáveis necessárias para a inicialização de testes, o resultado esperado já é definido pelo criador do teste que sabe exatamente qual valor deve ser retornado no final. A regra é simples, se o resultado retornado não é o esperado, o teste falha, caso contrário, o teste passa.
O grande diferencial do TDD para os outros métodos de teste é a ordem em que os testes são elaborados. No TDD o teste é escrito antes mesmo da implementação da própria funcionalidade. Ou seja, um teste é criado com classes e métodos que ainda não existem. Isso quer dizer que a primeira vez que o teste é executado, ele deve falhar. Esse é o modo como o TDD é aplicado. A aplicação do TDD tem 3 etapas a serem seguidas:
As etapas devem sempre seguir o mesmo sentido. O número de iterações não é importante desde que o objetivo seja alcançado: código testável e eficiente.
A grande dúvida é como os testes podem ser escritos sendo que a implementação do código ainda nem foi realizada. Isso é um dúvida bastante comum na primeira vez que alguém conhece o TDD, pois na maioria das vezes os testes são escritos depois da implementação. Isso acaba fazendo com que criemos testes voltados para a implementação. No TDD criamos implementação voltada para os testes. É ao contrário.
Para que o TDD seja aplicado com eficiência, é necessário o desenvolvedor saber com antecedência qual comportamentos ele espera e quais cenários ele deve testar. Por exemplo, em um programa de estacionamento, onde deve-se verificar um carro prestes a estacionar, sabemos que temos que testar os seguintes cenários:
Já sabemos quais são os cenários, por isso podemos criar três testes para essa funcionalidade. Após criar os testes, executamos e vemos as 3 falhas acontecerem, pois não existe implementação ainda. Queremos ter certeza que testamos todos os cenários possíveis e a implementação lide com todos eles.
Os testes devem cobrir todos os comportamentos esperados de cada caso de uso. Um caso de uso pode ser uma função pública que comporta algumas regras de negócio. Por exemplo, se temos uma calculadora, ela terá os seguintes casos de uso:
Cada item acima pode ser considerado um caso de uso no caso de uma calculadora. Cada caso de uso deve ter vários testes testando todos os cenários possíveis, sejam cenários positivos ou pessimistas. Por exemplo, o caso de uso “somar” deve ter os seguintes cenários:
Os 4 cenários acima não passam de alguns exemplos. Na prática poderiam ter mais cenários. O objetivo é fazer com que a implementação seja alinhada com os testes para nos certificarmos de que implementamos de um modo que atenda cada cenário.
Existem casos em que queremos testar os comportamentos de uma funcionalidade que depende de outro software externo (ex: microserviço). Por ser um software externo e não termos controle sobre o seu comportamento, devemos simular o comportamento desse software externo para que possamos testar como vai ser a reação do nosso software local. O Mockito é um bom exemplo de biblioteca que simula comportamentos de classes e métodos.
A grande vantagem em aplicar o TDD é a garantia de que o software não irá falhar por um mesmo erro duas vezes. Ao encontrar um bug no software, criamos um teste para verificar o comportamento esperado de uma funcionalidade sem o bug. Após criar o teste, arruma-se o bug e o teste deve passar. O teste que cobre esse bug continuará existindo e será executado toda vez que os testes forem rodados. Isso nos dá a garantia de que nunca mais nos deparamos com esse tipo de bug. Caso o mesmo bug venha a acontecer, esse teste irá falhar.
Além disso, se os testes estiverem cobrindo todos os cenários possíveis com eficiência, dificilmente o software irá quebrar. Conforme novas funcionalidades vão sendo implementadas, mais testes serão adicionados para cobrir cada cenário possível. A desvantagem do TDD é o tempo investido necessário para escrever os testes. Por essa razão, alguns optam por não adotar essa método. Entretanto, ao longo prazo, o TDD evita dores de cabeça devido a bugs que poderiam ser evitados com cobertura de testes.
Imagine um programa de pagamento onde o usuário deve gerar um boleto. Se o boleto for gerado com sucesso ele deve ser persistido no banco de dados. O código identificador do boleto é gerado separadamente e passado posteriormente para esse método de geração. Os testes devem ser escritos da seguinte forma:
public PaymentslipPaymentTest {
PaymentslipPayment paymentslipPayment;
PaymentslipRepository paymentslipRepository;
@Test
public void generate_must_generate_and_persist_paymentslip() {
String testCode = "12345";
paymentslipPayment.generate(testCode);
List<Paymentslip> paymentslips = paymentslipRepository.findAll();
assertEquals(1, paymentslips.size());
assertEquals(testCode, paymentslips.get(0).getCode());
}
@Test
public void generate_must_throw_when_code_is_null() {
String testCode = null;
assertThrows(Exception.class, () -> paymentslipPayment.generate(testCode));
List<Paymentslip> paymentslips = paymentslipRepository.findAll();
assertEquals(0, paymentslips.size());
}
}
Os testes irão falhar pois o método generate ainda não foi implementado. Passamos para a próxima etapa que é fazer o teste passar.
public PaymentslipPayment {
private PaymentslipGenerator paymentslipGenerator;
private PaymentslipRepository paymentslipRepository;
public void generate() {
Optional<Paymentslip> paymentslip = paymentslipGenerator.generatePaymentslip();
if (!paymentslip.isPresent()) {
throw new Exception("Could generate paymentslip")
}
paymentslipRepository.save(paymentslip.get());
}
}
O usuário deve usar o método generate para gerar o boleto. O gerador de boleto irá retornar um valor opcional que caso não exista irá lançar um exceção. Caso não sejam encontrados problemas, o boleto gerado é salvo no banco de dados através do repositório.
Esse é um exemplo básico de como pode ser aplicado o TDD. Nesse exemplo, ainda existem mais cenários que podem ser cobertos. Como por exemplo quando a código do boleto é uma String vazia ou quando um boleto repetido é gerado.
O TDD é uma garantia de que o software continuará funcionando mesmo após o código sofrer alteração. Sua grande vantagem é que ele é capaz de diminuir drasticamente o número de bugs que podem vir a acontecer. Um detalhe importante é que quando o TDD é aplicado em um projeto maduro é quase impossível fazer com que o código seja testável. Essa é a importância de aplicar primeiro o teste e depois a implementação.
Projetos práticos
Convertendo imagens para ascii art usando o valor da intensidade das cores cinzentas.
Simulação dos gráficos do segundo turno das eleições presidenciais, utilizando python e ferramentas de análise de dados, pandas e jupyter.
Projeto de comunicação entre dois dispositivos ESP8266 e Raspberrypi4. Laravel irá funcionar como servidor e receptor de dados de temperatura e umidade coletados com o DHT11.
Programando um jogo clássico de arcade usando javascript e p5.js. O usuário deve quebrar os blocos utilizando uma bola ao mesmo tempo que evita que a bola saia pela parte inferior da tela
Desenvolvimento dos conceitos mais básicos do clássico pacman, como: mapa, animação, deslocamento e detector de colisões.
Esperávamos horas para baixar apenas alguns megabytes. Nessas horas, quando o telefone tocava nós fazíamos de tudo para não atender o telefone. Mas infelizmente nem sempre dava certo....
A ausência e a presença de energia são dois estados que podem ser usados como valores. Esses valores são respectivamente zero e um.
O CPU é considerado o cérebro do computador, responsável por tarefas como gerenciar instruções de dispositivos de entrada e fazer cálculos.
Práticas como hospedagem de sites ou negócios online necessitam de servidores conectados à internet para permitir o acesso externo ao conteúdo.
Mini-computador fixado a um circuito elétrico integrado com outros componentes essenciais, como memória, componente de entrada e saída
O armazenamento de imagens é realizado com o sistema de numeração binária. A imagem é composta por um conjunto de pixels e cada pixel representa uma cor.