Criando testes unitários com Ruby on Rails

Olá pessoal.

Após ter criado os models precisamos criar testes de unidade para validar as operações que iremos realizar.

Para isso acesse a pasta do seu projeto:

cd projetorails

Execute o comando abaixo para criar todos os bancos incluindo o banco de teste:

rake db:create:all

A saída do comando deve ser algo igual à:

db/production.sqlite3 already exists
db/development.sqlite3 already exists
db/test.sqlite3 already exists

Altere o ambiente de desenvolvimento para ambiente de teste:

rake db:migrate RAILS_ENV=test

Remova todo o conteúdo do arquivo test/fixtures/contatos.yml e adicione o conteúdo que será utilizado para gerar os testes:

contato:
   telefone: 11 2222 3333
   celular:  11 9999 8888
   twitter:  monteirobrena
   facebook: monteirobrena
   msn:      brenamonteiro@hotmail.com

Observação: é necessário manter a identação neste arquivo para diferenciar as propriedades do Contato.

Crie o teste unitário que verificará  as operações que você pode fazer com o model Contato:

require 'test/test_helper'
   class ContatoTest < ActiveSupport::TestCase
      fixtures :contatos
      def test_create_contato
         # Cria o contato com o conteúdo especificado no arquivo test/fixtures/contatos.yml.
         contato_brena = Contato.new :telefone => contatos(:contato).telefone,
                                     :celular  => contatos(:contato).celular,
                                     :twitter  => contatos(:contato).twitter,
                                     :facebook => contatos(:contato).facebook,
                                     :msn      => contatos(:contato).msn
   end
   def test_save_contato contato_brena
      # Verifica se consegue salvar o contato.
      assert contato_brena.save
   end
   def test_copy_contato
      # Cria o contato.
      contato_brena = test_create_contato
     # Salva o contato.
      test_save_contato contato_brena
      # Copia o contato pelo ID.
      contato_brena_copia = Contato.find(contato_brena.id)
      # Verifica se os contatos são iguais.
      assert_equal contato_brena, contato_brena_copia
   end
   def test_update_contato
      # Cria o contato.
      contato_brena = test_create_contato
      # Modifica o facebook do contato.
      contato_brena.facebook = ""
      # Salva o contato.
      test_save_contato contato_brena
   end
   def test_destroy_contato
      # Cria o contato.
      contato_brena = test_create_contato
      # Salva o contato.
      test_save_contato contato_brena
      # Destrói o contato.
      contato_brena.destroy
   end
end

Para executar o teste rode o comando:

ruby test/unit/contato_test.rb

É importante lembrar que as alterações feitas nos testes não serão visualizadas no banco de dados. O arquivo YAML é utilizado apenas para popular o banco de teste, mas as operações de save e destroy não irão alterar as informações contidas no banco.

Saber isso é fundamental, perdi um tempo tentando entender porque as alterações não eram realizadas no banco e só após a ajuda do Rafael Ponte é que descobri o motivo pelo qual não via alterações no banco. Compartilho o link do gist que esclareceu minhas dúvidas sobre teste unitário.

Lembre-se que esta é apenas uma simples forma de começar a fazer testes nos projetos RoR.

Até breve ;)

Criando models com Ruby on Rails

Voltando ao Rails gostaria de compartilhar a criação dos models do meu novo projeto pessoal.

O primeiro passo é criar um projeto Rails como o que foi criado aqui.

O contexto utilizado será referente a uma publicação de um anúncio bem simplificado. E para isso será necessária a criação de três models: Usuário, Contato e Anúncio com as associações apresentadas na figura abaixo.

Aluguel Particular - Diagrama de classe

Aluguel Particular - Diagrama de classe

Entre na pasta do seu projeto Rails:

cd projetorails

Crie o model do Usuário:

rails g model usuario

