Front End

21 mai, 2013

Escrevendo um javascript testável: os primeiros passos no Jasmine

Publicidade

Código de qualidade

Um dos passos para se escrever um código de qualidade é escrever um código testável, e então os testes dele.

Ter rotinas automáticas que executem testes em nossos scripts também vai nos ajudar a refatorar certos trechos, com a garantia de que não quebramos nenhum comportamento.

Código confiável

Os testes devem ser capazes de reproduzir todas as entradas e saídas esperadas. Assim, cada vez que você mexer nele, não precisa se preocupar em testar manualmente todos os comportamentos. Pois você pode esquecer de alguns, testar de forma errada outros… Enfim, não é confiável.

Estamos acostumados a programar assim:

Código javascript não testável

Esse código acima, formata um input em javascript com uma máscara assim: 000,0000. Ao preencher o primeiro número, o script formata para 0,0001 e assim por diante… Apagando os demais zeros.

Só que esse código não é testável, ou seja, escrever uma rotina que “use”, dando uma entrada e comparando se foi igual a uma saída esperada, não é algo trivial. Primeiro passo, é reescrever para conseguirmos chamar assim:

formatWeight('123'); //0,0123

Evita duplicação de código

Note que não é preciso muito:

<script type="text/javascript">
function id(el){
    return document.getElementById(el);
}
function formatWeight(v){
	var integer = v.split(',')[0];

	v = v.replace(/\D/, "");
	v = v.replace(/^[0]+/, "");

	if(v.length <= 4 || !integer)
	{
		if(v.length === 1) v = '0,000' + v;
		if(v.length === 2) v = '0,00' + v;
		if(v.length === 3) v = '0,0' + v;
		if(v.length === 4) v = '0,' + v;

	} else {
		v = v.replace(/^(\d{1,})(\d{4})$/, "$1,$2");
	}

	return v;
}
window.onload = function(){
	id('peso').onkeyup = function(){
		this.value = formatWeight(this.value);
	}
};
</script>
<input type="text" name="peso" id="peso" maxlength="8" />

E agora eu tenho um código isolado. E se eu precisar desse mesmo comportamento em outro input, posso apenas invocar:

	id('peso').onkeyup = function(){
		this.value = formatWeight(this.value);
	}
	id('peso2').onkeyup = function(){
		this.value = formatWeight(this.value);
	}

Evitando assim duplicar código.

Esse foi o primeiro ganho em ter me preocupado em escrever um código testável.

Verificando a saída manualmente

Ainda não vou dizer como fazer os testes com o Jasmine, pois precisamos saber o que é um teste. O script que propus aqui é super simples. São poucas possibilidades de entrada, e cada entrada gera um tipo de saída.

Fazendo na mão, eu escrevi todas as entradas possíveis, e olho na tela se o retorno foi o que eu queria.

window.onload = function(){
	var $resultado = id('resultado'),
		p = '';

	p = formatWeight('1') + '<br/>';
	p += formatWeight('12') + '<br/>';
	p += formatWeight('123') + '<br/>';
	p += formatWeight('1234') + '<br/>';
	p += formatWeight('12345') + '<br/>';
	p += formatWeight('123456') + '<br/>';
	p += formatWeight('1234567') + '<br/>';

	$resultado.innerHTML = p;
};
</script>

<p id="resultado"></p>

Isso já é “melhor” do que nada, e não preciso digitar mais sete entradas diferentes no input, para ver se o resultado foi o certo. Testes durante o desenvolvimento são necessários. E fazer algo assim:

	id('resultado').innerHTML += 'Entrou: 1234567 e saiu: ' 
		+ formatWeight('1234567') + ', ' 
		+ (formatWeight('1234567') == '123,4567') + '<br/>';

É chato e pouco produtivo.

Jasmine

jasmine_logo

1. Baixe o jasmine standalone;

2. Copie a pasta lib e o arquivo SpecRunner.html, pode colar eles dentro de um diretório tests, do teu projeto, ou algo assim;

3. Edite as seguintes linhas do SpecRunner:

  <!-- include source files here... -->
  <script type="text/javascript" src="src/Player.js"></script>
  <script type="text/javascript" src="src/Song.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/SpecHelper.js"></script>
  <script type="text/javascript" src="spec/PlayerSpec.js"></script>

No meu caso ficaram assim:

  <!-- include source files here... -->
  <script type="text/javascript" src="formatWeight.js"></script>

  <!-- include spec files here... -->
  <script type="text/javascript" src="spec/formatWeightSpec.js"></script>

O arquivo formatWeigth.js, contém apenas a função formatWeight(), e a função id(). A chamada do window.onload, está lá no meu projeto. E o arquivo formatWeigtSpec.js é o teste em si. A entrada e a saída esperada.

4. Vamos ver o formatWeigtSpec.js:

describe("formatWeight", function() {

	it("should be equal", function() {
		expect(formatWeight('1')).toEqual('0,0001');
	});

});

5. Pronto! Testes feitos. Podemos até refatorar o código, com a certeza de que nenhum comportamento vai ser quebrado e nem precisar ficar digitando no input toda hora para ver se o script faz o que deveria.

Meu código final de testes ficou assim:

describe("formatWeight", function() {

	var arrIn = [
		'1', '12', '123', '1234', '12345', '123456', '1234567', 'abc', 'a1b2c3'
	],
	arrOut = [
		'0,0001', '0,0012', '0,0123', '0,1234', '1,2345', '12,3456', '123,4567', '0,0000', '0,0123'
	],
	i,
	arrInLength = arrIn.length;

	for(i = 0; i<arrInLength; i++) {

		(function(entry, output_expected){

			it( entry + " should be equal " + output_expected, function() {
				expect(formatWeight(entry)).toEqual(output_expected);
			});

		}(arrIn[i], arrOut[i]));
	}

});

Com segurança, posso refatorar a parte do código que me incomodava.

Chegando assim, ao meu código final:

function id(el){
    return document.getElementById(el);
}
function formatWeight(v){
	var integer = v.split(',')[0],
		zeroFill = 0,
		c = '';

	v = v.replace(/\D/g, "");
	v = v.replace(/^[0]+/, "");

	if(v.length <= 4 || !integer)
	{
		zeroFill = 4 - v.length;
		while(zeroFill--) {
			c += '0';
		}
		v = '0,' + c + v;

	} else {
		v = v.replace(/^(\d{1,})(\d{4})$/, "$1,$2");
	}

	return v;
}

Agora vc é capaz de usar o Jasmine como framework para testes. Comente com a sua experiência. Ficou em dúvida? Quer ver como ficou tudo junto? Baixe no meu git: https://github.com/wbruno/formatWeight