Basicamente, testes automatizados são uma forma de automatizar o processo de revisão e validação de um software através de uma ferramenta programável. Esse processo, que antes podia ser extremamente trabalhoso e demorado, pode ser feito em minutos -- ou até mesmo segundos, dependendo dos tipos e da quantidade de testes existentes na sua aplicação.
Mike Cohn criou o conceito de pirâmide de testes no livro "Succeeding with Agile". É basicamente uma forma de te fazer pensar nas várias camadas de testes e em como elas devem estar presentes:
Quanto mais baixo o nível do teste na pirâmide, mais desacoplado e mais rápido ele é, além de mais numeroso.
Há inúmeros tipos de teste -- muitos dos quais eu mesmo desconheço a fundo -- mas abordarei apenas os testes de unidade. Eles possuem esse nome pois tendem as menores unidades de classes e métodos de forma isolada do resto da aplicação: sem acoplamento, nem nada. Apenas verificações para garantir que eles funcionem conforme o esperado, que as variáveis tenham os valores esperados etc.
Uma disciplina importante para criar testes é aquela chamada de TDD - Desenvolvimento orientado a testes. Nela, primeiro se escreve os testes, depois o código para que o teste funcione, depois o código é refatorado para que se torne a melhor versão que puder ser (esse último passo costuma ser bastante ignorado, gerando débitos técnicos).
Ok, mas por que começar pelos testes e não pelo código ? Porque para criar uma solução, é necessário saber o que precisa ser criado. É necessário saber de antemão o que uma classe deve fazer, o que um método deve fazer, o valor que uma variável deve ter etc. Sem isso em mente, é muito fácil assumir que um resultado qualquer é o correto. E é assim que nascem os bugs 🐞!
Sem mais delongas, bora pro código.
Mãos à obra
Criei uma aplicação console para exemplificar. Nela, criarei uma classe "Conta" com as seguintes especificações:
- Uma pessoa pode depositar qualquer valor maior que zero na conta;
- Uma pessoa pode sacar valores maiores que 0 e menores ou iguais ao saldo total;
Adicionei um projeto NUnit com o Visual Studio. Nele, criei uma classe chamada ContaTests, conforme abaixo. O atributo [TestFixture] indica que uma classe possui testes.
[TestFixture]
public class ContaTests
{
}
E então parti para o teste do primeiro método. Para que um método seja detectado como teste, é necessário adicionar o atributo [Test] acima dele. Quanto ao nome do método de teste, fiz baseando-me em algumas recomendações que podem ser encontradas neste link..
[Test]
public void Depositar_RetornaSaldoMaisValorDepositado()
{
// Arrange
// Act
// Assert
}
Gosto de deixar os 3 comentários em todos os testes que crio para separar bem as fases dele. Essas fases formam o padrão AAA, muito utilizado na indústria, e correspondem a:
- Arrange: fase na qual é necessário preparar teste preparando as variáveis, criando instâncias de objeto necessárias etc.;
- Act: fase na qual o método a ser testado é executado;
- Assert: fase na qual é feita a verificação de que o resultado obtido está de acordo com o esperado.
Na primeira fase, criei apenas uma instância da classe Conta, que ainda não existe, e então criei uma variável com o valor que espero obter como resultado do teste.
// Arrange
Conta conta = new Conta("Lucas", 100);
decimal saldoEsperado = 200;
Na fase de ação, executei o método e armazenei o resultado em uma variável:
// Act
var resultado = conta.Depositar(100);
E por fim, fiz a verificação de que o resultado era igual ao saldoEsperado. Para isso, usei a classe Assert do NUnit. Ela contém vários métodos estáticos para testar inúmeras situações, como listas, igualdades, nulos etc.
// Assert
Assert.AreEqual(saldoEsperado, resultado);
O teste final ficou da seguinte forma:
[Test]
public void Depositar_RetornaSaldoMaisValorDepositado()
{
// Arrange
Conta conta = new Conta("Lucas", 100);
decimal saldoEsperado = 200;
Act
var resultado = conta.Depositar(100);
Assert
Assert.AreEqual(saldoEsperado, resultado);
}
O teste não compilará no primeiro momento pois a classe Conta ainda não existe. Logo, decidi criá-la:
public class Conta
{
public Conta(string cliente, decimal saldo)
{
Cliente = cliente;
Saldo = saldo;
}
public string Cliente { get; private set; }
public decimal Saldo { get; private set; }
public decimal Depositar(decimal valor)
{
Saldo += valor;
return Saldo;
}
}
Aproveitei e também criei o método Depositar. Ele é simples: recebe um valor e então adiciona esse valor à propriedade Saldo da classe.
Adicionei uma referência da classe no projeto de teste e então o executei. E então o teste passou !
⚠️⚠️ Uma forma de garantir que o teste funciona é fazendo-o quebrar propositalmente. Uma forma de quebrar o teste acima seria alterando o valor esperado para 300. Se o teste e o código estiverem certos, o teste não passará. Isso é importante para evitar que o resultado seja um falso-positivo.⚠️⚠️
Ok, o valor adicionado está correto, a soma está correta. Mas não é tudo a ser testado nesse método:
- Uma pessoa pode depositar qualquer valor maior que zero na conta;
Logo, é necessário criar um teste para verificar se o meu programa barra o depósito de um valor negativo como -R$100.
[Test]
public void Deposito_QuandoExecutadoComValorNegativo_LancaLancaInvalidOperationException()
{
//Arrange
Conta conta = new Conta("Lucas", 100);
//Act
Func<decimal> acao = () => conta.Depositar(-100);
//Assert
Assert.Throws<InvalidOperationException>(() => acao());
}
A grande diferença está nas fases Act e Assert. Assim que o valor -100 for detectado pelo método, é necessário que ele lance uma nova exceção: InvalidOperationException. Quando ela for lançada, o programa sairá daquele método antes de completá-lo, fazerndo com que o assert seja um pouco diferente. Para testar se ela realmente foi lançada, primeiro armazenei o método em um delegate (eu poderia explicar o significado mas tanto a Microsoft quanto o Macoratti fizeram um excelente trabalho falando sobre) e então utilizei o método Throws da classe Assert. Ele recebe o tipo da exceção como generic (artigo da Microsoft e artigo do Macoratti) e então consegue verificar se a exceção foi lançada.
Ao rodar o teste na primeira vez, ele não passoupois o método ainda não lança a exceção. Adicionei apenas 1 if para lançar a exceção e então o teste passou !
public decimal Depositar(decimal valor)
{
if (valor <= 0)
{
throw new InvalidOperationException("Não é possível depositar valores negativos");
}
Saldo += valor;
return Saldo
}
⚠️⚠️Para quebrar o teste e verificar se ele não dá um falso positivo, é possível simplesmente alterar o tipo da exceção esperada no Assert.⚠️⚠️
Por se tratar de um exemplo, o método de saque terá testes bastante parecidos com o do depósito, então é possível verificá-lo, assim como todo o resto do código, nesse repositório do GitHub que criei!
Bem, essa foi uma breve introdução sobre testes de unidade. Na próxima parte falarei sobre Mocks em um exemplo de uma API Rest e sobre o método SetUp.Se curtiu, não esqueça de compartilhar a postagem!
Até a próxima !
Top comments (0)