Introdução ao Test Driven Development usando exemplos
Existe um ótimo livro escrito por Kent Beck, Test-Driven Development by Example. Este livro é altamente recomendado na indústria porque, apesar de todas as suas falhas (e lentamente ficando desatualizado), ele fornece um esboço atemporal para o desenvolvimento orientado a testes. Test Driven Development (TDD) é algo que eu adoro, mas em um mundo onde os pontos-chave podem ser apresentados em um artigo, recomendar um livro (por melhor que seja) tornou-se mais difícil ultimamente. Se você já se perguntou como programar usando TDD, por que ele existe e por que é tão poderoso, este artigo está prestes a explorar algumas das razões.
O desenvolvimento orientado a testes é uma disciplina da engenharia de software. É a prática de escrever um teste para um determinado conjunto de código, depois escrever o código de produção para esse teste e, finalmente, limpar (refatorar) o código. Este ciclo é a espinha dorsal do desenvolvimento orientado a testes, vermelho (testes escritos), verde (testes aprovados) e limpo (refator quando em estado verde). Este ciclo mostra o poder do TDD - não há necessidade de testar o código da mesma forma que seus usuários (ou QA). Os testes devem ser rápidos e a capacidade de executá-los deve ser facilmente acessível - um botão de distância e não apenas controle de qualidade.
Vamos escrever um teste para algum código teórico. Os exemplos neste artigo estão em C#, no entanto, os desenvolvedores Java devem entender facilmente. Se você quiser tentar isso enquanto avançamos, este é o modelo de projeto em branco e este é o projeto final . Se estiver usando Java, este é um projeto em branco e dentro do repositório do projeto final está uma versão Java deste tutorial.
[TestClass]
public class AccountTests
{
[TestMethod]
public void Balance_ReturnsZero_WhenANewAccountIsCreatedTest()
{
// Arrange
var newAccount = new Account();
// Act
int balance = newAccount.Balance;
// Assert
Assert.AreEqual(0, balance);
}
}
public class Account
{
public int Balance;
}
Por que esse 'menor código' é uma boa regra? Um objetivo no TDD é 100% de cobertura de código, um engenheiro dorme bem quanto mais acredita que o código que escreveu não é um mistério (código misterioso significa buggy e não totalmente testado). 100% de cobertura não é possível por vários motivos, mas tentar chegar o mais próximo possível disso é uma meta eficaz para alcançar os pontos positivos do TDD. Quando alguém escreve mais código do que o necessário, eventualmente escreve caminhos de ramificação (ou dados dependentes), o que significa que o comportamento muda. Todo comportamento esperado de um determinado módulo deve ter um teste ; portanto, se alguém escreve um código mínimo para alcançar o verde, o ônus recai sobre os testes para provar que os comportamentos estão errados .
[TestMethod]
public void Overdraft_ReturnsZero_WhenANewAccountIsCreatedTest()
{
// Arrange
var newAccount = new Account();
// Act
int overdraft = newAccount.Overdraft;
// Assert
Assert.AreEqual(0, overdraft);
}
[TestMethod]
public void Balance_ReturnsZero_WhenOverdraftAndBalanceAreZeroAnd100IsTakenTest()
{
// Arrange
var newAccount = new Account();
// Act
newAccount.Balance -= 100;
int balance = newAccount.Balance;
// Assert
Assert.AreEqual(0, balance);
}
Qual é a menor quantidade de código que alguém pode escrever para que esses testes sejam aprovados? Talvez o cheque especial seja zero e devolva em espécie ou o saldo só devolva zero. Este é o código que decidi usar, que encapsula por que escrever o menor código é eficaz.
public class Account
{
public int Balance
{
get => 0;
set { }
}
public int Overdraft;
}
[TestMethod]
public void Balance_Returns100_When100IsAddedToABlankBalanceTest()
{
// Arrange
var newAccount = new Account();
// Act
newAccount.Balance += 100;
int balance = newAccount.Balance;
// Assert
Assert.AreEqual(100, balance);
}
public class Account
{
public int Balance
{
get => balance;
set
{
if(value > 0)
{
balance = value;
}
}
}
private int balance;
public int Overdraft;
}
[TestMethod]
public void Balance_ReturnsMinus100_WhenBalanceIsZeroAndOverdraftIs100And100IsTakenFromBalanceTest()
{
int expectedValue = -100;
// Arrange
var newAccount = new Account();
newAccount.Overdraft = 100;
// Act
newAccount.Balance -= 100;
int balance = newAccount.Balance;
// Assert
Assert.AreEqual(expectedValue, balance);
}
// Production code
public class Account
{
public int Balance
{
get => balance;
set
{
if(value >= -Overdraft)
{
balance = value;
}
}
}
private int balance;
public int Overdraft;
}
Não há muito código acima, mas o suficiente, do qual não gosto, que deve ser corrigido. Lógica dentro da propriedade definida deve ser evitada (geralmente apenas otimizações com propriedade alterada são aceitáveis). Embora os testes passem no código dentro da operação Set, esse código provavelmente deve entrar em um método.
Enquanto os testes passam (verde), os programadores estão confiantes de que quaisquer alterações não afetam o comportamento. Cada alteração que visa alterar o comportamento deve ser acompanhada pela execução dos testes (pelo menos para o módulo). Se algo interromper o comportamento, o programador saberá que o estado mudará para vermelho e poderá informá-los vitalmente sobre o que eles fizeram de errado.
public class Account
{
public int Balance
{
get => balance;
set
{
balance = ReturnHighestSafeBalance(balance, value);
}
}
private int balance;
public int Overdraft;
private int ReturnHighestSafeBalance(int existingValue, int newValue)
{
int safeValue = existingValue;
if (newValue >= -Overdraft)
{
safeValue = newValue;
}
return newValue;
}
}
Esse fluxo é o básico do Desenvolvimento Orientado a Testes, mas apenas arranha a superfície do que pode ser alcançado.
Existem alguns benefícios que podem ser observados na pequena quantidade de código escrito neste artigo. Os testes escritos para um determinado módulo são uma forma de documentação, eles ensinam a qualquer programador não apenas quais são os resultados esperados, mas também o caminho mais curto para atingir esses resultados. Ao colaborar em uma grande base de código, os testes garantem que quaisquer alterações ou novas funcionalidades não quebrem a funcionalidade existente (regressão) (também é provável que os testes de regressão sejam corrigidos).
Em artigos futuros, espero explorar como testar sem o código completo — pela primeira vez usando estruturas Mocking. Dentro dos frameworks Mocking, também está a capacidade de testar a lógica sem testar especificamente uma determinada unidade - que, como tudo, tem suas desvantagens. Objetos degenerados, por exemplo, devem ser evitados, os quais poderiam ser facilmente explorados em seu próprio artigo. Por enquanto, espero que você tente algum desenvolvimento orientado a testes e especialmente se em C # ou Java!
Transferências
- Projeto final (C# e Java)
- TDD por Agile Alliance ( link )
- O que é Desenvolvimento Orientado a Testes (TDD): Abordagem e Benefícios por Jash Unadkat ( link )
- O que é Desenvolvimento Orientado a Testes? de TestDriven.io ( link )
- Desenvolvimento orientado a testes: por exemplo de Kent Beck (2003)
- Learning Test-Driven Development: A Polyglot Guide to Writing Uncluttered Code by Saleem Siddiqui (2021)
- Código Limpo: Um Manual de Artesanato de Software Ágil (2008)
- Imagem de teste de código: Imagem de upklyak no Freepik
- 23/dez/2022: Adicionada versão Java do projeto.





































![O que é uma lista vinculada, afinal? [Parte 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)