Transformando os pepinos do cliente no código de testes da sua aplicação
1. Transformando os pepinos
do cliente no código de
testes da aplicação com
Cucumber
Rodrigo Urubatan
http://www.urubatan.com.br rodrigo@urubatan.com.br
2. 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"
4. Descobrindo os problemas
Reuniões com o cliente Cenários de uso do
sistema
Definição do Project
Backlog
Agile Business Analysis
User Stories
Lista do que deve ser feito
6. 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"
8. 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"
10. Qual o ferramental completo?
• Integração continua
• Testes de aceitação automatizados
• Relatório dos testes
• Deploy automatico
11. 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
12. 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
17. 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"
18. 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|
19. Executar as features existentes
rake cucumber
Mostra quais testes passaram e qais falharam e
porque.
27. 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
28. 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‟
29. 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 %>
30. 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
32. Dados de exemplo
Feature: Login
def path_to(page_name)
In order to make some money
As the service provider
case page_name
I want existing users to be able to access the system
when /the homes?page/
Background:
Given there is an user with name "admin" and password "admin"
'/'
when /the new user page/
Scenario: Login of existent user
new_user_path
Given I am on the login page
When I provide valid credentials when /the new session page/
And I press "Login" new_sessions_path
Then I should be redirected to "the home page"
Scenario: Login of inexistent user
Given I am on the login page Given /^there is an user with name "([^"]*)" and password
When I provide invalid credentials "([^"]*)"$/ do |login, password|
And I press "Login"
User.create :login => login, :password => password
Then I should be redirected to "the new session page"
end
34. 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
35. 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('/')
36. Configurar o Cucumber
require 'rspec'
require 'capybara/cucumber'
require 'capybara/session' # "before all"
require 'sqlite3' Before do
require "selenium-webdriver" db = SQLite3::Database.new "sample_db.sqlite3"
require 'cucumber/web/tableish' db.execute( "delete from users;" )
db.close
Capybara.default_selector = :css Capybara.current_driver = :selenium
Capybara.app_host = 'http://localhost:8080' end
Capybara.run_server = false
# "after all"
After do
Capybara.use_default_driver
end
37. Configurar os caminhos
Editar 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)
38. 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));");
}
}
39. 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();
}
}
40. 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
41. Criando o servlet de login
package sample_servlets; @Override
public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
private static final long serialVersionUID = 1L;
String login = req.getParameter("login");
String password = req.getParameter("password");
@Override
Connection conn = null;
protected void doGet(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException { try {
RequestDispatcher disp = req.getRequestDispatcher("WEB- Class.forName("org.sqlite.JDBC");conn =
INF/jsps/login.jsp"); DriverManager.getConnection("jdbc:sqlite:sample_db.sqlite3");
disp.forward(req, resp); 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");}
}}}
42. 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>
44. 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
46. Exemplo .NET
• Utilizando o • Utilizaremos as mesmas
VisualStudio.NET features e cenários
express • Primeiro
• .NET MVC 2 implementaremos o
• LINQ to SQL mapping Login
• Cucumber + Watir • Depois o gerenciamento
de usuários
• SQL Server Express Data
File
47. 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
48. 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
50. 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.
52. SQL Server pelo Ruby
require 'rubygems„
require „odbc„
@con = ODBC.connect('sample_db',nil,nil)
53. 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
54. 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
55. 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");
}
}
58. 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
59. Código para o Cucumber
When /^I fill in "([^"]*)" with "([^"]*)"$/ do |field,value| end
find_text_field field, value Then /^I should see the following users:$/ do |table|
end tbl = @browser.table(:id, 'usersList').to_a
Then /^I should see "([^"]*)"$/ do |value| idx = 1
raise "#{value} was not found in the document" unless table.hashes.each_with_index do |hash,index|
@browser.text.include? value
name = hash[:name]
end
login = hash[:login]
Given /^the following users:$/ do |table|
password = hash[:password]
table.hashes.each do |hash|
description = hash[:description]
name = hash[:name]
email = hash[:email]
login = hash[:login]
raise "unexpected users list" unless tbl[idx][2]==name
password = hash[:password]
raise "unexpected users list" unless tbl[idx][3]==login
description = hash[:description]
raise "unexpected users list" unless tbl[idx][4]==password
email = hash[:email]
raise "unexpected users list" unless tbl[idx][5]==description
@con.run( "insert into
users(name,login,password,description,email) values ( ?, raise "unexpected users list" unless tbl[idx][6]==email
?, ?, ?, ? )", name,login,password,description,email ) idx += 1
end end
end end
When /^I delete the 3rd user$/ do
@browser.goto path_to('the users page')
find_link('delete3')