testing sucks
DESCRIPTION
Presentation given in BrazilJS 2012TRANSCRIPT
Testing SucksLeo Balter - BrazilJS 2012 JS
Testar é chato!
Testes vs Testar!
Testes humanos• Repetitivos
• Estressantes
• Cansativos
Concentração polarizada
• Nos concentramos em um único ponto
• Não vemos os demais
• falsa sensação de segurança
Olhar Viciado
Testes de Trollagem
Pare de “ficar testando” seu código
JSHint/JSLint não testa código!
• Convenção de estilo
• Erros comuns
• “As Boas Partes”
Mito do Compilador
Pânico vs Benefícios
• Muito fácil criar testes para algo que ainda não existe
• Não tão simples quando a aplicação já existe e está “no ar”.
Aplicação não testada
A Síndrome do “o que está
funcionando não se deve mexer”.
Quebre a aplicação!• Criamos testes simples que passem
• Teste o que está em produção
• Cobertura de testes
• O que é crítico?
• Especificação
• O que falta testar?
• Testes de Trollagem
Acredite:Sua aplicação vai falhar!
Já viu essa síndrome?
• Não vai ao médico pra evitar descobrir um mal grave
• Pode haver um problema latente que vai falhar em um momento crítico
• Pode acabar em morte
Na aplicação
• Não testamos para não descobrir o que falha
• Há problemas latentes que vão falhar em momentos críticos (Por exemplo: um sistema de pagamento do seu e-commerce)
• Sua empresa morre (vai a falência)
Estabeleça o que é crítico
• Quão crítico é cada parte do seu sistema?
• Se não funcionar, quais as consequências?
Impacto
Ocorrência
Baixo
Baixo Alto
Alto
Risco de baixo nível
Risco de nível médio
Risco Crítico
http://bit.ly/11tTwNGerenciamento de Risco
Automatização
• --Retrabalho
• --Surpresas
• Mais tempo para o que é interessante
• Menos tempo para testar tudo novamente
Automatização
• --Retrabalho
• --Surpresas
• Mais tempo para o que é interessante
• Menos tempo para testar tudo novamente
Metodologias Ágeis
• Aplicar testes é uma rotina contida em Metodologias Ágeis
• Testes não dependem de metodologias ágeis
• Ex.: jQuery
Funciona
Faz o que deveria
Esse código funciona!
• Um código pode funcionar perfeitamente
• Estar 100% testado
• e não cumprir 10% do seu papel
Fluxo
Especificação
Fluxo
Especificação
Plano de Testes
Fluxo
Especificação
Plano de Testes
TestesFluxo
Validação
Plano de Testes
Validação
Especificação
Plano de Testes
Validação
Especificação
Plano de Testes Testes
Validação
Especificação
Plano de Testes Testes
Aplicação
Validação
Testes não validam especificação!
Testes não criam especificação!
Testes vs Especificação
• Se os testes validassem especificação, não poderiamos prever correções de bugs e comportamentos de ambientes.
• Bugs não fazem parte de uma especificação.
Baby Steps (Testes Unitários)
Teste que Falha Código Refatorar
Sempre em Baby Steps
• Ótimo para pegar o ritmo
• função 1, 2, 3, pin
• Treino do Lutador
Coberturade
Código
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
function foo( bar ) { if ( bar ) { console.log( 'yes!' ); else { console.log( 'no!' ); }}
Código 100% cobertopor testes!
• JSCoverage
• CoverJS
Tudo é testável
• Complexidade vs Criticidade
• window.location.replace()
Testes em JavaScript
JS
Testes
Unitários
FuncionaisInterface
IntegraçãoDesempenho
Open Web
vários ambientes
Chrome
Firefox
Opera
IE 8, 9[, 10, ...]
Chrome
Firefox
Opera
IE 8, 9[, 10, ...]
Mobile
PhantomJS
Webkit
Ferramentas
QUnit
JasmineMocha
NodeUnit
Vows
Estilos de Testes
TDD BDD Exports
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
module( "Basics" );
test( "hello test", function() { ok( 1 == "1", "Passed!" );});
http://qunitjs.com/
TDD
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
describe("A suite", function() { it("spec with an expectation",
function(){ expect(true).toBe(true); });
});
BDD
Exports
module.exports = { test1: function (test) { test.equals(this.foo, 'bar'); test.done(); }};
Exports
module.exports = { test1: function (test) { test.equals(this.foo, 'bar'); test.done(); }};
Exports
module.exports = { test1: function (test) { test.equals(this.foo, 'bar'); test.done(); }};
Exports
module.exports = { test1: function (test) { test.equals(this.foo, 'bar'); test.done(); }};
Exports
module.exports = { test1: function (test) { test.equals(this.foo, 'bar'); test.done(); }};
Como começar?
HTML Estático
JavaScript
Testes
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <div id="qunit-fixtures"> <div id="foo">bar</div> </div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>QUnit Example</title> <link rel="stylesheet" href="/t/qunit.css"></head><body> <div id="qunit"></div> <div id="qunit-fixtures"> <div id="foo">bar</div> </div> <script src="/t/qunit.js"></script> <script src="/js/script.js"></script> <script src="/t/unit/tests.js"></script></body></html>
Código testado tem bom desempenho?
• Testes unitários não medem desempenho
• Existem testes de desempenho
• Código sem erros pode evitar lentidão
DexterJS
Dexter.spy
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
http://jsbin.com/osijuq/7/edit
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
test( 'carly()', 4, function() {
var spy = Dexter.spy( window, 'carly', function( arg1 ) {
equal( arg1, 'crazy!', 'me sad got crazy!' );
});
meSad( false );
equal( spy.called, 1, 'carly was called once' );
spy.callback = 0;
equal( meSad( true ), 'Call me maybe?', 'keep asking' );
equal( spy.called, 2, 'carly called again!' );
spy.restore();
});
Dexter.stub
function carly( crazy ) { if ( typeof ( crazy ) === 'number' ) { return 'Call me maybe?'; } else { return 'Hey, I just met you!, and this is ' + crazy; }}
function meSad( maybe ) { return carly( maybe ? 5555555 : 'crazy!' );}
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
test( 'stubbed carly()', 1, function() { var stub = Dexter.stub( window, 'carly', function() { return 'relax'; }); equal( meSad( true ), 'relax', 'stubbed carly!' ); stub.restore();});
Dexter.fakeXHR
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
test( 'a fakeXHR', 2, function() { var fakeXHR = Dexter.fakeXHR(); $.get( '/ajax.url', function() { ok( true, 'ajax completed' ); });
equal( fakeXHR.requests.length, 1, 'requests === 1' );
fakeXHR.respond({ body : 'this is the ajax returned text', headers : { foo2 : 'bar2' }, status : 200 });});
Começar a criar testes?
Experimente!
• Coding Dojo
• Inicialize um projeto com o GruntJS
• TDD - Kent Back
• Engenharia de Software - Roger S Pressman
• Testable JavaScript - Mark Ethan Trostler *
Referências
Obrigado!
• @garu_rj
• @blabos
• @aoqfonseca
http://search.cpan.org/~leobalter/