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