Desenvolvimento dirigido por testes para TV digital utilizando Lua

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:

  1. o tratador sรณ deve tratar eventos de pressionamento de tecla;
  2. o tratador sรณ estรก interessado na tecla vermelha;
  3. quando a tecla vermelha for pressionada a frase “Botao vermelho pressionado.” deve ser escrita na posiรงรฃo 200 x 200 e
  4. um evento (comย  class = ‘ncl’, type = ‘presentation’, area = ‘fim’ e action = ‘start’) deve ser postado;
  5. 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.

No momento, vocรช estรก visualizando Desenvolvimento dirigido por testes para TV digital utilizando Lua

Rafael Carvalho

Rafael Carvalho รฉ empreendedor digital hรก mais de 20 anos e desenvolveu dezenas de negรณcios na internet. ร‰ criador de diversos treinamentos online, com destaque para o mรฉtodo Lanรงamento Enxuto e a Mentoria Imparรกveis, que sรฃo considerados os melhores treinamentos para quem deseja possuir um negรณcio lucrativo, honesto e saudรกvel na internet.

Deixe seu comentรกrio:

Este post tem 2 comentรกrios

  1. Victor Davi Almeida

    Olรƒยก, muito bom o conteรƒยบdo, entrei na รƒยกrea recentemente, acredito que em breve retornarei aqui pra mais consultas.

    1. Rafael Carvalho

      Valeu pelo feedback Victor. Desejo muito sucesso na sua nova jornada e espero conseguir continuar ajudando.
      Grande abraรƒยงo.