|
|
## **CARACTERÍSTICAS DE TESTES EFICIENTES**
|
|
|
|
|
|
* Devem ser relevantes do ponto de vista do produto
|
|
|
* Devem ser reproduzíveis, ou seja, devem retornar sempre o mesmo resultado caso o código não tenha sido modificado.
|
|
|
* Devem ser rápidos já que um dos propósitos dos testes é prover um feedback rápido.
|
|
|
* Devem estar isolados. Se um teste falhar deve ser por que a feature a qual ele verifica está com problemas. Quando um teste está isolado ele permite que seja possível diagnosticar mais facilmente o problema.
|
|
|
|
|
|
## **TEST DOUBLES**
|
|
|
|
|
|
Um dos objetivos do teste unitário é isolar o código (a unidade) que está sendo testado de dependências externas. 'Doubles' são objetos que substituem essas dependências de forma que o teste avalie apenas o código em questão. Doubles podem ser classificados nos seguintes tipos:
|
|
|
|
|
|
* **Fake**: É uma versão simplificada da dependência.
|
|
|
* **Stub**: Um objeto que retorna um valor pré-definidos quando um de seus métodos é chamado com um conjunto específico de parâmetros. Podem ser pensados como respostas 'hard-coded'.
|
|
|
* **Spy**: São objetos que 'gravam' as interações com a unidade (o código sendo testado). Dessa forma pode-se avaliar posteriormente o que aconteceu com o spy durante a execução.
|
|
|
* **Mock**: É um spy que pode se 'auto-avaliar'. Na sua criação é definido um conjunto de expectativas que são avaliadas na fase de asserção do teste.
|
|
|
|
|
|
## **MOCHA**
|
|
|
|
|
|
* É um framework para criação de testes que pode ser executado em Node ou no browser
|
|
|
* Instale o módulo com **npm install --save mocha**. O exemplo abaixo demonstra como adicionar um script "test" no _package.json_ que pode ser executado com **npm run-script test** (ou simplesmente **npm test**).
|
|
|
* Sobre as opções do comando **mocha -u bdd -R spec -t 500 --recursive**
|
|
|
* **-u**: define qual interface de teste que deve ser utilizada. A interface de teste é um conjunto de funções que serão utilizadas para escrever os testes. O valor default é bdd.
|
|
|
* **-r**: define o tipo de relatório que será gerado
|
|
|
* **-t**: quantos milissegundo para que o Mocha aguardará até os testes terminarem
|
|
|
|
|
|
![package_json_test](/uploads/b6d7e241a8bacd7d2c8ffd0cd4bdcbc5/package_json_test.png)
|
|
|
|
|
|
|
|
|
|
|
|
* Exemplo de um teste simples (que falha)
|
|
|
|
|
|
![mocha_test_fail_code](/uploads/de6d3227999666d9a185f4b6736e3330/mocha_test_fail_code.png)
|
|
|
![mocha_test_fail_output](/uploads/18e90011679e0bfd511b4fe8678387d1/mocha_test_fail_output.png)
|
|
|
|
|
|
|
|
|
|
|
|
* No exemplo abaixo uma função simples (validator) está sendo testada. A palavra-chave **describe** define uma suite de testes e **it** define um caso de teste específico. Dentro de um caso de teste são feitas asserções, neste caso com o módulo **assert**, disponível por default no Node
|
|
|
|
|
|
![validator_simple](/uploads/a90494c03c132ca8e8bc8c52f8591286/validator_simple.png)
|
|
|
![validator_simple_test](/uploads/e04cd9dc3ffb8c9e57205a0acae34df4/validator_simple_test.png)
|
|
|
|
|
|
## **CHAI**
|
|
|
|
|
|
* Um substituto para o módulo default 'assert' que é mais completo e fácil de utilizar.
|
|
|
* O Chai provê 3 tipos de asserção: **expect**, **should** e **assert**.
|
|
|
* Observe que o Chai é apenas uma library de asserções e não um framework de testes como o Mocha. No exemplo abaixo demonstra-se como utilizar uma asserção do Chai em um teste do Mocha:
|
|
|
|
|
|
|
|
|
|
|
|
![chai_ex_1](/uploads/e0e1198176cd400e9c2b399e6b7c879c/chai_ex_1.png)
|
|
|
|
|
|
|
|
|
|
|
|
* O resultado da unidade sendo testada é encapsulada pela função **expect**, Este função retorna:
|
|
|
* **Chains**: elementos que não modificam o comportamento das asserções mas que provêm expressividade -- pode-se adicionar um "." para encadear com outros chains.
|
|
|
* **Assertions**: elementos que executam a avaliação do resultado.
|
|
|
* **Flags**: permitem que o comportamento da asserção seja modificado.
|
|
|
|
|
|
|
|
|
|
|
|
|![chai_expect_01](/uploads/f26d383e502231dcb375f7a61d6bbaf1/chai_expect_01.png)|![chai_expect_02](/uploads/086ed7ab9b68437f10d582209ea7b739/chai_expect_02.png)|
|
|
|
|
|
|
![chai_expect_03](/uploads/4dd935ee2a225e6036485d8dd8f7d7ed/chai_expect_03.png)|![chai_expect_04](/uploads/f213bed5903702912496ac4a94efce70/chai_expect_04.png)|
|
|
|
|
|
|
|
|
|
|
|
|
* No exemplo abaixo utiliza-se diferentes valores de input para um mesma lógica de teste -- esta técnica é chamada de triangulação:
|
|
|
|
|
|
|
|
|
|
|
|
![chai_triangulation](/uploads/a098ff8d478a58f0dc30bcfb9ea2cec9/chai_triangulation.png)
|
|
|
|
|
|
|
|
|
|
|
|
* Testes parametrizados podem executar o mesmo teste com diferentes inputs:
|
|
|
|
|
|
![chai_triangulation_function](/uploads/0e6498a31f1d5a8bc28ec1a7adf4bcc2/chai_triangulation_function.png)
|
|
|
|
|
|
|
|
|
|
|
|
## BEFORE, BEFORE EACH, AFTER, AFTER EACH
|
|
|
|
|
|
* A função **beforeEach** é executada sempre antes de cada **it** dentro de um determinado escopo.
|
|
|
|
|
|
|
|
|
|
|
|
![beforeEach_example](/uploads/5788d83ad96e2217e684343f5805dd98/beforeEach_example.png)
|
|
|
|
|
|
|
|
|
|
|
|
* A função **before** será executada uma única vez antes de todos os testes. No exemplo, isto implica que apenas uma única instância do objeto validator será compartilhada por todos os testes. Normalmente se utiliza before quando é necessário alguma preparação custosa como configuração em um banco de dados ou a inicialização de um servidor.
|
|
|
|
|
|
|
|
|
|
|
|
![before_example](/uploads/b86cc65d13d5db67b5588e9aca6fd8d3/before_example.png)
|
|
|
|
|
|
|
|
|
|
|
|
* Analogamente as funções **afterEach** e **after** são chamadas, respectivamente, sempre, depois de cada teste e uma única vez depois de todos os testes.
|
|
|
|
|
|
## CONTEXT ##
|
|
|
|
|
|
* Utilize o **context** para agrupar testes que utilizam um setup comum e descreva o contexto deles de forma descritiva.
|
|
|
|
|
|
* Como regra geral **context** é utilizado para definir cenários, **describe** para definir features e ações e **it** para implementar as asserções.
|
|
|
|
|
|
|
|
|
|
|
|
![context_example](/uploads/0dd868bbca28992f1f5cb5cb67e8b596/context_example.png)
|
|
|
|
|
|
|
|
|
|
|
|
## SINON ##
|
|
|
|
|
|
* Ao escrever um teste desejamos que ele esteja isolado de suas dependências. Por exemplo, suponha que o código sendo testado faça uma chamada Ajax -- se a chamada real precisar ser executada durante o teste então o teste fica dependente da API externa que está sendo chamada. O Sinon facilita a criação de substitutos para as dependências:
|
|
|
* **Spies**, que possuem informações sobre chamadas a métodos sem que afetem seu comportamento.
|
|
|
* **Stubs**, são como spies mas substituem completamente a função. Isto permite que uma função seja substituída por um stub que tem um comportamento pré-definido como retornar um valor específico ou uma exceção.
|
|
|
* **Mocks**, permitem substituir um objeto de forma mais fácil combinando spies e stubs.
|
|
|
|
|
|
|
|
|
## SPIES ##
|
|
|
|
|
|
* A principal funcionalidade de um spy é obter informação sobre a chamada de funções. No exemplo veja que o objeto **Spy** contém informações sobre quais argumentos forem utilizados em sua primeira chamada (_spy.firstCall.args_)
|
|
|
|
|
|
|
|
|
```javascript
|
|
|
var spy = sinon.spy();
|
|
|
|
|
|
// We can call a spy like a function
|
|
|
spy('Hello', 'World');
|
|
|
|
|
|
//Now we can get information about the call
|
|
|
console.log(spy.firstCall.args); //output: ['Hello', 'World']
|
|
|
```
|
|
|
|
|
|
* No exemplo abaixo 'espia-se' o método _setName_ de user -- veja como é possível verificar quantas vezes ele foi chamado com **setNameSpy.callCount**
|
|
|
|
|
|
```javascript
|
|
|
var user = {
|
|
|
...
|
|
|
setName: function(name){
|
|
|
this.name = name;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//Create a spy for the setName function
|
|
|
var setNameSpy = sinon.spy(user, 'setName');
|
|
|
|
|
|
//Now, any time we call the function, the spy logs information about it
|
|
|
user.setName('Darth Vader');
|
|
|
|
|
|
//Which we can see by looking at the spy object
|
|
|
console.log(setNameSpy.callCount); //output: 1
|
|
|
|
|
|
//Important final step - remove the spy
|
|
|
setNameSpy.restore();
|
|
|
```
|
|
|
|
|
|
## **STUBS**
|
|
|
|
|
|
* Quando utilizamos um spy a função real ainda é executada -- com um stub ela é completamente substituída:
|
|
|
|
|
|
```javascript
|
|
|
var stub = sinon.stub();
|
|
|
stub('hello');
|
|
|
console.log(stub.firstCall.args); //output: ['hello']
|
|
|
```
|
|
|
|
|
|
* No exemplo abaixo, substitui-se o método _saveUser_ (que faz uma chamada Ajax) por um stub -- dessa forma é removida a dependência ao servidor externo:
|
|
|
|
|
|
```javascript
|
|
|
/* FUNÇÃO A SER TESTADA*/
|
|
|
function saveUser(user, callback) {
|
|
|
$.post('/users', {
|
|
|
first: user.firstname,
|
|
|
last: user.lastname
|
|
|
}, callback);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
```javascript
|
|
|
describe('saveUser', function() {
|
|
|
it('should call callback after saving', function() {
|
|
|
//We'll stub $.post so a request is not sent
|
|
|
var post = sinon.stub($, 'post');
|
|
|
|
|
|
post.yields();
|
|
|
|
|
|
//We can use a spy as the callback so it's easy to verify
|
|
|
var callback = sinon.spy();
|
|
|
|
|
|
saveUser({ firstname: 'Han', lastname: 'Solo' }, callback);
|
|
|
post.restore();
|
|
|
sinon.assert.calledOnce(callback);
|
|
|
});
|
|
|
});
|
|
|
```
|
|
|
|
|
|
* Outro uso comum de stubs é verificar se uma função foi chamada com um conjunto específico de argumentos. No exemplo abaixo o método 'post' do jQuery ($) é avaliado -- é verificado se o método foi chamado com a url e parâmetros definidos anteriormente com **sinon.assert.calledWith(post, expectedUrl, expectedParams)**
|
|
|
|
|
|
```javascript
|
|
|
describe('saveUser', function() {
|
|
|
it('should send correct parameters to the expected URL', function() {
|
|
|
//We'll stub $.post same as before
|
|
|
var post = sinon.stub($, 'post');
|
|
|
|
|
|
//We'll set up some variables to contain the expected results
|
|
|
var expectedUrl = '/users';
|
|
|
var expectedParams = {
|
|
|
first: 'Expected first name',
|
|
|
last: 'Expected last name'
|
|
|
};
|
|
|
|
|
|
//We can also set up the user we'll save based on the expected data
|
|
|
var user = {
|
|
|
firstname: expectedParams.first,
|
|
|
lastname: expectedParams.last
|
|
|
}
|
|
|
|
|
|
saveUser(user, function(){} );
|
|
|
post.restore();
|
|
|
sinon.assert.calledWith(post, expectedUrl, expectedParams);
|
|
|
});
|
|
|
});
|
|
|
```
|
|
|
|
|
|
## **MOCKS**
|
|
|
|
|
|
* Normalmente utilizados quando se precisa fazer o stub de mais de uma função em um único objeto. Se apenas uma função precisa ser substituída um stub é mais fácil de usar.
|
|
|
* Mocks podem fazer suas próprias asserções. Define-se as expectativas no próprio objeto mock que são verificadas ao final do teste.
|
|
|
|
|
|
```javascript
|
|
|
describe('incrementStoredData', function() {
|
|
|
it('should increment stored value by one', function() {
|
|
|
var storeMock = sinon.mock(store);
|
|
|
|
|
|
storeMock.expects('get').withArgs('data').returns(0);
|
|
|
storeMock.expects('set').once().withArgs('data', 1);
|
|
|
incrementStoredData();
|
|
|
storeMock.restore();
|
|
|
storeMock.verify();
|
|
|
});
|
|
|
});
|
|
|
```
|
|
|
|
|
|
* Encapsule o teste com o método sinon.test() -- dessa forma não é necessário utilizar o método restore() ao final (que restaura o estado do stub ou mock de forma a não influenciar os demais testes)
|
|
|
|
|
|
```javascript
|
|
|
it('should do something with stubs', sinon.test(function() {
|
|
|
var stub = this.stub($, 'post');
|
|
|
doSomething();
|
|
|
sinon.assert.calledOnce(stub);
|
|
|
});
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|