O Rails criou uma migração inicial para o nosso model. Esta migração encontra-se na pasta db/migrate/<horário_da_criação>_create_usuarios.rb. Abra a migração criada para o model e adicione as colunas:

class CreateUsuarios < ActiveRecord::Migration
   def self.up
      create_table :usuarios do |t|
         t.string  :email, :limit => 50
         t.string  :senha, :limit => 20
         t.string  :nome,  :limit => 50
         t.string  :CPF,   :limit => 14
         t.timestamps
      end
   end

   def self.down
      drop_table :usuarios
   end
end

No terminal execute o comando abaixo para rodar a migração que criará a tabela de usuários:

rake db:migrate

Crie o model de Contato:

rails g model contato

Altere a migração db/migrate<horário_da_criação>_create_contatos.rb:

class CreateContatos < ActiveRecord::Migration
   def self.up
      create_table :contatos do |t|
        t.string :telefone, :limit => 20
        t.string :celular,  :limit => 20
        t.string :twitter,  :limit => 50
        t.string :facebook, :limit => 50
        t.string :msn,      :limit => 50
        t.timestamps
      end
   end

   def self.down
      drop_table :contatos
   end
end

No terminal execute o comando abaixo para rodar a migração que criará a tabela de contatos:

rake db:migrate

Crie o model para Anúncio:

rails g model anuncio

Altere a migração db/migrate<horário_da_criação>_create_anuncios.rb:

class CreateAnuncios < ActiveRecord::Migration
   def self.up
      create_table :anuncios do |t|
         t.text :descricao, :limit => 2048
         t.timestamps
      end
   end

   def self.down
      drop_table :anuncios
   end
end

No terminal execute o comando abaixo para rodar a migração que criará a tabela de anúncios:

rake db:migrate

Quase pronto! Os models estão criados e você pode visualizar as tabelas através do plugin SQLite Manager para o Firefox. Mas ainda falta acrescentar as associações entre os models e entre as tabelas.

Vamos então associar o contato ao usuário. Edite o model Usuário que se encontra no diretório app/models/usuario.rb:

class Usuario < ActiveRecord::Base
   has_one :contato
end

Agora edite o model Contato (app/models/contato.rb):

class Contato < ActiveRecord::Base
   belongs_to :usuario
end

Esta associação nos diz que um usuário possui um contato. Sendo assim vamos criar uma migração para adicionar este relacionamento na tabela.

rails g migration add_column_contato_usuario

E edite a migração criada em db/migrate/<horário_da_criação>_add_column_contato_usuario.rb:

class AddColumnContatoUsuario < ActiveRecord::Migration
   def self.up
      add_column :usuarios, :contato_id, :integer
   end

   def self.down
      remove_column :usuarios, :contato_id
   end
end

No terminal execute o comando para gerar a migração de atualização da tabela de usuários:

rake db:migrate

E para finalizar vamos associar o anúncio ao usuário.
Edite o model Usuário(app/models/usuario.rb) para indicar que um usuário possui vários anúncios:

class Usuario < ActiveRecord::Base
   has_one  :contato
   has_many :anuncios
end

Edite também o model Anúncio(app/models/anuncio.rb) para afirmar que ele pertence a um usuário:

class Anuncio < ActiveRecord::Base
   belongs_to :usuario
end

Crie a migração que irá adicionar o id do usuário ao anúncio:

rails g migration add_column_usuario_anuncio

Edite a migração db/migrate/<horário_da_criação>_add_column_usuario_anuncio.rb:

class AddColumnUsuarioAnuncio < ActiveRecord::Migration
   def self.up
      add_column :anuncios, :usuario_id, :intege
   end

   def self.down
      remove_column :anuncios, :usuario_id
   end
end

No terminal execute o comando para gerar a migração de atualização da tabela de anúncios:

rake db:migrate

Agora sim os models estão prontos para criarmos os testes e as validações, mas isto será assunto para o próximo post.

