UNIVERSIDADE VEIGA DE ALMEIDA
BACHARELADO EM SISTEMAS DE INFORMAÇÃO
INTRODUÇÃO A TESTES AUTOMATIZADOS
EM RUBY ON RAILS
Thiago Cifani Ayres Escola
Cabo Frio
2011
THIAGO CIFANI AYRES ESCOLA
INTRODUÇÃO A TESTES AUTOMATIZADOS
EM RUBY ON RAILS
Trabalho desenvolvido durante a disciplina Monografia e
apresentada ao Curso de Sistemas de Informação da
Universidade Veiga de Almeida, campus Cabo Frio, como prérequisito para a obtenção do título de Bacharel em Sistemas de
Informação.
Orientador: Prof. Matheus Bousquet Bandini, M.Sc.
Campus Cabo Frio
2011
Universidade Veiga de Almeida - UVA
Faculdade de Informática
Curso de Bacharelado em Sistemas de Informação
Reitor:
Prof. Dr. Mário Veiga de Almeida Jr.
Vice-Reitor:
Prof. Tarquínio Prisco Lemos da Silva
Coordenador do Curso de Informática:
Prof. Edgar
Banca Examinadora:
__________________________________
Prof. Matheus Bousquet Bandini (Orientador)
M.Sc. em Sistemas e Computação – IME/RJ
__________________________________
Prof. Douglas Ericson Marcelino de Oliveira
M.Sc. em Sistemas e Computação – IME/RJ
__________________________________
Prof. Rodrigo Lessa de Sena
Especialista em Computação Gráfica - UVA
Universidade Veiga de Almeida – Cabo Frio
Estrada das Perynas, s/n
CEP: 28901-970 - Cabo Frio – RJ
Dedico este trabalho a Arnaldo, Rose, Priscilla e Elaine.
AGRADECIMENTOS
Agradeço a meus pais, família, amigos e professores. Agradeço todas estas pessoas que se
fizeram presentes e ocuparam com excelência o devido papel em minha vida. Agradeço pelo
carinho, respeito, dedicação e incentivo para continuar mesmo com todos os atenuantes da vida.
LISTA DE FIGURAS
Figura 3.1 – Modelo MVC Rails ............................................................................................. 23
Figura 3.2 – Código Rails para Localizar Cliente por ID ........................................................ 24
Figura 3.3 – Exemplo de uso do Active Record ...................................................................... 25
Figura 4.1 – Exemplo de Estrutura de Teste Unitário.............................................................. 29
Figura 4.2 – Teste Unitário com Asserção ............................................................................... 29
Figura 4.3 – Teste Unitário com Assert Equals ....................................................................... 30
Figura 4.4 – Teste Unitário com Assert Not Equal .................................................................. 30
Figura 4.5 – Exemplo de Repetição de Código em Teste Unitário.......................................... 31
Figura 4.6 – Exemplo de Método Setup .................................................................................. 32
Figura 4.7 – Ciclo do TDD ...................................................................................................... 36
Figura 4.8 – Exemplo de Describe com RSpec ....................................................................... 39
Figura 4.9 – Exemplo de Describe com Context e IT.............................................................. 39
Figura 4.10 Exemplo de Saída Formatada do RSpec............................................................... 39
Figura 4.11 – Exemplo de Teste RSpec Pendente ................................................................... 40
Figura 4.12 – Saída RSpec Teste Pendente.............................................................................. 40
Figura 4.13 – Teste RSpec pendente com mensagem personalizada ....................................... 41
Figura 4.14 – Teste RSpec com It para descrever comportamento ......................................... 41
Figura 4.15 – Teste RSpec Let e Specify ................................................................................. 42
Figura 4.16 – Teste Exemplo cenário com cucumber .............................................................. 43
Figura 5.1 – GemFile para Testes ............................................................................................ 47
Figura 5.2 – Comandos Instalação ........................................................................................... 48
Figura 5.3 – Team Feature ....................................................................................................... 48
Figura 5.4 – Team Steps Pendentes ......................................................................................... 49
Figura 5.5 – Team Steps .......................................................................................................... 49
Figura 5.6 – Controlador Team ................................................................................................ 50
Figura 5.7 – team_spec.rb ........................................................................................................ 50
Figura 5.8 – Team model ......................................................................................................... 51
LISTA DE ABREVIATURAS E SIGLAS
API
Application Programming Interface
BDD
Behavior Driven Development
CoC
Convention Over Configuration
DRY
Don't Repeat Yourself
DSL
Domain Specific Language
ERB
Embedded Ruby
HTML
Hyper Text Markup Language
IDE
Integrated Development Environment
JVM
Java Virtual Machine
MRI
Matz's Ruby Interpreter
MVC
Model View Controller
RoR
Ruby on Rails
SAAS
Software as a Service
SSH
Secure Shell
SVN
Subversion
TDD
Test Driven Development
URL
Uniform Resource Locator
VPS
Virtual Private Server
YAML
YAML Ain’t Markup Language
YARV
Yet another Ruby VM
XP
Extreme Programming
RESUMO
Este trabalho apresenta as formas de desenvolvimento de software baseado em testes
automatizados em Ruby on Rails como tecnologia para desenvolvimento de aplicações para a
Internet. São demonstrados suas principais características e componentes. Também são exibidos
diversos frameworks que facilitam a elaboração de testes e gems relacionadas. Por fim, uma
pequena suíte de testes é desenvolvida, de forma a exemplificar a utilização dos recursos
apresentados. Através da análise das características da ferramenta e dos resultados obtidos no
estudo de caso é respondida a razão do Ruby on Rails ser uma plataforma que cumpre com sucesso
sua proposta de simplicidade e agilidade.
ABSTRACT
This paper presents the ways of developing software-based automated tests in Ruby on Rails
as the technology to develop applications for the Internet. Showing us the main characteristics and
components. Also displayed are several frameworks that facilitate the development of tests and
related gems. Finally, a small tests suite is developed in order to illustrate the use of appeals. By
analyzing the characteristics of the tool and the results obtained in the study case, is answered the
reason for Ruby on Rails to be a platform that successfully fulfills its proposal of simplicity and
agility.
SUMÁRIO
1
2
3
INTRODUÇÃO ................................................................................................... 12
1.1
CONTEXTUALIZAÇÃO ................................................................................... 12
1.2
PROBLEMA ................................................................................................... 12
1.3
HIPÓTESE ..................................................................................................... 12
1.4
OBJETIVO ..................................................................................................... 13
1.5
JUSTIFICATIVA ............................................................................................. 13
1.6
METODOLOGIA DE PESQUISA ....................................................................... 13
1.7
ORGANIZAÇÃO DO TEXTO ............................................................................ 13
HISTÓRICO ........................................................................................................ 15
2.1
O COMPUTADOR .......................................................................................... 15
2.2
LINGUAGENS DE PROGRAMAÇÃO ................................................................. 15
2.3
MANIFESTO ÁGIL ......................................................................................... 16
TECNOLOGIAS ENVOLVIDAS ....................................................................... 19
3.1
RUBY ............................................................................................................ 19
3.1.1
RubyGems ................................................................................................ 21
3.1.2
RVM ........................................................................................................ 21
3.2
RAILS ............................................................................................................ 21
3.2.1
Padrão MVC ........................................................................................... 22
3.2.2
Suporte a Modelo no Rails ....................................................................... 24
3.2.2.1 Mapeamento Objeto Relacional ................................................... 24
3.2.2.2 Active Record .............................................................................. 25
3.2.3
Action Pack: Visualização e Controlador ............................................... 25
3.2.3.1 Suporte a Visualização ................................................................. 26
3.2.3.2 Controlador ................................................................................. 26
4
TESTES AUTOMATIZADOS ............................................................................ 27
4.1
TESTES EM RUBY ON RAILS .......................................................................... 28
4.1.1 Setup e TearDown ....................................................................................... 31
4.1.2 O que pode ser testado em Rails ................................................................. 32
4.1.3 Fixtures ....................................................................................................... 34
4.2
TEST DRIVEN DEVELOPMENT......................................................................... 34
4.3
BEHAVIOR DRIVEN DEVELOPMENT ................................................................ 36
4.3.1 Os Princípios do BDD ................................................................................. 37
4.4
RSPEC ........................................................................................................... 38
4.4.1 Descrevendo Comportamentos ................................................................... 38
4.4.2 Exemplos Pendentes.................................................................................... 40
4.4.3 Before, After, Specify, Let .......................................................................... 41
4.5
5
CUCUMBER ................................................................................................... 42
ESTUDO DE CASO ............................................................................................ 45
5.1
AMBIENTAÇÃO .............................................................................................. 45
5.2
PROERD ......................................................................................................... 45
5.2.1 Origem ......................................................................................................... 45
5.2.2 Propósito...................................................................................................... 46
5.3
OBJETIVO ...................................................................................................... 46
5.4
IMPLEMENTAÇÃO .......................................................................................... 47
5.4.1 Ferramentas ................................................................................................. 47
5.4.2 Testes com Rspec e Cucumber.................................................................... 47
6
CONSIDERAÇÕES FINAIS ............................................................................... 52
6.1
CONCLUSÕES ................................................................................................ 53
ANEXO A – CÓDIGO FONTE TESTES .......................................................... 54
REFERÊNCIAS BIBLIOGRÁFICAS ................................................................ 65
12
1.
1.1
INTRODUÇÃO
Contextualização
Atualmente o desenvolvimento de software tem crescido em larga escala no Brasil e no
mundo. Com a popularização da internet e dos smartsfones, desenvolver software tornou-se uma
forma de adquirir conhecimento e renda. Mas com o grande aumento de profissionais na área,
observou-se a necessidade de elaborar padrões de especificação para o desenvolvimento desses
aplicativos. Para facilitar o trabalho das equipes na elaboração e implementação do projeto, são
tomadas boas práticas para desenvolver software funcional e legível, uma das mais importantes
práticas adotadas para essa facilitação são os testes automatizados.
1.2
Problema
A grande dificuldade que as empresas de software enfrentam nos dias de hoje é a
manutenção do sistema em produção. Sabe-se que necessidades surgem a todo instante, e que para
o desenvolvedor, lembrar do código criado a algum tempo não é uma tarefa fácil. O problema dessa
deficiência torna a manutenção do código quase inviável. O rodízio de profissionais em equipes
também torna a padronização do código algo de difícil administração. Devido a essas dificuldades,
foram criados frameworks para facilitar o processo de criação e manutenção do código fonte,
otimizando todo o processo e garantido o retorno esperado do mesmo.
1.3
Hipótese
O ambiente de testes automatizados em Ruby On Rails, foco do trabalho, é um conjunto de
ferramentas integradas que visam tornar o desenvolvimento de aplicações web simples e rápido.
Esse ambiente visa cobrir o software com testes que garantem que determinada funcionalidade foi
implementada com êxito e executa sua função da melhor forma esperada. Os testes automatizados
também visão auxiliar o desenvolvimento através de redução na complexidade do código, o que
facilitaria a manutenção futura. Através dos testes, pode ser feita uma leitura de cada método do
sistema em questão e entender seu comportamento por completo. A maioria dos testes auxilia o
programador a entender a regra de negócio e programá-la da melhor maneira possível.
13
1.4
Objetivo
O objetivo desta pesquisa é apresentar o ambiente de Testes Automatizados em Ruby on
Rails. Seus benefícios e usos serão explicados ao decorrer do trabalho.
Será respondido ao longo deste trabalho o que é teste automatizado, TDD e BDD. Serão
explicadas as suas principais características, componentes e seu funcionamento. Ao final, será
apresentado um projeto construído utilizando Ruby on Rails de forma a validar a proposta dos testes
automatizados.
1.5
Justificativa
Ao adotar o padrão ágil de desenvolvimento que prioriza um produto de qualidade e um
total contato com o cliente, o desenvolvedor se depara com um cenário onde existem mudanças
constantes. Essas mudanças podem prejudicar a qualidade do código exponencialmente se não
trabalhada de uma forma a facilitar seu entendimento. Com testes automatizados, qualquer
desenvolvedor que entenda teste poderá determinar perfeitamente o comportamento da aplicação e
de seus métodos. Com eles também é possível afirmar que quando uma funcionalidade é terminada
realmente está terminada. Os testes facilitam a escrita do código, facilita a interação da equipe e
torna o sistema mais resistente a falhas e bugs.
1.6
Metodologia de Pesquisa
Foram utilizados livros especializados no assunto como fontes de pesquisa, documentos
produzidos pela comunidade que desenvolve a plataforma Ruby On Rails, além de sites na internet.
1.7
Organização do Texto
O texto está organizado em 6 capítulos. O Capítulo 2 mostra a evolução das tecnologias que
culminaram com o surgimento de aplicações na Internet, linguagens de programação e o Manifesto
Ágil. O Capítulo 3 apresenta a linguagem de programação Ruby, suas origens e ideais. Introduzirá o
framework Ruby On Rails seus componentes e principais características. O Capítulo 4 apresenta
14
testes automatizados em Ruby On Rails, TDD, BDD e o uso de dois frameworks muito utilizados
na comunidade para determinar o comportamento da aplicação e fazer teste de integração. O
capítulo 5 procura demonstrar a utilização dos conceitos apresentados através do desenvolvimento
de um sistema baseado na plataforma Ruby On Rails. As considerações finais sobre o projeto de
estudo deste texto se encontram no Capítulo 6.
15
2. HISTÓRICO
2.1 O Computador
Pode-se considerar o computador como uma das grandes descobertas da humanidade. Temse hoje o computador como uma ferramenta essencial no dia a dia de muitas empresas e indústrias.
Seu principal papel é o processamento de dados, sabe-se que os dados que são inseridos passam por
um processamento e armazenagem, o que permite a análise e compreensão de uma larga escala de
informação em poucos segundos.
Na década de 40, os computadores eram gigantescos e ocupavam grande espaço físico, eram
utilizados principalmente para pesquisas científicas e operações militares. Os computadores dessa
década tinham programas específicos para cada área de atuação e eram usados somente por
especialistas.
Com o passar das décadas e a grande quantidade de informação a serem processadas, as
empresas foram motivadas a expandir o uso dos computadores. Com isso, passaram a fabricar
computadores com tamanhos reduzidos e com um poder de processamento maior. Existiam
terminais de acesso a grandes mainframes, estes, gerenciavam todos os aplicativos e efetuavam o
processamento dos dados.
Devido à popularização dos computadores nas empresas, notou-se uma grande possibilidade
de expandir o mesmo para a utilização doméstica, o que proporcionou um maior contato do usuário
leigo e o surgimento de programas personalizados para atender diversas necessidades que surgiriam
com o tempo.
Atualmente, com o crescimento da demanda por computadores e dispositivos portáteis de
acesso a internet e a aplicativos, os smartfones e tecnologias móveis tem tomado uma grande fatia
do mercado tecnológico para utilização e resolução de problemas rotineiros.
2.2 Linguagem de Programação
Uma linguagem de programação é um conjunto de instruções que são lidas por uma
máquina para efetuar algum processamento, ou seja, este conjunto de instruções forma o programa
de computador. A linguagem permite que o programador controle o comportamento do programa e
as decisões do computador de acordo com uma série de blocos de códigos e estruturas que
permitem a tomada de decisões pela máquina.
16
O conjunto de instruções que são digitadas no computador compõem o que é chamado de
código fonte de um software. O que será compilado e transformado em linguagem de máquina para
que o processador venha a entender.
A principal motivação para utilizar uma linguagem de programação para a elaboração de um
software é a produtividade. A maioria das linguagens de programação eleva o nível da programação
utilizando sintaxe e conceitos mais próximos da linguagem humana.
As primeiras linguagens de programação surgiram na década de 50, sendo como principais
daquela época, FORTRAN, LISP, COBOL. Na década de 70 as linguagens que começaram a
aparecer foram, Pascal, SQL e C, então começou a debater-se sobre a criação de linguagens
estruturadas. Programação estruturada é a programação onde é utilizado um fluxo para a execução
do código escrito, ao contrário das linguagens que utilizavam o famoso GOTO para levar a diversas
partes do código, a programação estruturada segue um bloco de código com várias funções e subrotinas que seguidas estruturalmente processam as informações de acordo com as condições
impostas pelo desenvolvedor.
Um pouco mais tarde foi criada a programação orientada a objetos. A orientação a objetos
visa determinar escopos reduzidos para a construção dos blocos de código, facilitando assim o
entendimento do programa e sua real função e fluxo de troca de mensagens. Esse paradigma visa à
modelagem de objetos do mundo real, agregando a eles comportamentos semelhantes no contexto
da aplicação. A comunicação entre os objetos é feita através de mensagens entre os métodos. As
linguagens que inspiraram a orientação a objetos foram o C++ e o Perl.
Na década de 90 houve o surgimento do Java, Python e C#. A maioria dessas linguagens era
direcionada a web, devido à popularização da internet no mundo e a necessidade de aplicações para
internet. Uma peculiaridade da era da internet, foi o surgimento de linguagens de script, como PHP,
Ruby e JavaScript.
2.3 Manifesto Ágil
Com o aumento exponencial do uso de computadores por empresas e indivíduos, a
necessidade de produzir software tornou-se algo vital para a humanidade. Várias empresas
começaram a ser formadas ao redor do globo, desenvolvendo de formas distintas. Com a
necessidade de mão de obra e padronização na produção, áreas da computação foram surgindo, tais
como a Engenharia de Software. Portanto, foram elaborados alguns modelos para especificar o
desenvolvimento de sistemas, dos quais se destaca o modelo cascata ou seqüencial, que visa à
construção do software por etapas pré-determinadas.
17
Ao longo dos anos, alguns desenvolvedores perceberam que a forma de trabalhar não estava
correta, ou seja, as questões de levantamento de requisito antes e codificação depois de preencher
papéis de documentação não se encaixavam na realidade do mercado. Esses, mais conhecidos como
signatários, elaboraram o manifesto ágil, que determina algumas prioridades na elaboração do
software no mundo real. Esse manifesto visa reforçar as boas práticas para desenvolvimento
apontando o modelo a ser seguido num projeto. Nesse manifesto temos quatro valores principais:
•
Os indivíduos e suas interações acima de procedimentos e ferramentas
•
O funcionamento do software acima de documentação abrangente
•
A colaboração dos clientes acima da negociação de contratos
•
A capacidade de resposta às mudanças acima de um plano pré-estabelecido
O que foi observado com esses valores principais, é que o processo clássico para elaboração
de um projeto de software era engessado por partes vitais e seqüenciais, ou seja, se alguma das
partes não fosse completada a tempo, o projeto ficava congelado esperando liberação de tal parte. Já
o manifesto ágil prega uma interação constante com o cliente para ter feedback das funcionalidades
implementadas, o produto sendo posto em produção antes mesmo de uma extensa documentação, e
outros valores que vem melhorando a forma de desenvolver software atualmente. A agilidade nos
projetos não implica na velocidade do desenvolvimento e sim nos curtos períodos de tempo que o
produto leva para ser entregue, ou seja, têm-se entregas semanais dos produtos, para saber se tal
funcionalidade atende a necessidade do cliente. A busca por simplicidade no código e qualidade
técnica também são fatores abordados pelo manifesto ágil.
A partir dele, alguns dos signatários ganharam notoriedade no meio do mundo do
desenvolvimento, dentre eles podemos ressaltar Kent Beck, o criador do XP (BECK, 99). A técnica
do XP é utilizada para agilizar o processo de desenvolvimento e reduzir a complexidade do código
criado pelo desenvolvedor através de boas práticas, sendo elas:
•
Testes automatizados
•
Programação em Par
•
Refatoração
Com os testes é possível afirmar que o código elaborado realmente faz o que ele se propõe a
fazer. A programação em par visa à qualidade do software e o aprendizado com duas pessoas
18
programando juntas uma funcionalidade. E a refatoração é o ato de sempre melhorar o código uma
vez criado.
Refatoração ajuda você a fazer código mais legível.
Quando estamos refatorando, vemos código que funciona, mas
ainda não está totalmente estruturado. Um pouco de tempo gasto
com refatoração pode fazer o código ser auto-explicativo.
Programar dessa forma é sempre querer mostrar o que aquilo
significa pra você. [FOWL 99, p. 48]
19
3. TECNOLOGIAS ENVOLVIDAS
3.1 Ruby
Antigamente, a forma de diferenciar uma linguagem era fácil: haviam apenas a compilada,
como C ou Fortran, ou a interpretada, como BASIC. Sabe-se que linguagens compiladas dão
velocidade e acesso de baixo nível em computadores. Já as interpretadas são de alto nível, porém
mais lentas. O tempo se passou e alguns designers de linguagem começaram a chamar suas criações
de “Linguagem de Script”. Isso significa que essas linguagens, além de serem interpretadas, podiam
ser usadas para substituir arquivos batch e shell scripts, manipulando o comportamento de outros
programas e algumas funções do sistema operacional. Dentre as linguagens de Script podemos
citar: Python, Perl, TCL e Ruby. [THOM, 04]
Ruby é uma linguagem de script com uma tipagem dinâmica, forte e gerenciamento de
memória automático. É fácil de aprender. Tarefas diárias são simples de codificar e, uma vez tendo
as criado, é fácil mantê-las e expandí-las. É uma linguagem transparente, porque não existem partes
obscuras na escrita do código, por outro lado, esse é muito legível e torna o entendimento fácil para
o programador. A dificuldade que o programador tem em outras linguagens para expressar o
pensamento lógico, não existe em Ruby. Pela facilidade que há em escrever código em Ruby,
podemos nos preocupar apenas com as regras de negócio do escopo do software e com alguns
padrões de manutenção e melhorias constantes. Com a facilidade na leitura do código digitado, o
risco de errar ou escrever código com comportamento duvidoso é quase nula.
Ruby é uma linguagem concebida pelo intelecto de um homem através dos pontos positivos
de diversas linguagens. Yukihiro Matsumoto, mais conhecido na comunidade como “Matz”, pegou
os pontos principais de diversas linguagens como, ADA, LISP, Smalltalk, Python, Perl, e
incorporou no núcleo da linguagem. [FLAN, 08]
Pode-se ressaltar que Ruby é fortemente orientada a objetos, ou seja, tudo é um objeto.
Outro aspecto importante é que esta linguagem possui uma poderosa capacidade de
metaprogramação. Matz se preocupou em fazer uma linguagem que realmente fosse amiga do
programador, tornando seu trabalho diário divertido. Em suas próprias palavras ele dizia: “Ruby is
designed to make programmers happy” (Ruby é projetada para fazer os programadores felizes)
[MATZ].
20
Ruby é uma linguagem orientada a objetos genuína. Tudo
que é manipulado é um objeto e os resultados dessa
manipulação também são objetos. [THOM 04, p.09]
A primeira publicação de Ruby, a versão 0.95, foi lançada em dezembro de 1995. O
primeiro livro sobre a linguagem fora do Japão foi impresso somente em 2000. A versão da
linguagem amplamente utilizada hoje é a 1.8. Em 30 de janeiro de 2009 a versão 1.9.1 foi
introduzida, incorporando novas características e melhorias, principalmente no que diz respeito ao
desempenho.
Ruby, até sua versão 1.8, tinha apenas duas principais implementações. A primeira e mais
comum é a MRI (Matz’s Ruby Interpreter), interpretador do próprio Matsumoto. A segunda é
chamada JRuby, e roda o código ruby dentro da JVM (Java Virtual Machine). A partir da versão
1.9 uma nova máquina virtual é utilizada, o YARV (Yet another Ruby VM), uma máquina virtual
que interpreta byte code visando aumentar a velocidade de programas em Ruby.
3.1.2 RubyGems
RubyGems é um gerenciador de pacotes para a linguagem de programação Ruby. Uma gem
é um conjunto de códigos reutilizáveis que podem ser distribuídos num formato muito parecido
com o repositório apt-get do Linux. O interessante na utilização de gems é o padrão imposto na
elaboração da mesma e seu controle de versões.
RubyGems
é
um
framework
de
instalação
e
empacotamento para bibliotecas e aplicativos, facilitando a
localização, instalação, atualização e desinstalação de pacotes
Ruby. (THOM 04, p. 203)
Para instalar uma gem no Ruby, basta digitar o seguinte comando:
gem install nomedagem
21
3.1.3 RVM – Ruby Version Manager
RVM é um gerenciador de controle de versão para a linguagem Ruby que permite ao
desenvolvedor ter várias versões da linguagem instaladas na mesma máquina. Antes da criação do
RVM era difícil e conflitante utilizar versões diferentes do Ruby com gems diferentes e alternar de
acordo com cada projeto. Já com o uso da RVM basta digitar: rvm use versaodoruby, para
utilizar a versão especificada.
Com o RVM é possível ter várias versões de Ruby instalados na máquina e várias gems
dentro de cada versão, o que facilita bastante na hora de desenvolver um projeto, bem como
contribui na questão da compatibilidade entre sistemas desenvolvidos com versões distintas.
3.2 Rails
Com a popularização do desenvolvimento web e com as práticas ágeis sendo incorporadas
nas empresas em todo o mundo, a maioria dos desenvolvedores passaram a reaproveitar código para
evitar a repetição na hora de iniciar um novo projeto e ter que configurar todos os diretórios, pastas,
banco de dados e ambiente.
A partir do momento que a reutilização de código tornou-se uma tarefa quase que
obrigatória na área de desenvolvimento, foram surgindo os frameworks para abstração de camadas
mais complexas de programação e para reuso do código usado em projetos.
Uma façanha que a orientação a objetos conseguiu obter foi à estruturação de blocos de
códigos imensos, em pequenas classes especialistas. Alguns conceitos como Herança, Polimorfismo
e Interfaces auxiliam o desenvolvedor a estruturar melhor o seu código e fazê-lo sem muitas linhas
em um único arquivo.
Pensando assim, surgiu o Ruby on Rails. David Heinemeier Hansson (DHH), o criador do
Rails, cansado de utilizar códigos gigantescos, como Java e PHP, resolveu utilizar alguma
linguagem nova que atendesse sua necessidade naquele momento. Ele então, ao iniciar seu projeto
intitulado BaseCamp, na 37 Signals, empresa na qual ele foi contratado como programador,
começou a preparar seu ambiente de desenvolvimento, fazendo classes genéricas, abusando da
orientação a objetos e da linguagem Ruby, que permitia o uso intenso deste paradigma.[RUBY, 11]
Rails foi lançado em 2005 para uso pela comunidade como open source, o que permitiu sua
constante melhora e adição de funcionalidades por um grupo intitulado CORE TEAM e por outros
desenvolvedores.
22
ROR (Ruby On Rails) tem como objetivo facilitar o desenvolvimento de aplicações web. É
um framework que facilita a elaboração de aplicativos e torna fácil a manutenção devido a alguns
princípios de qualidade de software. O primeiro princípio, Convention Over Configuration (CoC),
diz que o programador que seguir algumas convenções terá rapidamente sua aplicação rodando com
o mínimo de complexidade. Programadores Java que já passaram por problemas de dependências,
configurações de XML (eXtensible Markup Language), banco de dados e servidores, podem
facilmente migrar para ROR e ver o quanto de configuração é encapsulada.
Rails prioriza indivíduos e interações. Não existem
ferramentas pesadas, configurações complexas e processos
elaborados. [RUBY, 2011, p21]
Rails também possui o princípio de DRY (Don’t Repeat Yourself), ou seja, deve-se apenas
ter código no lugar onde aquele código destina-se a ser usado. Não se deve repetir o código gerado
em outros lugares. Se esse código é usado em vários locais, deve-se torná-lo genérico e de acesso
múltiplo a várias classes.
DHH quando criou o Rails fez pensando de forma ágil e dentro de alguns padrões de
projeto. Uma característica que confirma essa afirmação é a criação de três ambientes de execução,
Production (Produção), Development (Desenvolvimento) e Test (teste). Como se sabe, uma
aplicação em produção tem toda uma particularidade, assim como nos outros ambientes. Em Java, é
necessário sempre que feita uma modificação, reiniciar o servidor e iniciar o mesmo para visualizar
a modificação efetuada. Já no ROR tem-se como utilizar o ambiente de desenvolvimento e
simplesmente atualizar o navegador para visualizar a modificação efetuada no código. Ratificando o
uso de metodologias ágeis, o ambiente de teste fora criado para facilitar a execução de suítes de
testes e configuração de algumas peculiaridades como seed de banco de dados, fixtures, etc.
E o último conceito, o KISS (Keep it Simple Stupid) visa manter o código criado na forma
mais simples possível, permitindo que qualquer desenvolvedor entenda o que o mesmo faz, e
reforçando o uso de refatoração no código criado.
3.2.1 Padrão MVC
Em 1979, Trygve Reenskaug desenvolveu uma nova arquitetura para desenvolver aplicações
interativas. Na sua concepção, as aplicações eram distribuídas em três partes: Modelo, Visualização
e Controlador. [RUBY, 11]
23
O modelo é responsável por manter o estado da aplicação. O modelo quase não tem contato
com o usuário, ele é responsável pelo armazenamento da informação na base de dados. Representa
também a entidade no sistema a ser desenvolvido e mantém em si, toda a regra de negócio da
aplicação.
Um modelo é mais que apenas dados. Ele agrega todas as
regras de negócio que se aplica aos dados. [RUBY, 2011, p. 51]
A visualização é responsável pela apresentação dos dados obtidos pelo modelo, gerando
assim a interface. Sabe-se que essa interface pode ser representada de diversas formas para o
mesmo modelo, dependendo assim da necessidade do usuário.
O controlador como o próprio nome já diz, controla toda interação do usuário com o sistema
e qual visualização o mesmo vai obter, ou seja, ele orquestra o modelo, e a visualização deste, e as
entradas de dados do usuário. É através do controlador que podemos buscar adicionar e localizar
dados no sistema.
Ruby on Rails é um framework MVC. Ao criar uma aplicação Rails, pode-se observar que o
framework cria automaticamente os diretórios para os controladores, as visualizações e os modelos.
Sendo assim, o Rails consegue interpretar todo o código adicionado nos três locais, respeitando o
conceito de MVC e deixando a aplicação mais fácil de ser mantida.
Figura 3.1 - Fonte: Modelo MVC Rails, http://wiki.cercomp.ufg.br/Equipe_Web/RoR/MVC, 2011
24
3.2.2 Suporte a Modelo no Rails
O objetivo principal do Rails é proporcionar aos desenvolvedores para web que utilizam o
padrão MVC a possibilidade de armazenar dados no banco de dados através da própria aplicação.
Ele trata cada modelo criado através do framework como uma entidade, e através dessa convenção
nota-se que o Rails cria automaticamente a tabela no banco de dados para receber os respectivos
dados e seus relacionamentos caso existam.
O problema em trabalhar com uma linguagem de programação orientada a objetos e um
banco de dados relacional já é conhecido, devido à complexidade em questão. É correto afirmar que
objetos possuem dados e métodos. Em contrapartida, bancos de dados são somente linhas e colunas
com conjunto de valores. O mapeamento objeto-relacional sempre foi uma tarefa difícil de ser
implementada, até mesmo em outras linguagens.
Devido a essa dificuldade, alguns desenvolvedores começaram a estudar padrões de
mapeamento objeto-relacional, o que permitia a comunicação e modelagem dos objetos em colunas
do banco de dados. Essas bibliotecas que fazem essa comunicação são conhecidas como ORM’s
(Object-Relational Mapping).
3.2.2.1 Mapeamento Objeto-Relacional
Bibliotecas de Mapeamento Objeto-Relacional mapeiam as tabelas do banco de dados em
classes. Por exemplo, se existe uma tabela chamada cliente, deve existir uma classe chamada
cliente. Os objetos clientes correspondem a linhas na tabela e seus atributos a colunas da mesma.
Cada objeto tem métodos get e set para preenchê-lo com os dados das colunas. Rails, em específico,
traz métodos que auxiliam a manipular os dados vindos do banco de dados. Por exemplo, é possível
observar nesse código Ruby a complexidade SQL que foi omitida para achar um cliente por seu
código:
cliente = Cliente.find(1)
Figura 3.2 – Código Rails para localizar Cliente por id
25
Em resumo, uma camada de Mapeamento Objeto-relacional mapeia tabelas para classes,
linhas para objetos e colunas para atributos desse objeto.
Geralmente, para que esse processo ocorra, é necessário utilizar arquivos XML para fazer o
relacionamento dos campos. Porém, com a Convenção sobre a Configuração do Rails, o processo é
automático.
3.2.2.2 Active Record
Active Record (AR) é a camada ORM que vem com o Rails. Ele segue estritamente o padrão
dos ORM’s tradicionais, porém, uma das grandes adições feitas foi à redução de configuração se o
padrão do framework for seguido. Para entender melhor como isso é feito, veremos um código em
Rails de um mapeamento de uma tabela chamada clientes:
classCliente<ActiveRecord::Base
end
cliente = Cliente.find(1)
cliente.nome = “Thiago”
cliente.save
Figura 3.3 – Exemplo de Uso do AR
Como é observado a classe automaticamente, quando criada, herda de ActiveRecord::Base,
que é a classe ORM que vem por padrão em Rails. Com essa classe herdada, todos os métodos e
atributos da classe ActiveRecord passam a fazer parte da classe Cliente, podendo simplesmente
fazer um find e localizar um cliente por id. Localizando o registro, é possível modificá-lo e depois
salvá-lo na base de dados. O AR do Rails é a base do Modelo do padrão MVC e também um padrão
de projeto estabelecido por Martin Fowler [FOW 03]. Ele possui algumas validações dos atributos
pertencentes ao modelo e outros métodos que fazem dele um framework prático e ú.
3.3 Action Pack: Visualização e Controlador
Quando é analisado a fundo a visualização e o controlador no padrão MVC, é possível
perceber o quão interligados estão. O controlador recebe várias requisições das interfaces geradas
26
pela visualização. Por outro lado, o controlador alimenta a camada de visualização com os dados do
Modelo. Devido a essa aproximação, o Rails agrupou os dois num pacote chamado ActionPack.
3.3.1 Suporte a Visualização
A parte de visualização em Rails é responsável por criar todas as telas que vão exibir as
respostas geradas pelas requisições. A visualização é uma página HTML padrão. Quando queremos
utilizar texto dinâmico na aplicação, ele utiliza alguma ação do controlador e exibe através de um
código Ruby dentro do HTML com extensão ERB (Embedded Ruby).
3.3.2 Controlador
O controlador é o centro da lógica da aplicação. Ele coordena a interação do usuário com a
camada de visualização e o modelo. Quando um usuário clica em algum link da aplicação, é o
controlador o responsável por executar aquela requisição, filtrando alguns dados, modificando-os e
salvando-os e exibindo seu resultado através de URL’s amigáveis.
O Controlador no Rails é o centro de toda lógica da
aplicação.
Ele
coordena as
interações
do
usuário,
as
herdar
de
visualizações e o modelo. [RUBY, 2011, p.57]
Todos
os
controladores
criados
pelo
Rails,
são
destinados
a
ActionController::Base, mas o framework cria um controlador chamado ApplicationController e o
faz ser herdado pelos controladores criados pelo usuário, mantendo assim métodos criados para uso
generalizado dos controladores em um só lugar.
27
4. TESTES AUTOMATIZADOS
Qualidade de Software é um assunto discutido em larga escala por empresas,
desenvolvedores e clientes. Antigamente era satisfatório ter um sistema em produção, porém
existem outras preocupações.
Um código bem escrito é considerado um código limpo segundo desenvolvedores
experientes no mercado. O que seria um código limpo e um código ruim? Segundo o livro Clean
Code [MART, 09] um código bom é um código que realmente expressa o que ele faz apenas sendo
lido, tanto pelo autor do mesmo, como por outros desenvolvedores. Um código ruim simplesmente
é o contrário da afirmação acima.
Com as metodologias ágeis sendo popularizadas e com o crescente uso da Programação
eXtrema (XP) [BACK,04], a necessidade de acelerar o processo de testes de software foi tornandose cada vez mais necessária. Antes de ouvir-se falar em testes automatizados, os desenvolvedores
depuravam o código existente através de debugging.
“Debugar” um código é seguir seu fluxo e tentar achar a parte que lança erros ou exceções
em ambiente de execução. Se fosse necessário debugar um sistema complexo e com milhares de
linhas de código, isso poderia ser uma tarefa complexa e nada produtiva. Essa antiga forma de teste,
depuração e correção de erros era utilizada principalmente na metodologia de desenvolvimento em
cascata, onde se levantavam as funcionalidades, faziam os diagramas, implementava-os e lançavaos para aprovação e teste.
Estes testes do modelo em cascata não eram automatizados, eram feitos por Testers, pessoas
especializadas em apenas realizar testes manuais em softwares. Porém, testes manuais são
demorados e na maioria das vezes não cobrem todas as possibilidades de resposta de um código, o
que não garantia que o código implementado alterasse o comportamento de outro método ou
função em outra parte. Algumas empresas ainda utilizam este modelo de testes em software, e por
incrível que pareça, ainda existem algumas que não fazem testes.
O que foi observado com o tempo é que modificação no código com o sistema em produção
é mais custosa do que as em projeto. Segundo o Manifesto Ágil, é necessário ter interações com o
cliente constantemente e estar plenamente disposto a modificar o código. Com testes
automatizados, a rotina e o desgaste devido aos testes manuais acabam. Pode-se executar o conjunto
de testes quantas vezes for necessário, cobrindo praticamente a totalidade do código implementado
e tendo a certeza, de que, depois de adicionada uma nova funcionalidade, todo o sistema irá
funcionar corretamente sem sofrer alterações comportamentais.
28
No mundo dos testes automatizados, há os testes unitários que são responsáveis por testar
pedaços de código unitariamente; tem-se os testes de integração que permite averiguar o fluxo de
dados e a comunicação dos diversos módulos implementados separadamente; e também os testes de
aceitação que literalmente testam a aceitação do sistema por seus usuários e se as funcionalidades
respondem de acordo com o que foi analisado e descrito.
Existem vários benefícios de se ter testes bem elaborados.
Você terá um melhor design no código que lhe proporcionará
mais confiança. Mas o mais importante é que se você testa bem,
seu trabalho é mais produtivo. [RAPP, 2011, P.17]
O mais importante dos testes automatizados é que eles facilitam a vida do programador e o
auxilia a criar um código mais legível e simples. Toda complexidade do código é reduzida com o
auxílio de testes. Existe uma técnica chamada TDD, comentada mais adiante, que define passos
para implementação de código depois de ter testes escritos. Sendo assim, o design do sistema tornase mais coeso e qualquer desenvolvedor que ler as especificações dos testes entenderá o
comportamento dos métodos das classes.
Testes auxiliam na criação e na manutenção do código fonte de um sistema. Segundo
Feathers, código legado é código sem testes, ou seja, não importa se o código é antigo ou novo, se
não tiver testes é um código legado.
4.1 Testes em Ruby on Rails
Ruby on Rails, por ser um framework ágil para desenvolvimento web, como o próprio Dave
Thomas menciona no livro Agile Web Development [RUBY, 11], já vem com várias bibliotecas de
testes automatizados. A biblioteca padrão é a Test::Unit, que vem junto com o Ruby até sua versão
1.8.7. A partir da 1.9.2 a padrão passou a ser a biblioteca chamada minitest.
Testes automatizados estão no coração de uma aplicação Rails. A partir do momento que o
desenvolvedor utiliza o comando rails new nomeaplicacao (Rails 3.0.6), é criado um diretório
específico para testes unitários, gerando toda uma infraestrutura para iniciação com testes. Testes
unitários são testes que pegam a menor unidade em uma classe. Para ser mais exato, estes testes
testam os retornos dos métodos, localizados principalmente no modelo.
Se existir um modelo chamado produto, dentro da pasta test/unit/, haverá um arquivo
chamado produto_test.rb. Dentro desse arquivo existe código Ruby como o que é visto logo abaixo:
29
classProdutoTest<ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end
Figura 4.1 – Exemplo de Estrutura de Teste Unitário
A
classe
gerada
ProdutoTest
é
uma
subclasse
de
ActiveSupport::TestCase.
ActiveSupport::TestCase é uma subclasse de Test::Unit::TestCase, o que indica que os geradores de
teste do Rails são baseados no RubyTest::Unit que vem pré-instalado com o Ruby. O bom disso é
que se o desenvolvedor já tem habilidades com os testes unitários em Ruby, esse conhecimento
pode ser utilizado em aplicações Rails. Para os novatos em testes unitários, o Rails cria uma
asserção (assert) como exemplo de como criar métodos de testes no framework. Toda asserção tem
por regra retornar verdadeiro (true) para os testes escritos. Quando é escrito “assert true” para que o
resultado seja verdadeiro, ou seja, para que o teste passe, a resposta daquele método deve ser
verdadeira.
Para aprofundar-se nos testes unitários, é preciso primeiramente aprender como utilizar
alguns métodos do framework de testes do Ruby. Esses métodos conhecidos como asserções
possuem a funcionalidade de retornar true ou false. Se uma asserção é falsa, o teste falha e o
conjunto de testes que está sendo executado é interrompido. Se a asserção é verdadeira, o teste
passa e garante que o método está bem estruturado para aquela funcionalidade escrita. A seguir é
apresentado um simples exemplo do uso de asserções no Test::Unit:
test "produto deve ter um nome" do
@produto = Produto.new
@produto.nome = “radio”
assert (@produto.nome == "radio")
end
Figura 4.2 – Teste Unitário com Asserção
30
Esse simples teste garante que o objeto produto que foi instanciado dentro de uma variável
de instância (@produto) chamada produto deve possuir um nome. Caso o nome não fosse definido,
o teste retornaria falso e nos exibiria uma mensagem dizendo que o teste “produto deve ter um
nome” retornou falso porque não está atribuindo um nome para o objeto produto.
A asserção mais usada é a assert_equal que garante que um valor atribuído a um objeto é
igual ao valor descrito no teste. Sabendo que para efetuar um teste, devemos ter um banco de dados
específico para testes, ou outros meios de instanciar objetos para executar os testes, os valores que
serão testados são pré-configurados para que o teste passe. A asserção assert_equal(), recebe dois
argumentos: um valor esperado e um valor que esta dentro do objeto e retorna true se os valores
forem iguais. Implicitamente, o método assert_equal utilizado o operador == do Ruby para
comparação de igualdade, assim como o equals do Java e C#. [RUBY, 11]
test
“produto.nome deve ser igual a camisa” do
@produto = produto.new
assert_equal “camisa”, @produto.nome
end
Figura 4.3 – Teste Unitário com Assert_Equal
Note que todos os métodos de teste feitos seguem a mesma estrutura. Começam com a
palavra chave test, nome do método, do, bloco de código e end, indicando o fim do método. Isso
facilita o parser do TestUnit identificar os métodos que são efetivamente testes. Se for necessário
testar a falha de um teste, ou seja, se um valor é diferente do outro, pode-se usar o
assert_not_equal().
test
“deve retornar verdadeiro se produto.nome for diferente” do
@produto = produto.new
@produto.nome = “calça”
assert_not_equal “camisa”, @produto.nome
end
Figura 4.4 – Teste Unitário com Assert_not_equal
Como é visto, é boa prática colocar no nome dos testes a real intenção do mesmo. Serão
listadas aqui algumas asserções utilizadas pelo Test::Unit framework do Ruby:
31
•
assert_instance_of(klass, object)( ), assert_kind_of(klass, object)( )
Informa se o objeto é um objeto da classe passada como argumento;
•
assert_match(pattern, string) ( ), assert_no_match(pattern, string) ( )
Igual o assert_equal() mas para expressões regulares;
•
assert_nil(object) ( ), assert_not_nil(object) ( )
Verifica se um objeto é nulo ou não;
•
assert_raise(*args, &block) ( ), assert_nothing_raised(*args, &block)( )
Verifica se uma exceção é levantada e o teste é bem sucedido se essa exceção foi levantada
pelo bloco passado. Na negativa o contrário ocorre.
É possível obter maiores informações sobre asserções e como utilizá-las no site do
framework de testes unitários no Ruby.
4.1.1 Setup e TearDown
Quando os testes crescem em quantidade de métodos, duplicações podem ser encontradas no
código, ou seja, observando os pequenos testes escritos nos exemplos a seguir, verifica-se que a
parte de configuração dos objetos é idêntica:
test
“deve retornar verdadeiro se produto.nome for diferente” do
@produto = Produto.new
@produto.nome = “camisa”
assert_not_equal “camisa”, @produto.nome
end
test
“produto.nome deve ser igual a camisa” do
@produto = Produto.new
@produto.nome = “camisa”
assert_equal “camisa”, @produto.nome
end
test "produto deve ter um nome" do
@produto = Produto.new
@produto.nome = “camisa”
assert (@produto.nome == "camisa")
end
Figura 4.5 – Exemplo de Repetição de Código em Teste Unitário
32
Nesses testes é sempre obrigatório instanciar os objetos e, em alguns, atribuir conteúdo às
variáveis. Para evitar duplicação de código e seguir sempre o princípio DRY em testes, pode-se
utilizar um método chamado setup.
def setup
@produto = Produto.new
@produto.nome = “thiago”
end
4.6 – Exemplo do Método Setup
Esse método é carregado automaticamente ao executar cada método de teste do conjunto de
testes, evitando a repetição de código em cada teste escrito.
Em testes unitários, é utilizado um método chamado tearDown para executar algo depois de
rodar os testes.
4.1.2 O que pode ser testado em Rails?
Quando se testa uma aplicação Rails, o framework especifica o local padrão dos testes
baseado na classe que será testada. Todos os arquivos e diretórios de testes em uma aplicação são
encontrados em uma pasta chamada test dentro da pasta do projeto. Tendo todos os arquivos em
apenas uma pasta centralizada de testes, facilita a localização dos arquivos e a criação e manutenção
de novos arquivos.
As
classes
de
teste
para
os
modelos
encontram-se
no
diretório
test/init/nomedaclasse_test.rb. Para essa classe de teste criada, o framework espera ter uma classe
modelo que a corresponda.
Os testes funcionais e os controladores têm a mesma relação que os testes unitários e os
modelos. Isso significa que testes funcionais são relevantes a testes específicos de controllers. Essas
classes estão localizadas em test/functional. Para cada classe controladora, existe um teste funcional
criado. Se esse controlador foi gerado através dos geradores do Rails. Todos os testes de
controladores no Rails herdam de ActionController::TestCase, que é uma subclasse de
ActiveSupport::TestCase que também é utilizado pelos modelos. [RAPP, 11]
33
O objetivo do teste de controlador é simular uma única
requisição para especificar o método do controlador e fazer
asserções sobre o resultado. [RAPP, 11, p. 131]
O Rails cria também um arquivo chamado test_helper.rb que contem algumas
funcionalidades
e
configurações
comuns
a
todos
os
testes,
bem
parecido
com
o
applicationController que é gerado para compartilhar métodos em todos os controllers. O que torna
essa informação preciosa é que todos os arquivos de testes fazem um require (como o import do
Java) do arquivo test_helper para que o mesmo possa ser executado com êxito.
Existem ainda dois outros tipos de testes em Rails. O teste de integração que, mesmo sendo
um pouco ignorado, é responsável por testar seqüências de eventos que invocam várias ações e
controladores e os testes de desempenho. Os testes de integração dão diretrizes para validação de
interações complexas na aplicação e também garantem que não existam falhas nos testes dos
controladores. Os testes de integração são criados usando o comando script/generate
integration_test e não são gerados automaticamente por nenhum outro gerador do Rails.
Testes de integração são a ferramenta de escolha no Rails
para testar qualquer processo que chama uma ou mais ações do
controlador. [RAPP, 2011, p. 218]
Já os testes de desempenho são diferentes dos outros testes automatizados. Eles não
verificam se um método retorna o que lhe é prescrito ou se existe algum erro de implementação,
mas eles dão informações sobre o desempenho das ações que são chamadas enquanto outros testes
são executados.
Para executar o conjunto de testes em Rails pode-se utilizar o comando rake test. Esse
comando roda todos os testes existentes na aplicação. Caso seja necessário rodar somente testes de
um grupo específico, por exemplo, unitários, basta digitar rake test:units, ou qualquer uma das
outras opções. Se o teste for executado com sucesso, o resultado no terminal do computador será
um “.” (ponto). Se o teste resultou em erro, a letra “E” aparecerá informando o erro e, se uma
asserção falhar, será exibido um “F”.
34
4.1.3 Fixtures
Na maioria das vezes, testar alguma funcionalidade em um sistema depende da existência de
dados pré-cadastrados na base de dados. Agora se for necessário criar dados fictícios todas as vezes
que testes rodarem na aplicação, haveria vários problemas de repetição e grande parte do tempo
produtivo seria perdido. Devido a essa necessidade, o Rails vem com algo chamado fixtures.
Segundo Rappin, uma fixture é um conjunto de dados predefinidos usados como base para
múltiplos testes. Em Rails, todo modelo tem uma base de teste relacionada. Elas são escritas em
arquivos YAML (YAML is not another Markup Language), e proporcionam um conjunto complexo
de dados que são carregados automaticamente antes de cada teste. A base de dados de teste em
Rails é renovada a cada vez que um teste é executado, por isso temos três ambientes. Se existe um
modelo chamado produto, há uma fixture em test/fixtures/ chamada produto.yml. Estas seguem o
padrão de indentação para que o framework possa identificar os dados escritos no arquivo.
4.2 Test Driven Development
Desenvolvimento Guiado por Teste, ou simplesmente TDD como é conhecida, é uma
técnica usado por equipes ágeis para desenvolver software guiado por teste.
Antes de implementar qualquer funcionalidade no sistema, escreve-se primeiramente um
teste que apresente falha e baseado nesse teste, cria-se o código que vai atender estritamente o que o
teste necessita. Ele é utilizado para design de aplicação pois, através dos testes que são elaborados
antes do código, é possível entender as regras de negócio da aplicação e o que o código escrito
realmente fará.
Test Driven Development é uma prática de
desenvolvimento que envolve escrever teste antes de
escrever código a ser testado. Começa escrevendo um
pequeno teste para o código que ainda não existe.
[CHE 10]
Escrevendo o código baseado de testes, o desenvolvedor tem o cuidado de pensar na melhor
forma de resolver um determinado problema da maneira mais simples possível. Testando primeiro e
35
escrevendo código depois, pode-se pensar melhor na funcionalidade e ter a certeza de que o código
implementado funciona, atendendo à necessidade descrita no teste.
Segundo BECK, todas as vezes que alguém toma uma decisão e não a testa, a probabilidade
dessa decisão estar incorreta é praticamente 100%. Ele também acredita que as funcionalidades que
não podem ser expressadas através de testes automatizados simplesmente não existem.
Esta técnica possui um ciclo conhecido como Ciclo do TDD. Nele tem que sempre escrever
um teste que apresente falha. Em seguida, escrever um código da maneira mais simples que faça o
teste ser bem sucedido. Por fim, refatorar esse código escrito fazendo com que o mesmo fique o
mais simples e compreensível.
Como a maioria das ferramentas de teste retornam verde quando o teste passa e vermelho
quando o mesmo falha, diz-se que este ciclo é baseado em três passos: RED/GREEN/REFACTOR.
Seguindo esse ciclo, o desenvolvedor cria regras de melhoria do código que facilitam
exponencialmente a harmonia da equipe e a qualidade do código como um todo. Rodando sempre
os testes e criando código baseado num teste já criado, tem-se segurança de que o código gerado
atende à real necessidade do teste e não afeta outras áreas do sistema.
Tendo uma aplicação sobre 100% de cobertura de testes, pode-se afirmar que ela está bem
estruturada e supostamente sem bugs que podem gerar resultados inesperados quando colocada em
produção.
Essa técnica passou a ser difundida por um dos signatários do movimento ágil, Kent Beck,
que, através da Programação Extrema [BECK 04], começou a implementar o TDD na prática em
equipes de desenvolvimento de software.
Além do ciclo do TDD temos segundo Martin [MART 09], três leis:
1. Não se pode escrever código antes de ter um teste falhando.
2. Não se pode escrever mais teste se o teste já escrito ainda falha e não compila.
3. Não se pode escrever código além do necessário para fazer o teste passar.
Segundo Beck, devem-se seguir o ciclo do TDD com passos de bebê, ou seja, um passo
errado no entendimento ou seguimento do ciclo pode prejudicar todo o processo. Se uma
funcionalidade é muito complexa e pensar em um teste pra ela é mais difícil do que escrever
diretamente o código para executá-la, é necessário pensar na menor parte da funcionalidade, ou
seja, a mais simples possível e ir incrementando a mesma bem devagar até ter entendido e
especificado através do teste o comportamento esperado.
36
O lado positivo do TDD além do ganho em produtividade devido à baixa correção de bugs,
é a facilidade de ter código autoexplicativo através dos testes para novos desenvolvedores.
Atualmente quando se quer saber a funcionalidade de uma gem em Rails ou de algum
código do próprio framework, os desenvolvedores lêem os testes e, por eles, já entendem o
comportamento do código que é coberto por aquele teste. É importante frisar que uma alteração no
framework sem testes não é aceita pelo núcleo do Rails. Devido a essa exigência, o código do Rails,
mesmo depois de anos sendo modificado e sendo utilizado por milhares de desenvolvedores em
todo o mundo, continua bem escrito e 100% coberto por testes, assegurando seu bom
funcionamento.
Figura 4.7 – Ciclo do TDD, http://reddevnews.com/articles/2007/11/01/testdriven-development-tdd.aspx, 2011
4.3 Behavior Driven Development
Enquanto TDD baseia-se no desenvolvimento guiado por testes, BDD baseia-se na descrição
do comportamento da aplicação baseado na opinião dos clientes. Baseados nessa descrição pode-se
afirmar que BDD necessita de um entendimento completo do mundo da aplicação baseado no ponto
37
de vista dos clientes (stakeholders1) para entregar algo útil. É preciso entender o domínio que a
aplicação será aplicada, os desafios e oportunidades que eles enfrentam e as palavras que eles
costumam usar para descrever o comportamento que eles querem da aplicação. [CHE 10]
BDD foi concebido em 2003 por Dan North em resposta ao TDD de Kent Beck. Uma das
grandes diferenças de um para o outro é que no BDD podemos utilizar a nossa língua nativa para
especificar os comportamentos. Isso permite aos desenvolvedores focar diretamente em como criar
o software e a funcionalidade, ao invés de detalhes técnicos da ferramenta ou da linguagem. Algo
interessante é que com ele é possível tornar os testes compreensíveis por usuários e clientes, bem
como utilizar as próprias especificações destes testes para descrever as funcionalidades na
ferramenta de testes comportamentais.
Dan North criou o primeiro framework em Java, o Jbehave, seguido do Rbehave que é o
anterior portado para Ruby.
4.3.1 Os Princípios do BDD
Alguns profissionais que trabalham com BDD afirmam que praticar essa metodologia é o
mesmo que escrever software que realmente vale a pena, porque ele se baseia inteiramente no que
os clientes querem.
Os três princípios, segundo Chelimsky são:
1. O suficiente é suficiente: Designs gigantescos, análises intermináveis. Deve-se fazer o
menos possível para começar e, qualquer coisa além disso, é um desperdício. Isso também
ajuda a pensar em sempre automatizar as tarefas repetitivas como deploy e build;
2. Entregar algo de valor: Se a pessoa está fazendo algo que não tem valor ou não está
aumentando sua habilidade de entregar valor, pare e faça outra coisa;
3. Tudo é comportamento: Independente se é em nível de codificação, aplicação ou algo
além, deve-se sempre usar o mesmo pensamento e linguistica de construção para descrever
comportamento em qualquer nível de granularidade.
1
Parte interessada no projeto.
38
4.4 Rspec
O RSpec é uma DSL (Domain-Specific Language) para especificar o comportamento
desejado do código Ruby. O mais interessante é que scripts do RSpec (specs) podem ser bem mais
legíveis que os próprios testes unitários, permitindo que o autor consiga expressar suas intenções
relacionadas ao código de uma forma mais simples e expressiva, tornando a leitura dos testes e do
código uma tarefa fácil.
Scripts do Rspec são coleções de comportamentos, que, por sua vez, tem coleções de
exemplos. O método describe cria um objeto com comportamentos. O comportamento configura o
contexto para um conjunto de especificações definidas com o método with, onde deve ser passada
uma sentença para descrever o contexto que será especificado.
Rspec pode ser usado para especificar testes de modelos e controladores, assim como também
pode ser usado para teste de visualização e integração. Isso significa que, dominando este
framework, o profissional tem uma ferramenta que atende todas as necessidades de cobertura de
testes tanto para uso somente com TDD como BDD.
Algumas palavras chaves são usadas para definir utilização do framework. São elas:
• Subject Code: O código cujo comportamento está sendo especificando com o RSpec;
• Expectation: Uma expressão de como o subject code deve se comportar;
• Code Example: Um exemplo executável de como o subject code pode ser usado e como
o seu comportamento esperado (expresso com expectations) em um determinado
contexto;
• Example group : Grupo de code examples.
4.4.1 Descrevendo Comportamentos
Rspec provê uma DSL para especificação do comportamento de objetos. Isso abraça a
metáfora de descrever o comportamento da mesma forma que o expressamos se estamos
conversando com um cliente ou outro desenvolvedor. Um exemplo disso poderia ser algo como:
Você: Descreva uma conta.
Alguém: Deve ter um saldo de R$ 0
Pode-se expressar essa conversa em RSpec da seguinte forma:
39
Describe “Uma nova conta” do
It “deveter um saldo de 0 reais” do
Conta = Conta.new
conta.saldo.should == Money.new(0, :real)
end
end
Figura 4.8 – Exemplo de Describe no RSPEC
Usa-se o método describe para definir um grupo de exemplo. A string que é passada para ele
especifica o que queremos descrever (uma nova conta). O método it define o código de exemplo. A
string passada para ele descreve o comportamento que se deseja especificar sobre essa condição
(deve ter um saldo de 0 (zero) reais).
Assim como o describe, o método context serve como um apelido para o método anterior,
podendo assim utilizar os dois relacionados, ou seja, o primeiro mais utilizado para descrever coisas
e o segundo para descrever o próprio contexto da coisa a ser especificada.
describe “Conta” do
context “sendo nova” do
It “deve ter saldo 0” do
end
end
end
Figura 4.9 – Exemplo de Describe com Context e It
Resumindo, escrevendo com describe, context e it é possível especificar de uma maneira mais
abrangente o teste em RSpec, possibilitando que o seu resultado possa ser utilizado como
documentação. Um exemplo de saída do resultado do Rspec seria:
Conta
Sendo nova
Deve ter saldo 0
Figura 4.10 – Exemplo de saída formatada do Rspec
40
Como é observado, as especificações geradas pelo RSpec servem perfeitamente para uma
futura documentação do sistema.
4.4.2 Exemplos Pendentes
No livro Teste Driven Development: By Example [BECK, 02], o autor fala sobre fazer uma
lista de testes para um determinado objeto. Isso permite que um desenvolvedor determine os
comportamentos da aplicação como um todo, auxiliando na abstração e nas futuras dependências
que podem existir. Para que um teste não implementado interrompa o fluxo da suíte de testes,
podemos apenas marcá-lo com pendente.
Com o Rspec pode-se facilmente criar testes pendentes, basta apenas usar o método it sem o
bloco:
Describe “Conta” do
it “deve ter limite de 100 reais”
end
Figura 4.11 – Exemplo Teste RSpec Pendente
Teria como saída:
Conta
deve ter limite de 100 reais (PENDING: Not Yet Implemented)
Pending:
deve ter limite de 100 reais
# Not Yet Implemented
Finished in 0.00191 seconds
3 examples, 0 failures, 1 pending
Figura 4.12 – Saída Rspec Teste Pendente
Sempre que são adicionados testes pendentes à lista de exemplos pendentes, ao executar o
Rspec, são mostrados quantos testes comportamentais são necessários descrever para terminar.
Caso seja necessário escrever algum teste que será elaborado em outro momento, deve-se
41
escrever o titulo do teste e no corpo do mesmo colocar a notação pending.
describe “Conta” do
context “sendo nova” do
It “deve ter saldo 0” do
Pending(“Implementar mais tarde”)do
end
end
end
end
Figura 4.13 – Teste Rspec Pendente com mensagem personalizada
4.4.3 Before, After, Specify, Let
Como existe nos testes unitários o setup e o teardown, em Rspec pode-se utilizar métodos que
realizam a mesma funcionalidade. Estes testes são conhecidos como Before e After. Se for
necessário instanciar um objeto para ser usado em cada teste comportamental descrito no describe,
deve-se fazê-lo individualmente ou, simplesmente, declará-lo dentro de um bloco before. Caso
precise limpar o ambiente depois de terminado o teste basta declarar algo em after. É possível ter
dentro de um describe vários métodos it. utilizando um método before ou after, isso prepara o
objeto para ser utilizado em todos os métodos it. Caso seja necessário especificar apenas para um it,
pode-se utilizar o before(:each) ou o after(:each). [CHE,2011]
Quando as asserções conseguem expressar aquilo a que elas correspondem apenas ao ler o
código Ruby especificado, não é necessário utilizar o método it. Neste caso, pode-se utilizar o seu
apelido chamado specify. Com o specify, a própria asserção é utilizada como descrição do
comportamento descrito, conforme o exemplo a seguir:
describe BlogPost do
before { @blog_post = BlogPost.new :title => 'foo' }
it "should not be published" do
@blog_post.should_not be_published
end
end
Figura 4.14 – Teste Rspec com It para descrever o comportamento
42
describe BlogPost do
let(:blog_post) { BlogPost.new :title => 'foo' }
specify { blog_post.should_not be_published }
end
Figura 4.15 – Teste Rspec com let e Specify
Como é possível observar, a string no método it simplesmente representa em inglês a mesma
coisa que a asserção dentro deste método. Sendo assim, ao utilizar o conceito de DRY e evitar a
duplicação de código, pode-se escrever apenas a asserção dentro do bloco esperado pelo método
speficy. É possível perceber ainda que o let foi usado no segundo exemplo. Este método consegue
ter um papel parecido com o before, mas carregando o objeto de uma forma mais legível e
performática. [FERNA 11]
4.5 Cucumber
O Cucumber é um framework de alto nível para testes automatizados. Mais utilizado para
testes de aceitação, ele permite descrever uma estória e implementar o código de acordo com essa
estória. Ele é considerado um framework de alto nível porque, diferentemente do Rspec, não utiliza
apenas código Ruby em sua descrição de funcionalidades, mas utiliza a linguagem plena como, por
exemplo, o inglês. Por convenção, é mais adequado utilizar o inglês para uma leitura universal do
código.
O framework se preocupa em testar o comportamento da aplicação mais no nível de usuário.
Existe uma grande diferença de testar uma funcionalidade isolada com o Rspec e testar a
funcionalidade com o Cucumber. Este último faz algo muito parecido com o que antes era chamado
de teste de caixa preta, ou seja, testar o comportamento da aplicação, como ela reage e qual sua
resposta sem saber em certo qual o código responsável por este comportamento. Alguns
desenvolvedores dizem que ele serve mais para auxiliar a comunicação do desenvolvedor com o
cliente e vice-versa.
Por ser texto claro, determinar uma funcionalidade no Cucumber para alguém que não
conhece linguagem de programação torna-se algo possível. A forma como se pode organizar o
pensamento é de fácil entendimento para ambas as partes. É correto afirmar que os testes com o
framework não substituem os testes com o Rspec e os unitários. Testes feitos com Rspec, mas que
43
testam uma funcionalidade separada são considerados testes de unidade, pois testam uma única
classe ou método. [CHE 10]
Como já foi mencionado, para criar qualquer tipo de funcionalidade devem ser seguir alguns
passos finitos. Estes também podem ser chamados de User Stories. Em BDD, para determinarmos o
escopo da nossa estória, utilizamos algumas palavras chaves:
• Dado que… (o estado atual das coisas)
• Deseja-se fazer algo… (a ação)
• Para que se consiga algo… (o resultado ou objetivo)
Para facilitar o entendimento de como o Cucumber trabalha com texto puro e o transforma em
especificações que devem ser implementadas, observa-se um exemplo de uma funcionalidade
(cenário) implementada:
Feature: Products registration
In order to control my products
As a system's user
I want to be able to manage the products registration
Scenario: Creating a new product
Given I visit the new product page
When I fill the new product form with Foobar as description and
10.00 as the price
And click on the 'Create' button
Then the number of existent products should be increased by one
And I should be sent to the new product's page
Figura 4.16 – Exemplo de Cenário em Cucumber – Fonte: cassiomarques.wordpress.com.br
Como se pode observar, tem-se uma funcionalidade exigida pelo cliente que deve compor o
sistema, ou seja, o sistema deve ser capaz de registrar um produto. Determinando a funcionalidade,
são utilizados os passos que foram descritos anteriormente para compor a estória. Tem-se um breve
resumo do que a funcionalidade faz: Para que possa controlar os produtos, um usuário do sistema
deve ser capaz de gerenciar o cadastro de produtos. Cenário: “Dado que estou visitando a página de
44
novo produto, quando preencher o formulário do produto com “Foobar” na descrição e “10” como
preço, e clicar no botão “criar”, então o número de produtos deve aumentar em um e deve-se ir para
a página de novos produtos”.
Este arquivo deve ser salvo com a extensão feature para que o framework o entenda o como
sendo uma funcionalidade.
É observado que na criação dessa funcionalidade utiliza-se algumas palavras chaves do
framework:
• Feature: Define a funcionalidade do sistema. Pode conter um ou mais cenários. Logo após o
titulo da feature deve-se escrever uma breve descrição sobre o que aquela funcionalidade
fará;
• Scenario: Define um possível cenário da aplicação. Nele é feita descrição de uma das
possíveis user stories, além de possibilitar testes como a aplicação deve se comportar nesse
momento, ou seja, o que ela deve fazer e quais resultados deve exibir;
• Given: Usado para determinar o estado da aplicação no momento que o teste é executado;
• When: Usado para especificar as ações a serem executadas;
• Then: Usado para especificar o resultado esperado.
• And: Usa-se como adendo, podendo unir vários Given/When/Then.
Para que os testes possam ser lidos pelo Cucumber, é preciso seguir estritamente esses
passos de criação das especificações. Vale a pena ressaltar que o mesmo pode ser utilizado em
vários idiomas, inclusive em Português.
45
5. ESTUDO DE CASO
Este estudo de caso visa à prática dos conceitos abordados nesse material. Uma aplicação
em Ruby on Rails será construída baseada em testes com as ferramentas utilizadas como exemplo
para composição dessa monografia.
5.1 Ambientação
Sabe-se que a Internet tem crescido sobremaneira nos últimos tempos. Devido a esse
espantoso crescimento, as aplicações na Internet tem se popularizado em todo o mundo. Hoje não
se fala mais com freqüência sobre criar aplicações desktop que tenha um servidor e rode em apenas
algumas máquinas em rede, mas utilizam-se termos como SaaS (Software as a Service) e Cloud
Computing (computação em nuvem). Devido ao constante uso da internet como meio de unir
pessoas em diversas parte do mundo, percebeu-se que poderia ser utilizado um software de
coordenação de instrutores da polícia militar para agendar formaturas e cadastrar turmas.
5.2 PROERD
O Programa Educacional de Resistência às Drogas (PROERD) da Polícia Militar do Estado
do Rio de Janeiro constitui uma medida preventiva, complementar às ações de repressão ao uso
indevido e tráfico de drogas no Estado do Rio de Janeiro, sendo uma forma de atuação da
Corporação voltada para a prevenção ao consumo de drogas e violência contra e entre crianças e
adolescentes.
5.2.1 Origem
O PROERD teve por base o Projeto D.A.R.E (Drug Abuse Resistance Education),
inicialmente desenvolvido e aplicado pelo Departamento de Polícia e Distrito Escolar Unificado da
Cidade de Los Angeles/EUA, adaptado à realidade brasileira pelo Centro de Capacitação
PROERD/PMERJ.
Através de curso realizado no ano de 1992 no Rio de Janeiro por profissionais do projeto
D.A.R.E., acima mencionado, deu-se início as atividades do programa se estendendo por todo o
país, levando uma mensagem às nossas crianças de como poderão resistir às drogas e violências.
46
5.2.2 Propósito
O Programa Educacional de Resistência às Drogas - PROERD consiste em um esforço
cooperativo da Polícia Militar, Escola e Família para oferecer atividades educacionais em sala de
aula, a fim de prevenir o abuso de drogas e violência entre crianças e adolescentes. A ênfase do
plano de estudos está em auxiliar os estudantes a reconhecerem e resistirem às pressões diretas ou
indiretas que poderão influenciá-los a experimentar álcool, cigarro, maconha, inalantes, outras
drogas ou se engajarem em atividades violentas.
O Programa oferece estratégias preventivas para reforçar os fatores de proteção, em especial
os referentes à família, escola e comunidade, facilitando o desenvolvimento de habilidades de
resistência em jovens que poderiam correr o risco de envolver-se com drogas e problemas de
comportamento. Pesquisadores identificaram fatores protetores ligados à família, escola e
comunidade, os quais fortalecem essa resiliência nos jovens; em outras palavras, a capacidade de
crescerem de forma independente e saudável apesar de condições adversas.
Esta estratégia concentra-se no desenvolvimento das múltiplas competências, habilidades de
comunicação, auto-estima, empatia, tomada de decisões, resolução de conflitos, objetivo de vida,
independência, alternativas positivas ao uso de drogas ou a outros comportamentos destrutivos.
5.3 Objetivo da aplicação
A aplicação tem como objetivo atender as necessidades da coordenação do curso, ou seja, o
usuário deve ser capaz de cadastrar os instrutores e as turmas desses instrutores. Sabendo que o
objetivo principal dessa aplicação, além de atender a necessidade do cliente, é demonstrar para nós
como é o percurso de testes de uma aplicação que começa do zero.
No final da implementação do sistema, o sistema permite o cadastro de instrutores, escolas e
turmas, especificar por cidade quais escolas e turmas pertencem a cada instrutor e ter a marcação de
formaturas para as turmas de cada instrutor.
Todo o sistema é protegido por login e senha. O mesmo está hospedado em uma VPS e terá
seu código disponibilizado no github.
47
5.4 Implementação
5.4.1 Ferramentas
Para a elaboração do sistema será utilizado o ambiente de desenvolvimento num sistema
operacional MACOS X, utilizando como IDE (Integrated Development Environment) o TextMate.
Será utilizada a linguagem abordada nessa monografia, Ruby, com o framework Rails. Para efetuar
os testes em alguns dos controladores da aplicação, serão utilizados o RSpec e o Cucumber.
Basicamente será criada a funcionalidade de login do sistema, junto com o cadastro de
instrutores e turmas. O real objetivo dessa abordagem de um estudo de caso não é para criar uma
aplicação, tanto que alguns dados como diagramas do mesmo serão ocultados aqui. O objetivo aqui
é demonstrar com esse pequeno exemplo o uso prático dos frameworks de teste estudados, além de
validar tudo o que foi dito nessa monografia.
Serão iniciados testes de modelos, controladores e view com o RSpec, com os mesmos os
testes sendo feitos com o Cucumber. Como o software possui várias entidades e features, será
utilizada para exemplo a funcionalidade Turmas, como adicioná-la com Rspec e cucumber.
5.4.2 - Testes com Rspec e Cucumber
Para que possa ser utilizado o cucumber e o rspec em um projeto rails, deve-se editar o
Gemfile com as seguintes linhas:
group :development, :test do
gem "rake", "0.8.7"
gem "rspec-rails", "2.5.0", :group => [:development, :test]
gem "factory_girl_rails", "1.1.beta1", :group => :test
gem "cucumber-rails", "0.4.1", :group => :test
gem "capybara", "0.4.1.2", :group => :test
gem "database_cleaner", "0.6.7", :group => :test
gem "launchy", "0.4.0", :group => :test
gem "devise", "1.3.4"
gem "shoulda"
end
Figura 5.1 – GemFile para testes
48
É claro que para que exista o gemfile, um projeto rails deve ser criado. Para criar um projeto
rails basta digitar o comando rails new nomedaaplicacao. Depois do mesmo criado e do gemfile
com as linhas anteriores, roda-se o comando bundle install para baixar para a máquina todas as
gems que foram especificadas no arquivo.
Depois de baixadas as gems, pode iniciar o uso dos frameworks. Será criado os testes para a
classe Team.rb e será feito TDD em cima desse passo a passo. Primeiramente deve rodar os
comandos:
rails generate rspec:install
rails generate cucumber:install
Figura 5.2: Comandos de instalação
Após ter instalado os frameworks, a aplicação já está pronta para utilizar os helpers e
expectations. Sendo criados modelos através dos geradores do rails, automaticamente é criado os
specs do Rspec.
Para dar inicio ao testes, deve-se criar por escolha do trabalho, uma feature do cucumber,
basta criar um arquivo com a extensão feature.
Feature: teams registration
In order to control the teams
As a system's user
I want to be able to manage the team registration
Scenario:
Given I am a user named "foo" with an email "[email protected]" and
password "please"
When I sign in as "[email protected]/please"
Then I should be signed in
And I visit the new team page
When I fill the new team form with "Veiga de Almeida" as name
And click on the 'Create' button
And I should see the message Team created sucessfully
Figura 5.3 : team.feature
Após a criação desse arquivo, ao ser executado o comando rake cucumber, o terminal
retornará os seguintes passos:
49
Given /^I visit the new team page$/ do
pending
end
When /^I fill the new team form with "(.*)" as name$/ do |name|
pending
end
When /^click on the 'Create' button$/ do
pending
end
When /^I should see the message Team created sucessfully$/ do
pending
end
Given /^that I have created a team "([^"]*)"$/ do |team|
pending
end
Figura 5.4: Team steps Pendentes
Esses são os passos que o cucumber retorna para que o desenvolvedor implemente as
funcionalidades. Para que o teste possa passar com sucesso, devemos criar uma rota para visitar ao
querer adicionar um novo Team. Para isso basta ir em routes.rb e adicionar o resources para team.
Para preencher o formulário, deve-se criar um formulário new.html.erb, com os campos para
preenchimento do nome da turma, e um botão chamado create. O redirecionamento será feito pela
action create para o índex com um flash notice dizendo que foi criado com sucesso. Para efetuar
todos esses passos, deve-se criar o controlador team_controller.rb e o modelo Team.rb. Sabendo o
que deve ser feito basta adicionar os passos que estão no arquivo a seguir:
Given /^I visit the new team page$/ do
visit('/teams/new')
end
When /^I fill the new team form with "(.*)" as name$/ do |name|
@actual_count = Team.count
fill_in "name", :with => name
end
When /^click on the 'Create' button$/ do
click_button("Create")
end
When /^I should see the message Team created sucessfully$/ do
Then %{I should see "Team created sucessfully"}
end
Given /^that I have created a team "([^"]*)"$/ do |team|
Team.create(:name => "cool team")
end
Figura 5.5: Team steps
50
class TeamsController < ApplicationController
before_filter :authenticate_user!
def index
@teams = Team.all
end
def new
@team = Team.new
end
def create
@team = Team.new(params[:team])
if @team.save
flash[:message] = "Team created sucessfully"
redirect_to @team
else
redirect_to :action => 'new'
end
end
def show
@team = Team.find(params[:id])
end
def edit
@team = Team.find(params[:id])
end
end
Figura 5.6: Controlador Team
Os testes do rspec serão em cima do modelo aqui demonstrado por team.rb. Como foi feito o
teste pelo cucumber antes, o modelo já existe devido a criação pelo gerador do rails. Utilizando o
mesmo, o rspec já cria o arquivo de teste para tal modelo, chamado team_spec.rb. Basta utilizar o
shoulda que está no gemfile como helper e adicionar tais validações ao spec:
require 'spec_helper'
describe Team do
before(:each) do
@team = Factory(:team)
end
it {should validate_presence_of :name}
it {should validate_uniqueness_of :name}
it {should belong_to :user }
end
Figura 5.7: team_spec.rb
O modelo deve ficar da seguinte forma:
51
class Team < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
belongs_to :user
end
Figura 5.8: Team model
E nesse momento os testes devem estar todos passando. A entidade está devidamente
testada, e os métodos coberto por testes para garantir que fazem aquilo que propõem que seja feito.
52
6. CONSIDERAÇÕES FINAIS
6.1 Conclusões
Existe uma variedade de novos sistemas surgindo no mercado e com o advento da Internet o
uso de programas através do navegador tem se tornado a cada dia algo mais comum. Com a
facilidade de criar um software com alguma linguagem ágil para desenvolvimento web, a
quantidade de aplicativos para computadores e dispositivos portáteis aumentou exponencialmente
nos últimos cinco anos.
Com o surgimento do framework Ruby on Rails e com todas as facilidades que o mesmo
trouxe para o desenvolvimento de aplicativos, pode-se encontrar vários aplicativos novos em toda a
rede. Aplicativos tais que tomaram o mercado de uma maneira espantosa. O Twitter, por exemplo,
pode ser mencionado como caso de sucesso.
Ruby on Rails também prega a conduta de compartilhamento de código entre a comunidade,
pois ele próprio surgiu da iniciativa de um jovem em compartilhar seu framework pessoal com a
comunidade. Disto pode-se tirar o motivo desse trabalho. Como gerenciar um framework utilizado
por milhares de desenvolvedores e atualizado pelos mesmos sem que haja uma fuga do padrão de
código ou de sua qualidade? A resposta é clara e vem com o manifesto ágil, o XP e o próprio
framework.
Testes automatizados não somente nos auxiliam a cuidar da estética do código, como também
permite produzir código compreensível por seres humanos. A programação em par pregada pela
programação extrema nos motiva a sempre escrever código para que outra pessoa o entenda e que
esse código seja tão simples a ponto de qualquer programador conseguir lê-lo. O manifesto ágil
veio para mostrar para os engenheiros de software e para as grandes corporações que para
desenvolver software não era necessário tantos passos complexos e desnecessários, e que o cliente
gostaria mesmo de ver o programa atendendo sua necessidade.
Utilizando testes automatizados pode-se garantir que o software que esta sendo escrito
realmente atende a necessidade estabelecida pelo cliente. Quando se escreve código com teste,
realmente este está terminado. Foi possível ver como utilizar testes unitários e como fazer TDD.
TDD é uma regra de vida do programador que pretende melhorar seu código continuamente. O
ciclo do TDD é como uma doutrina a ser seguida e aperfeiçoada a cada dia.
Foi observado também que a participação dos clientes na especificação e criação do software
é algo essencial. Quando utiliza-se BDD pode-se simplesmente, com o auxilio do cliente,
53
determinar funcionalidades e cenários na própria língua nativa do cliente, o que garante que será
feito com que a aplicação corresponda igualitariamente à especificação feita pelo proprietário do
software. O Cucumber e o Rspec são frameworks de BDD e que facilitam o uso dessa tecnologia
em Ruby on Rails, comprovando o quão fácil e produtivo é utilizar esta tecnologia para melhorar
constantemente a produção do código e desenvolvimento do software.
Por fim, foram implementadas algumas funcionalidades de um software de exemplo para
mostrar como deve-se trabalhar para a composição dos testes sendo eles unitários, funcionais ou de
integração. Foram utilizados os frameworks mencionados nesta monografia e foi obtido sucesso nos
testes e, conseqüentemente, na implementação do software. Portanto esse trabalho servirá como um
facilitador no aprendizado de testes automatizados em Ruby on Rails para futuros programadores.
6.2 Trabalhos Futuros
Como o sistema abordado nessa monografia não foi concluído, como trabalho futuro,
pretende-se terminá-lo para que o mesmo entre em produção e atenda a necessidade do cliente, tão
como sirva de portfólio e consulta de com realizar testes em Ruby On Rails.
54
ANEXO A – CODIGO FONTE
require 'spec_helper'
describe HomeController do
describe "GET 'index'" do
it "should be successful" do
get 'index'
response.should be_success
end
end
end
home_controller_spec.rb
require 'spec_helper'
describe UsersController do
before (:each) do
@user = Factory(:user)
sign_in @user
end
describe "GET 'show'" do
it "should be successful" do
get :show, :id => @user.id
response.should be_success
end
it "should find the right user" do
get :show, :id => @user.id
assigns(:user).should == @user
end
users_controller_spec.rb
55
require 'spec_helper'
describe TeamsController do
before (:each) do
@user = Factory(:user)
sign_in @user
end
it 'should have a new page' do
get :new
response.should be_success
end
it 'should have an index page' do
get :index
response.should be_success
request.flash.should be_empty
end
it 'should create an team' do
expect {post :create, :team => Factory.attributes_for(:team)}.to
change(Team, :count).by(1)
response.should redirect_to(Team.last)
end
context 'when manipulating teams' do
let(:team) {Factory :team}
it 'should show an team' do
get :show, :id => team
response.should be_success
end
it 'should be able to edit an team' do
get :edit, :id => team
response.should be_success
end
it 'should be able to successfully update an team' do
put :update, :id => team, :team => {:name => "Estacio"}
response.should be_redirect
end
it 'should be able to successfully delete an team' do
delete :destroy, :id => team
response.should redirect_to(teams_path)
end
end
Teams_controller_spec.rb
56
require 'spec_helper'
describe Team do
before(:each) do
@team = Factory(:team)
end
it {should validate_presence_of :name}
it {should validate_uniqueness_of :name}
it {should belong_to :user }
end
end
end
team_spec.rb
require 'spec_helper'
describe User do
before(:each) do
@attr = {
:name => "Example User",
:email => "[email protected]",
:password => "foobar",
:password_confirmation => "foobar"
}
end
it "should create a new instance given a valid attribute" do
User.create!(@attr)
end
it "should require an email address" do
no_email_user = User.new(@attr.merge(:email => ""))
no_email_user.should_not be_valid
end
it "should accept valid email addresses" do
addresses = %w[[email protected] [email protected] [email protected]]
addresses.each do |address|
valid_email_user = User.new(@attr.merge(:email => address))
valid_email_user.should be_valid
end
end
it "should reject invalid email addresses" do
addresses = %w[user@foo,com user_at_foo.org example.user@foo.]
addresses.each do |address|
invalid_email_user = User.new(@attr.merge(:email => address))
invalid_email_user.should_not be_valid
end
end
it "should reject duplicate email addresses" do
User.create!(@attr)
user_with_duplicate_email = User.new(@attr)
user_with_duplicate_email.should_not be_valid
end
57
it "should reject email addresses identical up to case" do
upcased_email = @attr[:email].upcase
User.create!(@attr.merge(:email => upcased_email))
user_with_duplicate_email = User.new(@attr)
user_with_duplicate_email.should_not be_valid
end
describe "passwords" do
before(:each) do
@user = User.new(@attr)
end
it "should have a password attribute" do
@user.should respond_to(:password)
end
it "should have a password confirmation attribute" do
@user.should respond_to(:password_confirmation)
end
end
describe "password validations" do
it "should require a password" do
User.new(@attr.merge(:password => "", :password_confirmation
=> "")).
should_not be_valid
end
it "should require a matching password confirmation" do
User.new(@attr.merge(:password_confirmation => "invalid")).
should_not be_valid
end
it "should reject short passwords" do
short = "a" * 5
hash = @attr.merge(:password => short, :password_confirmation
=> short)
User.new(hash).should_not be_valid
end
end
describe "password encryption" do
before(:each) do
@user = User.create!(@attr)
end
it "should have an encrypted password attribute" do
@user.should respond_to(:encrypted_password)
end
user_spec.rb
58
require 'factory_girl'
Factory.define :user do |u|
u.name 'Test User'
u.email '[email protected]'
u.password 'please'
end
Factory.define :team do |t|
t.name 'Veiga de Almeida'
end
factories.rb
Feature: home
As a user
I want to see the teams link
to go to the team list page
Scenario: Go to the team list page
Given I visit the home page
When I click on Teams link
Then should go to the team list page
home.feature
Feature: Teams
In order to keep track of teams
People should be able to
Create a list of teams
Scenario: List teams
Given I am a user named "foo" with an email "[email protected]" and
password "please"
When I sign in as "[email protected]/please"
Then I should be signed in
Given that I have created a team "cool team"
When I go to the teams page
Then I should see "cool team"
teamlist.feature
59
Feature: Sign in
In order to get access to protected sections of the site
A user
Should be able to sign in
Scenario: User is not signed up
Given I am not logged in
And no user exists with an email of "[email protected]"
When I go to the sign in page
And I sign in as "[email protected]/please"
Then I should see "Invalid email or password."
And I go to the home page
And I should be signed out
Scenario: User enters wrong password
Given I am not logged in
And I am a user named "foo" with an email "[email protected]" and
password "please"
When I go to the sign in page
And I sign in as "[email protected]/wrongpassword"
Then I should see "Invalid email or password."
And I go to the home page
And I should be signed out
Scenario: User signs in successfully with email
Given I am not logged in
And I am a user named "foo" with an email "[email protected]" and
password "please"
When I go to the sign in page
And I sign in as "[email protected]/please"
Then I should see "Signed in successfully."
And I should be signed in
When I return next time
Then I should be already signed in
sign_in.feature
Feature: Show Users
As a visitor to the website
I want to see registered users listed on the homepage
so I can know if the site has users
Scenario: Viewing users
Given I am a user named "foo" with an email "[email protected]"
and password "please"
When I go to the homepage
Then I should see "User: foo"
user_show.feature
60
Feature: Sign out
To protect my account from unauthorized access
A signed in user
Should be able to sign out
Scenario: User signs out
Given I am a user named "foo" with an email "[email protected]"
and password "please"
When I sign in as "[email protected]/please"
Then I should be signed in
And I sign out
Then I should see "Signed out"
When I return next time
Then I should be signed out
sign_out.feature
Feature: Edit User
As a registered user of the website
I want to edit my user profile
so I can change my username
Scenario: I sign in and edit my account
Given I am a user named "foo" with an email "[email protected]"
and password "please"
When I sign in as "[email protected]/please"
Then I should be signed in
When I follow "Edit account"
And I fill in "Name" with "baz"
And I fill in "Current password" with "please"
And I press "Update"
And I go to the homepage
Then I should see "User: baz"
user_edit.feature
Feature: teams registration
In order to control the teams
As a system's user
I want to be able to manage the team registration
Scenario:
Given I am a user named "foo" with an email "[email protected]"
and password "please"
When I sign in as "[email protected]/please"
Then I should be signed in
And I visit the new team page
When I fill the new team form with "Veiga de Almeida" as name
And click on the 'Create' button
And I should see the message Team created sucessfully
team.feature
61
Feature: Sign up
In order to get access to protected sections of the site
A user
Should be able to sign up
Scenario: User signs up with valid data
Given I am not logged in
When I go to the sign up page
And I fill in "Email" with "[email protected]"
And I fill in "Password" with "please"
And I fill in "Password confirmation" with "please"
And I press "Sign up"
Then I should see "Welcome! You have signed up successfully."
Scenario: User signs up with invalid email
Given I am not logged in
When I go to the sign up page
And I fill in "Email" with "invalidemail"
And I fill in "Password" with "please"
And I fill in "Password confirmation" with "please"
And I press "Sign up"
Then I should see "Email is invalid"
Scenario: User signs up without password
Given I am not logged in
When I go to the sign up page
And I fill in "Email" with "[email protected]"
And I fill in "Password" with ""
And I fill in "Password confirmation" with "please"
And I press "Sign up"
Then I should see "Password can't be blank"
Scenario: User signs up without password confirmation
Given I am not logged in
When I go to the sign up page
And I fill in "Email" with "[email protected]"
And I fill in "Password" with "please"
And I fill in "Password confirmation" with ""
And I press "Sign up"
Then I should see "Password doesn't match confirmation"
Scenario: User signs up with password and password confirmation
that doesn't match
Given I am not logged in
When I go to the sign up page
And I fill in "Email" with "[email protected]"
And I fill in "Password" with "please"
And I fill in "Password confirmation" with "please1"
And I press "Sign up"
Then I should see "Password doesn't match confirmation"
sign_up.feature
62
Given /^I visit the new team page$/ do
visit('/teams/new')
end
When /^I fill the new team form with "(.*)" as name$/ do |name|
@actual_count = Team.count
fill_in "name", :with => name
end
When /^click on the 'Create' button$/ do
click_button("Create")
end
When /^I should see the message Team created sucessfully$/ do
Then %{I should see "Team created sucessfully"}
end
Given /^that I have created a team "([^"]*)"$/ do |team|
Team.create(:name => "cool team")
end
teams_steps.rb
Given /^I visit the home page$/ do
visit root_path
end
When /^I click on Teams link$/ do
click_link "Teams List"
end
Then /^should go to the team list page$/ do
visit teams_path
end
home_steps.rb
63
Given /^no user exists with an email of "(.*)"$/ do |email|
User.find(:first, :conditions => { :email => email }).should be_nil
end
Given /^I am a user named "([^"]*)" with an email "([^"]*)" and password
"([^"]*)"$/ do |name, email, password|
User.new(:name => name,
:email => email,
:password => password,
:password_confirmation => password).save!
end
Given /^I am a new, authenticated user$/ do
email = '[email protected]'
login = 'Testing man'
password = 'secretpass'
Given %{I have one user "#{email}" with password "#{password}"}
And %{I go to login}
And %{I fill in "user_email" with "#{email}"}
And %{I fill in "user_password" with "#{password}"}
And %{I press "Sign in"}
end
Then /^I should be already signed in$/ do
And %{I should see "Logout"}
end
Given /^I am signed up as "(.*)\/(.*)"$/ do |email, password|
Given %{I am not logged in}
When %{I go to the sign up page}
And %{I fill in "Email" with "#{email}"}
And %{I fill in "Password" with "#{password}"}
And %{I fill in "Password confirmation" with "#{password}"}
And %{I press "Sign up"}
Then %{I should see "You have signed up successfully. If enabled, a
confirmation was sent to your e-mail."}
And %{I am logout}
end
Given /^I am logout$/ do
visit('/users/sign_out')
end
When /^I sign in as "(.*)\/(.*)"$/ do |email, password|
Given %{I am not logged in}
When %{I go to the sign in page}
And %{I fill in "Email" with "#{email}"}
And %{I fill in "Password" with "#{password}"}
And %{I press "Sign in"}
end
Then /^I should be signed in$/ do
Then %{I should see "Signed in successfully."}
end
Then /^I sign out$/ do
visit('/users/sign_out')
end
When /^I return next time$/ do
And %{I go to the home page}
end
64
Then /^I should be signed out$/ do
And %{I should see "Sign up"}
And %{I should see "Login"}
And %{I should not see "Logout"}
end
Given /^I am not logged in$/ do
visit('/users/sign_out') # ensure that at least
end
user_steps.rb
65
REFERÊNCIAS BIBLIOGRÁFICAS
[BECK 04] BECK, Kent; Programação Extrema (XP) Explicada: Acolha as Mudanças. 1a Ed.
Bookman, 2004. 184p.
[FLAN 08] FLANAGAN, David; MATSUMOTO Yukihiro; The Ruby Programming
Language.1a Ed. O'Reilly Media, 2008.
[FOWL 05] FOWLER, Martin; Patterns Of Enterprise Architeture. 2005.
[FERNA 10] FERNANDEZ, Obies; The Rails 3 Way.2a Ed. Addison-Wesley, 2010.
[GUID 11] Ruby on Rails Guides.Disponível em < http://guides.rubyonrails.org >.Acesso em
15/05/2011.
[MANI 11] Manifesto Ágil Site. Disponível em < http://manifestoagil.com.br >. Acessado em
15/05/2011
[PROERD 11] Proerd Site. Disponível em < http://www.policiamilitar2.rj.gov.br/proerd.asp >.
Acessado em 15/05/2011
[MART 09] MARTIN, Robert C.; Clean Code – a HandBook of Agile Software Craftsmanship.
Pearson, 2009.
[FOWL 99] ______; BECK Kent; BRANT John; OPDYKE William; ROBERTS Don.
Refactoring: Improving the Design of Existing Code. 2a Ed. Addison-Wesley, 1999.
[THOM 04] THOMAS, David; FOWLER Chad, HUNT Andy; Programming Ruby: The
Pragmatic Programmers' Guide. 2a Ed. Pragmatic Bookshelf, 2004.
[FEAT 04] FEATHERS, Michael; Working Effectively with Legacy Code. 1a Ed. Prentice Hall,
2004
[BECK 02] BECK, Kent; Test Driven Development: By Example, 1a Ed. Addison-Wesley, 2002.
66
[FREE 09] FREEMAN, Steve; Pryce Nat; Growing Object-Oriented Software, Guided by Test;
1a Ed. Addison Wesley, 2009
[CHE 10] CHELIMSKY, David, et al. The RSpec Book: Behaviour Driven Development with
RSpec, Cucumber, and Friends. Pragmatic BookShelf, 2010. p.22
[RUBY 11] RUBY, Sam, THOMAS David, HANSSON David Heinemeier. Agile Web
Development with Rails . 4a Ed. Pragmatic Bookshelf, 2011.
[RAPP 11] RAPPIN, Noel. Rails Test Prescriptions. Pragmatic Programmers, 2011
67
Download

UNIVERSIDADE VEIGA DE ALMEIDA INTRODUÇÃO