Sumรกrio
O Desenvolvimento Dirigido por Testes ou em inglรชs Test Driven Development (TDD) รฉ uma tรฉcnica de desenvolvimento de software muito utilizada em projetos que adotam metodologias รกgeis, como SCRUM e XP, para gerenciamento.
Em TV digital o desenvolvimento tambรฉm pode, e deve, ser รกgil. Por isso decidi escrever este tutorial abordando uma soluรงรฃo para a realizaรงรฃo de testes unitรกrios em projetos de TV digital.
Neste tutorial apresentarei uma ferramente para a realizaรงรฃo de testes unitรกrios em cรณdigo Lua e desenvolverei uma soluรงรฃo para possibilitar o uso da mesma ferramenta em projetos de TV digital.
Nosso ambiente de trabalho serรก composto pelo cรณdigo a ser testado, os testes e uma ferramenta para execuรงรฃo dos testes e apresentaรงรฃo dos resultados. Vale mencionar que focarei em cรณdigo Lua porque nรฃo encontrei, e acredito que ainda nรฃo foi lanรงada, uma ferramenta para a execuรงรฃo de testes em cรณdigo NCL. Seria bem interessante utilizarmosย Desenvolvimento Dirigidos por Comportamento ou em inglรชs Behavior Driven Development (BDD) para projetos em NCL.
Ferramenta para execuรงรฃo dos testes
Existem vรกrias opรงรตes de ferramentas para testes unitรกrios em Lua. Nรณs utilizaremos o Telescope pelo fato dele possuir funcionalidades interessantes e uma API bem documentada.
Para instalar o Telescope, com o Luarocks previamente instalado, basta digitar:
sudo luarocks build telescope --from=http://luarocks.luaforge.net/rocks-cvs |
Tambรฉm รฉ possรญvel obter o cรณdigo fonte a partir do repositรณrio git:
git clone git://github.com/norman/telescope.git |
Apรณs a instalaรงรฃo podemos chamar o Telescope com o comando tsc. O help รฉ bem explicativo e pode ser chamado com o comando abaixo:
tsc --help |
Criando os primeiros testes
Neste primeiro exemplo criaremos um รบnico arquivo com as funรงรตes de soma e subtraรงรฃo e os seus testes. O cรณdigo pode ser observado abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | -- Funcoes que desejamos testar function soma(a,b) return a+b end function sub(a,b) return a-b end -- Testes para a funcao soma context("Funcao soma(a,b)", function() test("Teste 1 + 1 = 2", function() assert_equal(soma(1,1), 2) end) test("Teste 1 + 's' = 0", function() assert_equal(soma(1,'s'),0) end) end) -- Testes para a funcao sub context("Funcao sub(a,b)", function() test("Teste 1 - 1 = 0", function() assert_equal(sub(1,1),0) end) test("Teste 2 - 1 = 1", function() assert_equal(sub(2,1),1) end) end) |
Atรฉ a linha 9 nรฃo temos novidades. Na linha 11 iniciamos a declaraรงรฃo do contexto onde iremos organizar os testes para a funรงรฃo soma.
Nesse momento vale abrir uma parรชnteses e falar um pouco sobre os contextos. Como disse acima, o Telescope apresenta funcionalidades interessantes e uma delas รฉ a possibilidade de organizarmos os testes em contextos, que ainda podem ser aninhados. No exemplo nรฃo utilizamos contextos aninhados porque os testes sรฃo bem simples, porรฉm no cรณdigo abaixo podemos ver o uso de contextos em outro exemplo com uma complexidade um pouco maior:
-- tests context("Testing servers/api.lua", function() -------- context("Teste de conexao", function() local s,d,h,c = client.get(string.format("http://%s:%d", host, port), nil, auth) ----- if not s then test("Sem conexao", function() assert_false(s) assert_equal(c, "connection refused") assert_blank(d) assert_nil(h) end) end ----- if s then test("Com conexao", function() assert_true(s) assert_type(c, "number") --deve retornar um codigo assert_type(h, "table") -- deve retornar uma tabela com o cabecalho end) end ----- end) ---------- context("Teste de recursos", function() --------- test("Recurso INVALIDO", function() local s,d,h,c = client.get(string.format("http://%s:%d/%s", host, port,"xyz"), nil, auth) assert_true(s) assert_type(c, "number") --deve retornar um codigo assert_equal(c, 404) end) -------- test("Recurso PING disponivel", function() local s,d,h,c = client.get(string.format("http://%s:%d/%s", host, port,"ping"), nil, auth) assert_true(s) assert_type(c, "number") --deve retornar um codigo assert_not_equal(c, 404) end) -------- test("Recurso SEARCH disponivel", function() local s,d,h,c = client.get(string.format("http://%s:%d/%s", host, port,"search"), nil, auth) assert_true(s) assert_type(c, "number") --deve retornar um codigo assert_not_equal(c, 404) end) -------- end) ---------- end) |
Esse cรณdigo รฉ parte de um arquivo de testes de um um servidor Lua que implementa uma API e oferece alguns recursos. Como pode-se notar, os contextos mostram-se bem รบteis para a organizaรงรฃo dos testes de um projeto quando comeรงamos a trabalhar em algo mais elaborado.
Voltando ao nosso primeiro exemplo, a sintaxe bรกsica, a partir da linhaย 11, para construirmos nossos testes รฉ:
context("Nome do contexto", function() test("Nome do teste", function() -- Assertions end) --Mais testes end) |
O Telescope oferece por padrรฃo as assertions mais bรกsicas (veja a API). Porรฉm, se desejar, vocรช tambรฉm pode construir as suas.
Para executarmos nosso teste fazemos:
tsc nome-do-arquivo.lua |
A saรญda serรก algo como:
1 2 3 4 5 6 7 8 9 | 4 tests 3 passed 3 assertions 0 failed 1 error 0 unassertive 0 pending Teste 1 + 's' = 0: teste1.lua:4: attempt to perform arithmetic on local 'b' (a string value) stack traceback: /usr/local/share/lua/5.1//telescope.lua:374: in function 'invoke_test' /usr/local/share/lua/5.1//telescope.lua:399: in function 'run' ...usr/local/lib/luarocks/rocks/telescope/scm-1/bin/tsc:266: in main chunk [C]: ? |
Na lina 1 temos o resumo dos testes, na linha 3 o teste que falhou e na linha 4 o motivo da falha. A partir da linha 5 temos a saรญda do interpretador Lua.
Podemos melhorar nossa saรญda com o parรขmetro -f:
tsc -f nome-do-arquivo.lua |
Obtendo com isso uma saรญda mais detalhada, onde o Telescope apresenta um resumo mais elaborado dos testes realizados (linhas 1 a 9):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ------------------------------------------------------------------------ Funcao soma(a,b): Teste 1 + 1 = 2 [P] Teste 1 + 's' = 0 [E] ------------------------------------------------------------------------ Funcao sub(a,b): Teste 1 - 1 = 0 [P] Teste 2 - 1 = 1 [P] ------------------------------------------------------------------------ 4 tests 3 passed 3 assertions 0 failed 1 error 0 unassertive 0 pending Teste 1 + 's' = 0: teste1.lua:4: attempt to perform arithmetic on local 'b' (a string value) stack traceback: /usr/local/share/lua/5.1//telescope.lua:374: in function 'invoke_test' /usr/local/share/lua/5.1//telescope.lua:399: in function 'run' ...usr/local/lib/luarocks/rocks/telescope/scm-1/bin/tsc:266: in main chunk [C]: ? |
Rebuscando um pouco mais
No exemplo anterior criamos as funรงรตes e os testes no mesmo arquivo. Porรฉm, na prรกtica, isso nรฃo funciona muito bem e nem deve ser feito.
Vamos separar nossos arquivos encapsulando as funรงรตes em um mรณdulo. Assim podemos chamar o mรณdulo diretamente do nosso arquivo de testes.
Nosso mรณdulo de operaรงรตes matemรกticas ficarรก assim:
-- Modulo com operacoes matematicas module("matematica") -- operacao soma function soma(a,b) return a+b end -- operacao subtracao function sub(a,b) return a-b end |
E agora os testes em outro arquivo:
-- Chama o modulo matematica require"matematica" -- Testes context("Funcao soma(a,b)", function() test("Teste 1 + 1 = 2", function() assert_equal(matematica.soma(1,1), 2) end) test("Teste 1 + 's' = 0", function() assert_equal(matematica.soma(1,'s'),0) end) end) context("Funcao sub(a,b)", function() test("Teste 1 - 1 = 0", function() assert_equal(matematica.sub(1,1),0) end) test("Teste 2 - 1 = 1", function() assert_equal(matematica.sub(2,1),1) end) end) |
Utilizando testes nos projetos de TV digital
Agora que jรก vimos como construir nossos testes iremos aplicar nosso conhecimento num exemplo que envolve desenvolvimento para TV digital.
Tomemos uma aplicaรงรฃo muito simples: “Quando o telespectador pressiona o botรฃo vermelho uma mensagem รฉ desenhada na tela e uma รขncora รฉ iniciada”.
Neste exemplo teremos um nรณ Lua com um tratador de eventos que sรณ estรก interessado em eventos de pressionamento de tecla, mais especificamente sรณ da tecla vermelha. Por nรฃo ser o objetivo deste tutorial, nรฃo entrarei em detalhes do cรณdigo NCL.
Nosso tratador de eventos pode ser implementado assim:
function handler (evt) if (evt.class == 'key') and (evt.type == 'press') and (evt.key == 'RED') then canvas:attrColor('white') canvas:attrFont('vera',30) canvas:drawText (200, 200, "Botao vermelho pressionado." ) event.post { class = 'ncl', type = 'presentation', area = 'fim', action = 'start', } end end event.register(handler) |
Iremos elaborar alguns testes para garantir a consistรชncia na implementaรงรฃo do tratador de eventos. Para exemplificar, vamos definir algumas condiรงรตes de contorno e posteriormente construiremos os testes para elas. Vale a ressalva de que esse procedimento nรฃo estรก 100% de acordo com os “mandamentos” (“…inicialmente o desenvolvedor escreve um teste e depois escreve o cรณdigo que possa ser validado pelo teste…”) do TDD, porรฉm como o objetivo aqui nรฃo รฉ ser doutrinador-xiita, vamos focar na ferramenta e na soluรงรฃo; os mais rigorosos podem comeรงar pelos testes sem problemas :-).
Nossas condiรงรตes de contorno sรฃo:
- o tratador sรณ deve tratar eventos de pressionamento de tecla;
- o tratador sรณ estรก interessado na tecla vermelha;
- quando a tecla vermelha for pressionada a frase “Botao vermelho pressionado.” deve ser escrita na posiรงรฃo 200 x 200 e
- um evento (comย class = ‘ncl’, type = ‘presentation’, area = ‘fim’ e action = ‘start’) deve ser postado;
- o tratador de eventos deve ser registrado.
Com nossas condiรงรตes de contorno definidas podemos continuar. Porรฉm o cรณdigo do tratador de eventos nรฃo irรก funcionar fora do middleware. Ao executรก-lo recebemos o erro:
lua: exemplo.lua:16: attempt to index global 'event' (a nil value) stack traceback: tratador.lua:16: in main chunk [C]: ? |
Isso acontece porque algumas bibliotecas utilizadas nรฃo sรฃo padrรตes do Lua e sรณ estรฃo presentes na implementaรงรฃo do middleware. Para contornarmos essa situaรงรฃo, iremos realizar algumas configuraรงรตes adicionais no arquivo de testes e assim garantir que eles funcionem fora do ambiente do middleware.
Logo no inรญcio do nosso arquivo de testes criaremos as configuraรงรตes para simularmos os mรณdulos utilizados pelo tratador de eventos.
O arquivo completo pode ser visto abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | ------- -- Tabelas representando os valores do sistema ------- system_modified = false -- Tela canvas_values = { color = '', font = '', font_size = 0, text_x = 0, text_y = 0, text_message = '', } -- Eventos events ={ posted = {}, event_registered = '' } ------- -- Configuracao modulo event ------- event = {} _G["event"] = event -- Se recebe um tratador (funcao) registra e retorna true function event.register(handler) if type(handler) == 'function' then events.event_registered = handler return true else return false end end -- Posta os eventos function event.post(tab) if type(tab) == 'table' then events.posted.class = tab.class events.posted.type = tab.type events.posted.area = tab.area events.posted.action = tab.action system_modified = true return true else return false end end ------- -- Configuracao modulo canvas ------- canvas = {} _G["canvas"] = canvas function canvas:attrColor(color) canvas_values.color = color system_modified = true end function canvas:attrFont(font,size) canvas_values.font = font canvas_values.font_size = size system_modified = true end function canvas:drawText(x,y,message) canvas_values.text_x = x canvas_values.text_y = y canvas_values.text_message = message system_modified = true end -------- -- Carrega o tratador de eventos dofile"main.lua" -------- -------- -- Inicio dos Testes -------- context("Exemplo de testes para tratador de eventos", function() test("Teste 1: Tratador NAO trata outro tipo de evento", function() --configuracao do evento local evt = { class = "ncl", type = "presentation", action = "start" } system_modified = false handler(evt) assert_false(system_modified) end) ---------------- test("Teste 2: So trata eventos quando a tecla vermelha (RED) for pressionada", function() --configuracao do evento local evt = { class = "key", type = "press", key = "RED" } system_modified = false handler(evt) assert_true(system_modified) end) ---------------- test("Teste 3: Frase: 'Botao vermelho pressionado.' eh exibida", function() --configuracao do evento local evt = { class = "key", type = "press", key = "RED" } handler(evt) assert_equal(canvas_values.color,"white") assert_equal(canvas_values.font,"vera") assert_equal(canvas_values.font_size,30) assert_equal(canvas_values.text_x,200) assert_equal(canvas_values.text_y,200) assert_equal(canvas_values.text_message,"Botao vermelho pressionado.") end) ------------------ test("Teste 4: Tratador posta evento", function() --configuracao do evento local evt = { class = "key", type = "press", key = "RED" } handler(evt) assert_equal(events.posted.class,"ncl") assert_equal(events.posted.type,"presentation") assert_equal(events.posted.area,"fim") assert_equal(events.posted.action,"start") end) ------------------ test("Teste 5: Tratador de eventos esta registrado", function() assert_equal(events.event_registered,handler) end) end) |
Atรฉ a linha 61 temos as configuraรงรตes para os testes. O que fiz foi criar algumas tabelas que representam o estado do sistema e funรงรตes que modificam esse estado. Neste caso as funรงรตes tem o mesmo nome das que estรฃo nas bibliotecas para TV digital. Na linha 65 o arquivo com o tratador de eventos รฉ carregado e da linha 70 atรฉ o final os testes sรฃo realizados.
Desta forma, quando o tratador de eventos รฉ chamado (linha 81 por exemplo) o interpretador Lua chamarรก as funรงรตes que foram criadas no inรญcio do arquivo. Essas funรงรตes modificam o estado das tabelas que representam o sistema, assim podemos verificar se o tratador conseguiu realizar o que desejรกvamos apenas checando o valor dessas tabelas.
Nenhuma modificaรงรฃo foi realizada no cรณdigo do tratador de eventos de forma que ele funcionarรก normalmente quando for executado pelo middleware.
Evite repetiรงรฃo de cรณdigo
Para testes em um รบnico tratador de eventos que estรก em um รบnico arquivo esse exemplo atende nossas necessidades. Contudo se tivermos vรกrios arquivos separados com vรกrios tratadores de eventos comeรงamos perceber que copiar um cabeรงalho de configuraรงรฃo para cada aquivo pode ser algo, alรฉm de chato, que certamente causarรก problemas para manutenรงรฃo.
Para solucionar esse problema poderรญamos (รฉ, nรฃo vamos estender esse tutorial ainda mais) criar um รบnico mรณdulo com as configuraรงรตes necessรกrias e importรก-lo em cada arquivo de testes. Fica a dica.
Conclusรฃo
Nesse tutorial apresentei uma ferramenta para a realizaรงรฃo de testes em cรณdigo Lua e desenvolvemos uma metodologia para utilizaรงรฃo destes testes nos projetos de TV digital. Espero que essa informaรงรฃo possa ser รบtil nos seus projetos.
Utilize os comentรกrios para enriquecermos ainda mais as informaรงรตes que foram apresentadas. Portanto se encontrou algum erro, tem alguma dรบvida ou mesmo qualquer comentรกrio, nรฃo deixe de se expressar.
Olรยก, muito bom o conteรยบdo, entrei na รยกrea recentemente, acredito que em breve retornarei aqui pra mais consultas.
Valeu pelo feedback Victor. Desejo muito sucesso na sua nova jornada e espero conseguir continuar ajudando.
Grande abraรยงo.