Para entender melhor o funcionamento dos models e suas associações não deixe de ler a apostila RR-71 da Caelum e a documentação da API ActiveRecord.

Até breve ;)

Desenvolvendo um leitor de RSS com jQuery Mobile

Olá pessoal.

Procurando por meios de começar o desenvolvimento para dispositivos móveis encontrei o Sencha Touch e o jQuery Mobile. Brinquei um pouco com Sencha Touch mas percebi que seria necessário estudar o ExtJs primeiro. Então preferi aprofundar no jQuery Mobile e utilizar o conhecimento que tenho sobre jQuery.

Resolvi fazer um leitor de RSS para o meu blog que pode ser acessado aqui, e o código completo encontra-se no meu GitHub.

Inicialmente tive um pouco de dificuldade em ler o feed, já que o RSS do WordPress não é disponibilizado como JSON. (Observação: Para quem possui uma instância do WordPress instalada, existem vários plugins para exportar o feed como JSON.) Após pesquisar encontrei o Google Feed API que faz o download do RSS em vários formatos, incluindo JSON o qual escolhi para fazer o meu leitor de feeds.

Vamos ao código:

Primeiro vamos configurar nossa página para utilizar os plugins necessários.

<!DOCTYPE html>
   <html>
      <head>
         <meta charset="utf-8" />
         <title>Reflexões Brenianas - Feed</title>
         <!-- CSS do jQuery Mobile -->
         <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
         <!-- jQuery -->
         <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
         <!-- jQuery Mobile -->
         <script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js">
         <!--
              Google Feed API
              Será necessário gera uma chave através do link http://code.google.com/apis/loader/signup.html
         -->
         <script type="text/javascript" src="https://www.google.com/jsapi?key=[SUA_CHAVE_GERADA]"></script>
      </head>

O corpo da página já utiliza as tags do HTML 5 (section, header, footer), mas também é possível fazer com divs.

   <body id="page_body">
      <!-- Início da home -->
      <section id="home" data-role="page">
         <!-- Cabeçalho -->
         <header data-role="header" data-position="fixed">
            <center>
               <h1>Reflexões Brenianas - Feed</h1>
            </center>
         </header>
         <!-- Conteúdo -->
         <div data-role="content">
            <ul data-role="listview">
            </ul>
         </div>
         <!-- Rodapé -->
         <footer data-role="footer" data-position="fixed">
            <center>
               <h3>monteirobrena</h3>
            </center>
         </footer>
      </section>
      <!-- Fim da home -->

Agora falta a codificação Javascript facilitada pelos plugins utilizados ;)

   <script type="text/javascript">
      // Carrega a API do Google para download do Feed.
      google.load("feeds", "1");

      // Inicializa a obtenção do feed.
      function initialize() {

         // Obtém o feed do blog.
         var feed = new google.feeds.Feed("http://monteirobrena.wordpress.com/feed");

         // Define a quantidade de entradas do feed que será buscada.
         feed.setNumEntries(10);

         // Carrega o arquivo do feed.
         feed.load(function(result) {

            // Obtém a página.
            var $page = $("#home");

            // Remove o conteúdo contido na div que utiliza a classe 'content'.
            $page.find(".content").empty();

            // Verifica se houve algum erro ao buscar o feed.
            if (result.error) {
               alert("Nenhum post encontrado.");
            }else{

               // Cria uma nova lista de posts.
               $page.find(".content").html("<ul></ul>");

               // Obtém a lista onde os posts serão adicionados.
               $list = $page.find(".content ul");

               // Percorre a lista de posts.
               for (var count = 0; count < result.feed.entries.length; count++) {

                  // Obtém o item do feed pelo índice.
                  var entry = result.feed.entries[count];

                  // Obtém o título do post.
                  var title = entry.title;

                  // Obtém o link do post.
                  var link = entry.link;

                  // Constrói o item da lista com o título do post e cria um link com o índice.
                  var strLink = '<li><a href="javascript:create_page(' + count + ',\'' + title + '\');">';

                  strLink += title;

                  strLink += '</a></li>\n';

                  // Atribui o html montado para um item.
                  var item = $(strLink);

                  // Adiciona o item na lista de posts.
                  $list.append(item);
            }
            // Atribui a lista montada à listview
            $list.listview();
         }
      });
   }
   // Chama a função que inicializa.
   google.setOnLoadCallback(initialize);

