Upload
rodrigo-urubatan
View
3.576
Download
0
Embed Size (px)
DESCRIPTION
Slides da palestra apresentada no AgileBrazil 2010 sobre BDD, Cucumber e testes de aplicações .NET, Java e Rails com o cucumber
Citation preview
http://www.urubatan.com.br [email protected]
Transformando os pepinos do cliente no código de testes da aplicação com
Cucumber
Rodrigo Urubatan
Sobre Urubatan
Trabalho com desenvolvimento desde 1997, já desenvolvi sistemas em diversas linguagens, como Delphi, C, C++, PHP, ASP, ColdFusion, Assembly, Leather, Java e Ruby.
Atualmente trabalho com pesquisa e desenvolvimento na HP, utilizando principalmente Java, e com Ruby em outros projetos e cursos.
Alem de ser o autor do livro "Ruby On Rails: Desenvolvimento fácil e Rápido de aplicações web"
O cliente tem um problema a resolver
Descobrindo os problemas
Reuniões com o cliente
Definição do Project
Backlog
Agile Business Analysis
User Stories
Lista do que deve ser feito
Cenários de uso do
sistema
Behavior Driven Development
Cenário: Login
Scenario: Login of existent user
Given I am on the login page
When I provide valid credentials
And I press "Login"
Then I should be redirected to "the home
page"
Pensando melhor na feature
Feature Login
Feature: Login
In order to make some money
As the service provider
I want existing users to be able to access the system
Scenario: Login of existent user
Given I am on the login page
When I provide valid credentials
And I press "Login"
Then I should be redirected to "the home page"
Scenario: Login of inexistent user
Given I am on the login page
When I provide invalid credentials
And I press "Login"
Then I should be redirected to "the login page"
Tudo faz parte de um conjunto
Qual o ferramental completo?
• Integração continua
• Testes de aceitação automatizados
• Relatório dos testes
• Deploy automatico
Ciclo de implementação
1. Montar o backlog de features a serem implementadas
2. Priorizar as features
3. Pegar uma das features para implementar
4. Escrever os cenários/Testes de aceitação para a feature
5. Executar os cenários
6. Escrever código o suficiente para um cenário/teste passar
7. Executar os cenários novamente
8. Repetir passos 6 e 7 até que todos os cenários estejam passando
Exemplo com Ruby on Rails
1. Criar uma aplicação Rails
2. Configurar o suporte ao cucumber
3. Criar features
4. Executar os testes
5. Implementar as features
6. Executar os testes
7. Repetir passos 4 a 6 até que o sistema esteja pronto
Criar uma aplicação Rails
rails new rails_sample
Configurar o ambiente e a aplicação
Instalar gems:
gem install rspec –version 2.0.0.beta.8
gem install rspec-rails –version 2.0.0.beta.8
gem install capybara database_cleaner cucumber-railscucumber spork launchy
Remover o arquivo “Gemfile”
Executar:
ruby script/rails generate cucumber:install --capybara --rspec
Feature Scaffold
rails generate cucumber:feature user name:string
login:string password:string description:text
email:string
Geração espontânea de testes
Feature: Manage users
In order to [goal]
[stakeholder]
wants [behaviour]
Scenario: Register new user
Given I am on the new user page
When I fill in "Name" with "name 1"
And I fill in "Login" with "login 1"
And I fill in "Password" with "password 1"
And I fill in "Description" with "description 1"
And I fill in "Email" with "email 1"
And I press "Create"
Then I should see "name 1"
And I should see "login 1"
And I should see "password 1"
And I should see "description 1"
And I should see "email 1"
Scenario: Delete user
Given the following users:
|name|login|password|description|email|
|name 1|login 1|password 1|description 1|email 1|
|name 2|login 2|password 2|description 2|email 2|
|name 3|login 3|password 3|description 3|email 3|
|name 4|login 4|password 4|description 4|email 4|
When I delete the 3rd user
Then I should see the following users:
|Name|Login|Password|Description|Email|
|name 1|login 1|password 1|description 1|email 1|
|name 2|login 2|password 2|description 2|email 2|
|name 4|login 4|password 4|description 4|email 4|
Executar as features existentes
rake cucumber
Mostra quais testes passaram e qais falharam e
porque.
cucumber –f pretty
Implementar as features
rails generate scaffold user name:string login:string
password:string description:text email:string
Cadastro Automágico
Executar as features existentes
rake cucumber
Mostra quais testes passaram e qais falharam e
porque.
cucumber –f pretty
De volta ao Login
Pedindo ajuda ao Cucumber
Cucumber Ruby Back-End
When /^I provide valid credentials$/ do
fill_in('login', :with => 'admin')
fill_in('password', :with => 'admin')
end
Then /^I should be redirected to "([^\"]*)"$/ do |page_name|
current_path = URI.parse(current_url).path
current_path.should == path_to(page_name)
end
When /^I provide invalid credentials$/ do
fill_in('login', :with => 'admin1')
fill_in('password', :with => 'admin')
end
Implementando a tela de login
rails generate controller session new
Editando o arquivo routes.rb
resource :sessions, :controller => :session
resources :users
match 'login' => redirect('/sessions/new'), :as =>
:login
root :to => „users#index‟
Editando a view (new.html.erb)
<%= form_tag :action => :create do %>
<label for="login">Login:</label><input
type="text" id="login" name="login"/><br/>
<label for="password">Password:</label><input
type="password" id="password"
name="password"/><br/>
<input type="submit" value="Login"/>
<% end %>
O controller (session_controller.rb)
class SessionController < ApplicationController
def new
end
def create
login = params[:login]
password = params[:password]
u = User.where("login = :login and password = :password", :login => login, :password => password).first
if u
redirect_to root_path
else
redirect_to new_sessions_path
end
end
end
Resultado
Dados de exemplo
Feature: Login
In order to make some money
As the service provider
I want existing users to be able to access the system
Background:
Given there is an user with name "admin" and password "admin"
Scenario: Login of existent user
Given I am on the login page
When I provide valid credentials
And I press "Login"
Then I should be redirected to "the home page"
Scenario: Login of inexistent user
Given I am on the login page
When I provide invalid credentials
And I press "Login"
Then I should be redirected to "the new session page"
def path_to(page_name)
case page_name
when /the home\s?page/
'/'
when /the new user page/
new_user_path
when /the new session page/
new_sessions_path
Given /^there is an user with name "([^\"]*)" and password
"([^\"]*)"$/ do |login, password|
User.create :login => login, :password => password
end
Tudo Pronto!
Exemplo Web com Java
1. Criar um projeto Web Dinâmico com eclipse (ou outra IDE Java)
2. Copiar a pasta features do projeto Rails
3. Configurar cucumber para testar aplicação Java
4. Executar cucumber
5. Implementar Login
6. Executar cucumber
7. Implementar cadastro de usuários
8. Executar cucumber
Automação do browser
require 'capybara'
require 'capybara/dsl'
include Capybara
Capybara.current_driver = :selenium
Capybara.app_host = 'http://www.google.com'
Capybara.run_server = false
visit('/')
Configurar o Cucumber
require 'rspec'
require 'capybara/cucumber'
require 'capybara/session'
require 'sqlite3'
require "selenium-webdriver"
require 'cucumber/web/tableish'
Capybara.default_selector = :css
Capybara.app_host = 'http://localhost:8080'
Capybara.run_server = false
# "before all"
Before do
db = SQLite3::Database.new "sample_db.sqlite3"
db.execute( "delete from users;" )
db.close
Capybara.current_driver = :selenium
end
# "after all"
After do
Capybara.use_default_driver
end
Configurar os caminhosEditar o arquivo paths.rb para que fique assim:
module NavigationHelpers
def path_to(page_name)
case page_name
when /the home page/
'/'
when /the login page/
'/login'
when /the new user page/
'/users/new'
when /the users page/
'/users/'
when /the new session page/
'/login'
else
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
"Now, go and add a mapping in #{__FILE__}"
end
end
end
World(NavigationHelpers)
Setup do Banco de dados
package commandLine;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class DbSetup {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite:sample_db.sqlite3");
Statement stat = conn.createStatement();
stat.executeUpdate("drop table if exists users;");
stat.executeUpdate("create table users (name varchar(200), login varchar(200), password varchar(200), description text, email varchar(200));");
}
}
Servidor HTTP Embedded
package commandLine;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;
public class Main {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
WebAppContext wac = new WebAppContext("WebContent", "/");
HandlerCollection handlers = new HandlerCollection();
Handler[] handlerArray = new Handler[] {wac, new DefaultHandler()};
handlers.setHandlers(handlerArray);
server.setHandler(handlers);
server.start();
}
}
Alteração do login_steps.rb
require 'sqlite3‘
When /^I provide valid credentials$/ do
fill_in('login', :with => 'admin')
fill_in('password', :with => 'admin')
end
Then /^I should be redirected to "([ \̂"]*)"$/ do |page_name|
current_path = URI.parse(current_url).path
current_path.should == path_to(page_name)
end
When /^I provide invalid credentials$/ do
fill_in('login', :with => 'admin1')
fill_in('password', :with => 'admin')
end
Given /^there is an user with name "([^\"]*)" and password "([^\"]*)"$/ do |login, password|
db = SQLite3::Database.new "sample_db.sqlite3"
db.execute( "insert into users(login,password) values ( ?, ? )", login, password )
db.close
end
Criando o servlet de login
package sample_servlets;
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher disp = req.getRequestDispatcher("WEB-INF/jsps/login.jsp");
disp.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String login = req.getParameter("login");
String password = req.getParameter("password");
Connection conn = null;
try {
Class.forName("org.sqlite.JDBC");conn = DriverManager.getConnection("jdbc:sqlite:sample_db.sqlite3");
PreparedStatement pstmt = conn.prepareStatement("select * from users where login=? and password=?");
pstmt.setString(1, login);pstmt.setString(2, password);ResultSet query = pstmt.executeQuery();
if (query.next()) {resp.sendRedirect("/");} else {resp.sendRedirect("/login");}
} catch (Exception e) {e.printStackTrace();resp.sendRedirect("/login");} finally {
if (conn != null)
try {conn.close();} catch (SQLException e) {e.printStackTrace();
resp.sendRedirect("/login");}
}}}
Criação da página de login
Criar o arquivo WEB-INF/jsps/login.jsp com o seguinte conteúdo:
<html>
<head>
<title>Sample Login Page</title>
</head>
<body>
<form method="POST"><label for="login">Login</label><input
type="text" id="login" name="login" /><br />
<label for="password">Password</label><input type="password"
id="password" name="password" /><br />
<input type="submit" value="Login"/></form>
</body>
</html>
Executar o cucumber
cucumber features\login.feature
Implementando o Gerenciamento de
usuários
• Alterar os steps do cucumber para que insiram os
dados no banco correto
• Exectar o cucumber
• Criar um servlet UsersServlet
• Criar as JSPs necessárias
• Executar o cucumber
Cucumber gerenciamento de usuários
Exemplo .NET
• Utilizando o
VisualStudio.NET
express
• .NET MVC 2
• LINQ to SQL mapping
• Cucumber + Watir
• SQL Server Express Data
File
• Utilizaremos as mesmas
features e cenários
• Primeiro
implementaremos o
Login
• Depois o gerenciamento
de usuários
Criação do projeto
• Criar um novo projeto C# MVC 2 Blank
• Criar um controller de nome HomeController,
uma View de nome Index para este controller e
uma MasterPage
• Dentro de Model criar um “New Item” “Link to
SQL Classes” de nome DataClasses1
Dados para o projeto
• Criar um banco SQL Server de nome sample_db
• Criar um Data Connection para este banco de dados
• Criar um DSN ODBC para o mesmo banco
• Em Data Connections, na pasta Tables criar uma nova tabela de nome users com os campos
• id, int, identity
• name, nchar(200)
• login, nchar(200)
• password, nchar(200)
• email, nchar(200)
• description, ntext
• Arrastar a tabela para o LINQ Designer e renomear para Users
Instalação de dependencias
gem install watir ruby-odbc
Configuração do cucumber
• Baixar exemplos do Watircuke de http://github.com/richdownie/watircuke
• Baixar arquivos de features/support/ para o mesmo diretório no projeto
• env.rb
• paths.rb
• watircuke.rb
• Criar diretório features/step_definitions
• Copiar os arquivos .feature do projeto de exemplo rails.
Executando o Cucumber
SQL Server pelo Ruby
require 'rubygems„
require „odbc„
@con = ODBC.connect('sample_db',nil,nil)
Começando a brincadeira
• Criar o arquivo
step_definitions/cucumber_steps.rb
• Colocar todos os steps que o cucumber disse
estarem pending quando executado pelo console
• Fazer o “merge” de todos os passos similares
utilizando as expressões regulares
Given /^there is an user with name "([^\"]*)" and password "([^\"]*)"$/ do |login, password|
p = @con.proc("insert into users(login, password) values (?, ?)") {}
p.call(login, password)
end
Given /^I am on (.*)$/ do |page|
@browser.goto path_to(page)
end
When /^I provide valid credentials$/ do
find_text_field('login','admin')
find_text_field('password','admin')
end
When /^I press "([^\"]*)"$/ do |label|
find_button label
end
Then /^I should be redirected to "([^\"]*)"$/ do |page|
raise "Not on the expected page" unless @browser.url == path_to(page)
end
When /^I provide invalid credentials$/ do
find_text_field('login','admin2')
find_text_field('password','admin2')
end
Criar o LoginController
DataClasses1DataContext dataClasses = new DataClasses1DataContext();
public ActionResult Index()
{
return View();
}
public ActionResult Create(String login, String password)
{
try{
Users u = dataClasses.Users.First(usr => usr.login == login && usr.password == password);
return Redirect("http://localhost:1467/");
}
catch (Exception e)
{
return RedirectToAction("Index");
}
}
Criar a view Login/Index.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/ViewMasterPage1.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
System Login
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2><%: ViewData["Message"] %></h2>
<form method="post" action="Login/Create">
<p> <label for="login">Login</label><input type="text" id="login" name="login"/> </p>
<p> <label for="password">Password</label><input type="password" id="password“ ame="password"/> </p>
<p><input type="submit" value="Login" /></p>
</form>
</asp:Content>
Criando o Cadastro de Usuários
• Criar um UsersController com os métodos padrão
• Implementar os métodos da forma mais simples
possível
• Implementar as views
• Executar o cucumber novamente para testar a
aplicação
Código para o CucumberWhen /^I fill in "([ \̂"]*)" with "([^\"]*)"$/ do |field,value|
find_text_field field, value
end
Then /^I should see "([^\"]*)"$/ do |value|
raise "#{value} was not found in the document" unless @browser.text.include? value
end
Given /^the following users:$/ do |table|
table.hashes.eachdo |hash|
name = hash[:name]
login = hash[:login]
password = hash[:password]
description = hash[:description]
email = hash[:email]
@con.run( "insert into users(name,login,password,description,email) values ( ?, ?, ?, ?, ? )", name,login,password,description,email )
end
end
When /^I delete the 3rd user$/ do
@browser.goto path_to('the users page')
find_link('delete3')
end
Then /^I should see the following users:$/ do |table|
tbl = @browser.table(:id, 'usersList').to_a
idx = 1
table.hashes.each_with_index do |hash,index|
name = hash[:name]
login = hash[:login]
password = hash[:password]
description = hash[:description]
email = hash[:email]
raise "unexpected users list" unless tbl[idx][2]==name
raise "unexpected users list" unless tbl[idx][3]==login
raise "unexpected users list" unless tbl[idx][4]==password
raise "unexpected users list" unless tbl[idx][5]==description
raise "unexpected users list" unless tbl[idx][6]==email
idx += 1
end
end
Executando o Cucumber
Referências
• Meu livro - http://livro.urubatan.com.br• Meu blog - http://www.urubatan.com.br• Cucumber - http://wiki.github.com/aslakhellesoy/cucumber• Capybara- http://github.com/jnicklas/capybara• WebDriver -
http://code.google.com/p/selenium/wiki/RubyBindings• Watir - http://watir.com/• Watircuke - http://github.com/nofxx/watircuke• Rails – http://rubyonrails.org• ASP.NET MVC - http://www.asp.net/mvc