Skip to content

Commit

Permalink
Add new exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrolacerda committed Mar 13, 2021
1 parent b02d0df commit bb562c0
Show file tree
Hide file tree
Showing 9 changed files with 4,388 additions and 239 deletions.
2 changes: 1 addition & 1 deletion exercicios/02.exercicio-testes-unitarios.md
Expand Up @@ -185,4 +185,4 @@ describe('Reservations Library', function() {

## Próxima seção

Avançar para exercício [03. Implementando Stubs e Mocks](03.exercicio-stubs-mocks.md)
Avançar para exercício [03. Implementando Stubs e Mocks](03.exercicio-test-doubles.md)
9 changes: 0 additions & 9 deletions exercicios/03.exercicio-stubs-mocks.md

This file was deleted.

128 changes: 128 additions & 0 deletions exercicios/03.exercicio-test-doubles.md
@@ -0,0 +1,128 @@
# Exercícios práticos sobre teste de software

Siga as instruções abaixo para instalar as ferramentas utilizadas para melhorar a qualidade da aplicação.

## 3. Implementando Test Doubles

### 3.1 Instalando `Sinon`, `sinon-chai` e `Proxyquire`

A biblioteca [**`Sinon.JS`**](https://sinonjs.org/) é uma biblioteca Javascript para criar test spies, stubs e mocks. Ou seja, simular o comportamento de um módulo para possibilitar a execução de testes unitários. **Sinon** pode ser usada com qualquer framework de testes.

Vamos utilizar também um módulo chamado `sinon-chai` que, apesar de não ser obrigatório, facilita a leitura e organização dos testes.

Também devemos prestar atenção às dependências do nosso código. Grande parte das funções depende de bibliotecas externas para executar suas tarefas. Portanto é preciso isolar uma unidade a ser testada que possui uma dependência. Para isso vamos usar o [**`Proxyquire`**](https://www.npmjs.com/package/proxyquire). Para instalar ambas bibliotecas execute:

```bash
npm install -D sinon sinon-chai proxyquire
```

### 3.2 Configurando a aplicação para trabalhar com Stubs

Vamos usar Stubs anônimos para remover/alterar configurações de ambiente da nossa aplicação, apenas como demonstração. No seu arquivo `package.json` altere a linha 13 (ou a linha que chama o script _test_), para que ela tenha o seguinte conteúdo: `"test": "cross-env DEBUG=restaurant-reservation:* mocha"`. O conteúdo do seu objeto `scripts` deve ficar assim:

```json
"scripts": {
"debug": "cross-env DEBUG=restaurant-reservation:* nodemon",
"eslint": "eslint",
"test": "cross-env DEBUG=restaurant-reservation:* mocha --recursive",
"start": "node bin/www"
},
```

Podemos também simular como um método responde para validar o comportamento da função que estamos testando e que interage com esse método. Uma vez que sabemos qual a estrutura da resposta de um método, o objetivo é combinar esta estrutura.

No arquivo `./test/unit/lib/reservations.js`, substituia o seu conteúdo por:

```javascript
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const chai = require('chai');
const should = chai.should();
const sinonChai = require('sinon-chai');
chai.use(sinonChai);
const Reservation = require('../../../lib/schema/reservation');
const db = require('sqlite');

describe('Reservations Library', function() {
const debugStub = function() {
return sinon.stub();
}
let reservations;

before(function() {
reservations = proxyquire('../../../lib/reservations', {
debug: debugStub
});
});

context('Validate', function() {
it('should pass a valid reservation with no optional fields', function() {
const reservation = new Reservation({
date: '2017/06/10',
time: '06:02 AM',
party: 4,
name: 'Family',
email: 'username@example.com'
});

return reservations.validate(reservation)
.then(actual => actual.should.deep.equal(reservation));
});

it('should fail an invalid reservation with a bad email', function() {
const reservation = new Reservation({
date: '2017/06/10',
time: '06:02 AM',
party: 4,
name: 'Family',
email: 'username'
});

return reservations.validate(reservation)
.catch(error => error.should.be.an('error').and.not.be.null);
});
});

context('Create', function() {
let dbStub;
let validateSpy;

before(function() {
dbStub = sinon.stub(db, 'run').resolves({
stmt: {
lastID: 1349
}
});
reservations = proxyquire('../../../lib/reservations', {
debug: debugStub,
sqlite: dbStub
});
});

after(function() {
dbStub.restore();
});

it('should return the created reservation ID', function(done) {
const reservation = new Reservation({
date: '2017/06/10',
time: '06:02 AM',
party: 4,
name: 'Family',
email: 'username@example.com'
});

reservations.create(reservation)
.then(lastID => {
lastID.should.deep.equal(1349);
done();
})
.catch(error => done(error));
});
});
});
```

## Próxima seção

Avançar para exercício [04. Analisando Cobertura de Testes](04.exercicio-cobertura.md)
155 changes: 155 additions & 0 deletions exercicios/04.exercicio-cobertura.md
Expand Up @@ -3,3 +3,158 @@
Siga as instruções abaixo para instalar as ferramentas utilizadas para melhorar a qualidade da aplicação.

## 4. Analisando Cobertura de Testes

Para monitorar a cobertura de testes na nossa aplicação vamos usar o [**`Istanbul`**](https://istanbul.js.org/).

### 4.1 Instalando e configurando o `Istanbul`

Para instalar o `Istanbul` execute o seguinte comando:

```bash
npm install -D nyc
```

_Sim, o `nyc`. É CLI para o **Istanbul**_

No arquivo `package.json` adicione a seguinte linha no objeto `scripts`: `"coverage": "cross-env DEBUG=restaurant-reservation:* nyc --reporter=text --reporter=html mocha --recursive",`. Ele deve ficar da seguinte maneira:

```json
"scripts": {
"coverage": "cross-env DEBUG=restaurant-reservation:* nyc --reporter=text --reporter=html mocha --recursive",
"debug": "cross-env DEBUG=restaurant-reservation:* nodemon",
"eslint": "eslint",
"test": "cross-env DEBUG=restaurant-reservation:* mocha --recursive",
"start": "node bin/www"
},
```

Para analisar a cobertura de testes do projeto, execute:

```bash
npm run -s coverage
```

Junto com o relatório gerado no terminal, também foi gerado um relatório em HTML. Basta abrir o arquivo `./coverage/index.html` no seu browser.

Precisamos remover os arquivos gerados automaticamente pelo relatório de cobertura de código do nosso linter. Para isso, abra o arquivo `.eslintignore` e adicione a seguinte linha:

```properties
coverage/*
```

<!--npm run -s eslint . -->

### 4.2 Instalando e configurando o `chai-http`

Para testes funcionais, vamos utilizar o `chai-http`. Uma biblioteca que utiliza o SuperAgent, uma biblioteca de requisições HTTP pequena e progressiva, do lado do cliente.

Para instalar o `chai-http` execute o seguinte comando:

```bash
npm install -D chai-http
```

### 4.3 Criando testes funcionais

Crie um novo arquivo de testes para os testes funcionais: `./test/functional/reservations.js`. Ele deve ter o seguinte conteúdo:

```javascript
const chai = require('chai');
const chaiHttp = require('chai-http');
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const logger = require('morgan');
chai.use(chaiHttp);
const should = chai.should();

describe('/reservations', function() {
let dbStub;
let loggerStub;
let debugStub;
let app;

before(function() {
dbStub = {
run: function() {
return Promise.resolve({
stmt: {
lastID: 1349
}
});
}
};
dbStub['@global'] = true;

loggerStub = sinon
.stub(logger, 'morgan')
.returns(function(req, res, next) {
next();
});

debugStub = function() {
return sinon.stub();
}
debugStub['@global'] = true;

app = proxyquire('../../app', {
sqlite: dbStub,
morgan: loggerStub,
debug: debugStub
});
});

after(function() {
loggerStub.restore();
});

context('GET', function() {
it('should return the reservations form', function(done) {
chai.request(app)
.get('/reservations')
.end(function(err, res) {
res.should.have.status(200);
res.text.should.contain('To make reservations please fill out the following form');
done(err);
});
});
});

context('POST', function() {
it('should accept a valid reservation request', function(done) {
chai.request(app)
.post('/reservations')
.set('content-type', 'application/x-www-form-urlencoded')
.send({
date: '2017/06/10',
time: '06:02 AM',
party: 4,
name: 'Family',
email: 'username@example.com'
})
.end(function(err, res) {
res.text.should.contain('Thanks, your booking request #1349');
res.should.have.status(200);
done(err);
});
});

it('should reject an invalid reservation request', function(done) {
chai.request(app)
.post('/reservations')
.set('content-type', 'application/x-www-form-urlencoded')
.send({
date: '2017/06/10',
time: '06:02 AM',
party: 'bananas',
name: 'Family',
email: 'username@example.com'
})
.end(function(err, res) {
res.text.should.contain('Sorry, there was a problem with your booking request.');
res.should.have.status(400);
done();
});
});
});
});
```
2 changes: 1 addition & 1 deletion lib/reservations.js
Expand Up @@ -19,7 +19,7 @@ function getAll() {
*/
function create(reservation) {
return new Promise((resolve, reject) => {
validate(reservation)
module.exports.validate(reservation)
.then(module.exports.save)
.then(statement => resolve(statement.stmt.lastID))
.catch(error => reject(error));
Expand Down

0 comments on commit bb562c0

Please sign in to comment.