Com este código já conseguimos montar nossa home carregando uma lista com os posts.

Para finalizar falta criarmos uma página com o post no momento que um item da lista for clicado, ou melhor, quando ele receber um evento TAP.

// Cria uma página com o post
function create_page(page_id, title) {

   // Obtém o título.
   var titlePost = title;

   // Obtém o feed do blog.
   var feed = new google.feeds.Feed("http://monteirobrena.wordpress.com/feed");

   // Define a quantidade de entradas do feed que será buscada.
   feed.setNumEntries(10);

   // Carrega o arquivo do feed.
   feed.load(function(result) {

      // Get the content of page.
      var $page = $("#home");

      // Remove o conteúdo contido na div que utiliza a classe 'content'.
      $page.find(".content").empty();

      // Verifica se houve algum erro ao buscar o feed.
      if (result.error) {
         alert("Post não encontrado.");
      }else{
         // Percorre a lista de post.
         for (var count = 0; count < result.feed.entries.length; count++) {

            // Verifica se o título é igual.
            if (titlePost == result.feed.entries[count].title){

               // Obtém o item do feed pelo índice.
               var entry = result.feed.entries[page_id];

               // Obtém o conteúdo do post.
               var post = entry.content;

               // Monta a tela do post.
               var strPost = '<section id="page_' + page_id + '" data-role="page">';
               strPost += '<header data-role="header" data-position="fixed">';

               // Adiciona um link com o estilo de um botão de voltar que chama a função para recarregar a lista de posts.
               strPost += '<a href="#home" data-icon="back" onclick="initialize();">Voltar</a>';

               strPost += '<center><h1>Reflexões Brenianas - Feed</h1></center></header>';
               strPost += '<div data-role="content">';

               // Adiciona o título do post.
               strPost += '<h3>' + title + '</h3>';

               // Adiciona o conteúdo do post.
               strPost += post + '</div>';

               strPost += '<footer data-role="footer" data-position="fixed">';
               strPost += '<center><h3>monteirobrena</h3></center></footer></section>';

               // Adiciona o html montado à página.
               $('#page_body').append(strPost);

               // Inicializa a página.
               $.mobile.initializePage();

               // Navega até a página.
               $.mobile.changePage("#page_" + page_id, "slide", true, false);
            }
         }
      }
   });

Possivelmente deve existir formas mais fáceis para criar uma nova página sem precisar, por exemplo, criar novamente o section e poder reaproveitar o cabeçalho e o rodapé. Mas preferi mostrar um jeito mais didático.

A quem interessar e quiser aprender um pouco mais sobre jQuery Mobile recomendo a leitura do livro jQuery Mobile O’Reilly e jQuery Mobile First Look.

Até breve ;)

Engenharia de Software – Até onde se aplica?

Na pós-graduação de Engenharia de Software tenho aprendido o caminho das pedras para desenvolver software de qualidade seguindo os princípios propostos pela Engenharia de Software Clássica.

Entretanto seguir todas as etapas  desenvolvimento é muito custoso principalmente no tempo gasto com análise e projeto. Há quem diga que o tempo gasto nestas etapas é válido pois o software produzido terá mais qualidade do que um software que foi desenvolvido sem estas etapas.

Qualidade – resultado de um milhão de atos altruísta de importar-se (Clean code).

Recentemente li o livro Getting real, da 37Signals cuja leitura recomendo piamente, que afirma que as especificações por mais bem feitas que sejam, nunca representaram o real entendimento que uma tela consegue provocar.

