Desenvolvimento Guiado a Testes
Kent Beck(Pai do TDD), criou a biblioteca SUnit para a linguagem Smalltalk em 1990 para testes unitários.
Em 1998 Kent Beck e Erich Gamma desenvolveram a biblioteca JUnit para testes unitários no Java.
Foi sendo percebido que ao desenvolver guiado a testes o design dos sistemas estavam melhorando.
Red-Green-Refact
- Primeiro passo(Red): Escreva um teste que falhe
- Segundo passo(Green): Escreva o código para que o teste tenha sucesso
- Terceiro passo(Refact): Elimine a redundância
- Garantir a qualidade do softare;
- Segurança e facilidada na manutenção;
- Melhor design de software;
- Documentação técnica.
- Unitário: Testes de pequenos trechos de código, uma função por exemplo.
- Integração: Testes que envolvem mais de um elemento, classes e serviços que possuem dependência.
- Sistema(código legado)
Testes unitários e de integração são mais fáceis e baratos para se fazer.
Segundo a pirâmide de testes do Martin Flower, um sistema deve possuir mais testes de unidades do que os outros tipos.
- UI (Acceptance Tests)
- Service (Integration Tests)
- Unit (Unit Tests) (Prioridade, base da pirâmide)
- Testes devem ser confiáveis
- Testes devem ser fáceis de escrever
- Restes devem ser fáceis de entender hoje e no futuro
- Não estamos focados em velocidade
- Setup: Quando é colocado o SUT(System Under Test, o objeto sendo testado) no estado necessário para o teste;
subject = described_class.new
- Exercise: Quando acontece a interação com o SUT;
result = subject.sum(5,5)
- Verify: Quando é verificado o comportamento esperado;
expect(result).to eq(10)
- Teardown: Quando o sistema é colocado no estado em que ele estava antes do teste ser executado(Utilizando Rspec, isso ocorre automaticamente)
Desenvolvimento Guiado a Comportamento
Criado em 2003 por Dan North devido a dificuldade de ensinar o TDD aos seus alunos, que ficavam com dúvida por onde começar, o que testar e como nomear os testes.
O BDD nasceu como um modo de rever a nomenclatura do TDD e como se enxerga essa prática, hoje BDD é uma abordagem de desenvolvimento de software.
Uma abordagem/método que te faz pensar em como usar seu código e a partir disso gerar uma documentação/especifícação do comportamento que seu código faz.
A principal motivação do TDD não é testar o seu software, e sim especificá-lo com exemplos de como usar o seu código e deixar isso guiar o design do software.
Teste == Comportamento
Ferramentas: Cucumber, BDD Gem
O BDD permite que o cliente participe/crie da especificação dos cenários.
rspec --init
inicializa os arquivos padrões do rspec.
rspec
roda os testes do diretório.
rspec /folder
executa todos os testes dentro da pasta.
rspec /folder/test_spec.rb
executa determinado arquivo de teste.
rspec /folder/test_spec.rb -e "teste"
executa o teste informado do arquivo, pelo nome.
rspec /folder/test_spec.rb:10
executa o teste da linha informada.
Os arquivos de testes serão mantidos dentro do diretório spec
e também precisam possuir o sufixo _spec.rb
Um arquivo de teste inicia-se com o método describe
, sinalizando que iremos descrever/"explicar" algo através de testes.
Quando informado uma classe no describe, o rspec irá tentar localizar a classe no diretório
describe Class do
...
end
Não é necessário informar uma classe, pode ser uma string também
describe 'test one' do
...
end
Podemos também passar para o describe
tanto a classe como um texto:
describe Calculator, "Testes sobre calculadora" do
...
end
Dentro do describe
podemos utilizar três métodos para escrever nossos testes, são eles: it, specify e example
it 'do something' do ... end
specify 'do something' do ... end
example 'do something' do ... end
Até a versão 2.11 do Rspec, era utilizado o método expect
dentro dos teste para indicar o resultado esperado.
A partir da versão 2.11 até atualmente, o método utilizado é o expect
Link útil: https://www.betterspecs.org/
Pensando em agrupar nossos testes, teve a ideia do comando context
, ele serve para agruparmos testes que são referente a uma mesma função e/ou contexto no qual o teste ocorre.
context '#sum' do
it 'with positive numbers' do
...
end
end
Dentro do describe
, quando passamos uma classe para ele, é possível fazer uso do método subject
que irá instanciar um objeto da classe informada(implícito):
describe Calculator do
# Calculator.new
subject
...
end
Para renomear o subject:
subject(:calc) { described_class.new }
Nos casos onde passamos para o describe
um texto, também podemos definir nosso subject(explícito):
describe "Calculadora" do
subject { Calculator.new }
...
end
Podemos usar o método xit
no lugar do it
nos testes para tornar um teste pendente, fazendo com que o rspec ignore ele.
xit "wip test" do
...
end
expect().to ...
expect().to_not ...
Compara o objeto instanciado em si, não o valor
expect(calc).to equal(calc)
Compara o valor que o objeto instanciado possui
expect(result).to eq("ruby")
Validar que o valor esperado é verdadeiro
expect(1.odd?).to be true
expect(1.odd?).to be_truthy
Validar que o valor esperado é falso
expect(1.even?).to be false
expect(1.even?).to be_falsey
Validar que o valor esperado é nulo
expect(nome).to be_nil
Utilizamos o matcher be
para fazer comparações
expect(result).to be > 5
expect(5).to be >= 5
expect(5).to be < 10
expect(10).to be <= 10
expect(10).to be_between(5, 10).inclusive
expect(9).to be_between(5, 10).exclusive
expect(email).to match(/.@./)
Pode ser utilizado com texto e arrays
expect(numbers).to start_with 1
expect(name).to start_with "Alex"
Pode ser utilizado com textos e arrays
expect(numbers).to end_with 3
expect(name).to end_with "Teste"
Compara se o objeto é instância direta de determinada classe
expect(name).to be_instance_of String
Compara se o objeto é instância direta de determinada classe ou se o objeto pertence a uma classe que herda da classe informada.
Class Texto < String
expect(texto).to be_kind_of Texto
expect(texto).to be_kind_of String
Verifica se o objeto informado possui determinado método diretamente ou indiretamente
expect(name).to respond_to(:size)
Valida se a instância possui o atributo e valor informado
expect(person).to have_attributes(:name, age: 20)
.nil? => be_nil .even? => be_even
Podemos usar métodos predicados do Ruby no Rspec
expect(10).to be_even
Para capturar erros/exceções, precisamos utilizar o expect da seguinte forma:
expect{subject}.to ...
Passando para ele, entre chaves, a ação que irá gerar o erro.
Apena valida se a ação gerou uma exceção, sem especificar qual.
expect{subject}.to raise_exception
Com esse, podemos validar a classe da exceção e a mensagem.
expect{subject}.to raise_error(ZeroDivisionError)
expect{subject}.to raise_error("divided by 0")
expect{subject}.to raise_error(ZeroDivisionError, "divided by 0")
expect{subject}.to raise_error(/divided/)
Podemos iniciar um arquivo utilizando somente o describe
ou RSpec.describe
Também podemos passar para o describe uma string que será interpretada como subject, no lugar de uma classe.
Verifica se os valores informados estão presentes no subject
expect(subject).to include(1,2)
Alias do include
Verifica se o subject é exatamente igual o array informado
expect(subject).to match_array([1,2,3])
Verificar se um ou mais valores estão no range(subject)
expect(subject).to cover(2,4)
Utiliza a premissa de testar o subject do teste, não precisando passar o subject.
is_expected.to cover(5)
Combinado com o it { ... }
se torna muito eficaz e ajuda na verbosidade do teste no log, colocando automaticamente should + matcher
it { is_expected.to cover(5) }
it { expected(subject).to cover(5) }
Podemos compor matchers no expect adicionando .and
ou .or
após o matcher
it { is_expected.to start_with('a').and end_with('b') }
it { is_expected.to eq(1).or eq(2) }
Com o all, podemos validar todos os itens da coleção
it { is_expected.to all(be_odd)
Matcher para valores ponto flutante, com ele podemos dizer que esperamos uma diferença X a partir de um valor Y.
it { expect(12.5).to be_within(0.5).of(12) }
Com isso, o resultado será válido caso esteja entre 11.5 até 12.5
Utilizamos o satisfy para validações mais específicas ou complexas que outro matcher não consiga oferecer.
it { expect(2).to satisfy { |e| e % 2 == 0 } }
it { expect(2).to satisfy('be even') { |e| e % 2 == 0 } }
Qualquer método def ...
que seja criado dentro do spec para auxiliar nos testes.
Um arquivo module ...
que possui um ou mais métodos para auxiliar nos testes, podem ser utilizados nos teste após serem incluídos/importados no RSpec.configure
Executar determinada ação antes dos testes
before(:suite)
-> Antes de todos os testes do spec
before(:all)
ou before(:context)
-> Antes de todo contexto/conjunto de testes
before(:each)
ou before(:example)
-> Antes de cada teste
before do
end
Executar determinada ação depois dos testes
after(:suite)
-> Depois de todos os testes do spec
after(:all)
ou after(:context)
-> Depois de todo contexto/conjunto de testes
after(:each)
ou after(:example)
-> Depois de cada teste
after do
end