Analisando estas duas vertentes podemos chegar a uma prévia conclusão:

- Para desenvolver um software inovador é necessário abrir mão de algumas premissas da Engenharia de Software.

E você, acha que essa conclusão é válida? Comente ;)

DevDay 2011

Ei pessoal.

Este é mais um post de divulgação de evento que será realizado em BH.
O DevDay 2011 acontecerá no dia 27/08 na UNA Campus Aimorés e abordará assuntos como:

  • Planejamento e acompanhamento ágil com Team Foundation
  • Qualidade de codificação
  • Windows Azure
  • TDD
  • Novidades do Rails 3.1
  • Programação funcional com C#
  • Evolução do Javascript

A inscrição pode ser feita no site http://devday.devisland.com

Bom evento pra todos ;)

1° Evento #HoraExtraBH

Neste sábado dia 9/7 acontecerá o 1° Evento #HoraExtraBH na Una – Campus Aimorés.

O evento será composto de paletras, cases, mesa-redonda e dojo.

Inscreva-se gratuitamente.

Programação:

  • 08:30 as 08:50 – Case 1 – Douglas aguiar – “consolidando uma nova cultura na empresa”
  • 09:00 as 09:40 – Palestra 1 – Edgard Davidson – “Porque virar professor?”
  • 09:50 as 10:10 – Case 2 – Herberth – “NOSQL + Python na Deskmetrics”
  • 10:20 as 11:00 – Case 3 – Rafael Spinola – Solucao com Servidores cloud
  • 11:10 as 11:30 – Case 4 – Dirceu Belem “Implantacao de aplicacoes para mobile como: ipad, iphone e android
  • 11:40 as 12:00 – Case 5 – Marcello Cardoso “Design centrado no usuario”
  • 12:00 as 13:00 – Mesa redonda
  • 13:00 as 14:00 – Pausa para almoco.
  • 14:00 as 16:30 – Dojo (Pra quem quiser ficar)

Até lá ;)

Oitavo #DevDojo – BH

Olá pessoal.

No 2° Café Ágil em BH foi iniciado o #horaextrabh, ainda não tive a oportunidade de comparecer, mas tenho acompanhado a lista do grupo. E foi lá que fiquei sabendo dos dojos que a DevIsland tem organizado.

O dojo irá acontecer no dia 28/05 de 9h e meia até às 12h, na UNA do Barro Preto (Rua Goitacazes, 1159, bairro Barro Preto). O tema será um dos quatros disponibilizados no formulário de inscrição.

E como prometi estou divulgando os eventos de desenvolvimento para os leitores.

Divulguem e compareçam.

Até lá ;)

Utilizando Devise em um projeto RoR

Olá pessoal.
Continuando meu propósito de criar um projeto real com RoR descobri um projeto no Github, denominado Devise que é uma solução para autenticação nos projetos RoR.
Além da página de documentação do Devise também utilizei o Railscast – Introducing Devise para enfim adicioná-lo no projeto.

Abra o terminal e instale o Devise:

$ gem install devise

Acesse a pasta do seu projeto Rails, procure pelo arquivo Gemfile e acrescente a linha:

gem 'devise'

Volte para o terminal e atualize as gems:

$ bundle install

Crie um scaffold simples:

$ rails generate scaffold project name:string

Execute as migrações:

$ rake db:migrate

Gere o devise para o projeto:

$ rails generate devise:install

Adicione a configuração default de e-mail no arquivo pastadoprojeto/config/environments/development.rb:

config.action_mailer.default_url_options = { :host => 'localhost:3000' }

Gere o devise para o model User:

$ rails generate devise User

Altere a classe do model User (pastadoprojeto/app/models/user.rb):

class User < ActiveRecord::Base

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation
end

Altere a migração DeviseCreateUsers (pastadoprojeto/db/migrate/…_devise_create_users.rb):

class DeviseCreateUsers < ActiveRecord::Migration
  def self.up
    create_table(:users) do |t|
      t.database_authenticatable :null => false
      t.recoverable
      t.rememberable
      t.trackable
      t.timestamps
    end

    add_index :users, :email,                :unique => true
    add_index :users, :reset_password_token, :unique => true
  end

  def self.down
    drop_table :users
  end
end

Execute as migrações:

$ rake db:migrate

Remova o arquivo index.html do diretório pastadoprojeto/public.

Adicione a rota no arquivo pastadoprojeto/config/routes.rb:

Tutorialdevise::Application.routes.draw do
  devise_for :users

  resources :projects
# Rota que redireciona para a página principal gerada pelo scaffold.
  root :to => "projects#index"

end

Execute as rotas:

$ rake routes

Por fim adicione a div user_nav no arquivo pastadoprojeto/app/views/layout/application.html.erb, que exibirá os links Sign out, Sign up e Sign in no topo de todas as páginas:

<!DOCTYPE html>
<html>
<head>
  <title>Tutorialdevise</title>
  <%= stylesheet_link_tag :all %>
  <%= javascript_include_tag :defaults %>
  <%= csrf_meta_tag %>
</head>
<body>
	<div id="user_nav">
		<% if user_signed_in? %>
			Signed in as <%= current_user.email %>. Not you?
			<%= link_to "Sign out", destroy_user_session_path %>
		<% else %>
			<%= link_to "Sign up", new_user_registration_path %> or <%= link_to "Sign in", new_user_session_path %>
		<% end %>
	</div>
<%= yield %>
</body>
</html>

Para consegui testar basta iniciar o servidor:

$ rails s

Agora abra o navegador, acesse http://localhost:3000/users/sign_up cadastre-se e depois acesse http://localhost:3000/users/sign_in para logar.
Nos dois casos as páginas serão redirecionadas para a página principal do scaffold, mas não se preocupe, isto pode ser alterado no arquivo de rotas.
Com isso já dá pra começar a brincar com o Devise.
Até breve ;)

Enquete – O usuário não sabe o que quer?

Compilação do 2° Café Ágil em BH – Parte 2

Olá pessoal.

Nesta segunda parte irei blogar sobre algo que me chamou bastante atenção na palestra do Mozair: As dúvidas sobre utilização das metodologias ágeis. Então vamos lá:

  • Como convencer os clientes a utilizar metodologias ágeis?

A principal vantagem para o cliente é o contrato mais maleável, ou seja ele poderá mudar de idéia durante o desenvolvimento sem precisar quebrar o contrato.

  • Como convencer os gerentes a utilizar metodologias ágeis?

Pergunte a ele quantas noites virou resolvendo bugs de sistemas em produção. As metodologias ágeis proporcionam qualidade de vida pro time ;)

  • Quais os artefatos que podem ser utilizados?

O principal é o épico, uma história grande com uma abordagem geral contendo o que é mais importante e o que mais agrega valor ao cliente. O épico ajuda identificar os possíveis problemas de implementação.

Desenhos, rascunhos, fluxogramas, metáforas também são válidos para identificar o que será feito e onde cada atividade se encaixa no projeto.

  • Como fazer o contrato?

O contrato pode ser fechado em features com tempo maleável ou com tempo fixo. Neste último será entregue o que for feito no prazo definido. Nota: Análise de Ponto de Função geralmente proporciona uma margem de erro maior.

É importante lembrar que:

  • Quanto mais longo o projeto menos você consegue identificar o que pode dar errado.
  • O crescimento do time causa overhead.
  • Experiência conta mais do que certificações.
  • As metodologias adotadas dependem do projeto e do cliente (Não há bala de prata).

Com isto encerro meu relato da palestra “Sticking to your principles”.

Gostou?! Discorda?! Comente!!!

Até breve ;)