Transformando os pepinos do cliente no código de testes da sua aplicação

3,814 views
3,722 views

Published on

Slides da palestra apresentada no AgileBrazil 2010 sobre BDD, Cucumber e testes de aplicações .NET, Java e Rails com o cucumber

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,814
On SlideShare
0
From Embeds
0
Number of Embeds
835
Actions
Shares
0
Downloads
79
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Transformando os pepinos do cliente no código de testes da sua aplicação

  1. 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. 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"
  3. 3. O cliente tem um problema a resolver
  4. 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
  5. 5. Behavior Driven Development
  6. 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"
  7. 7. Pensando melhor na feature
  8. 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"
  9. 9. Tudo faz parte de um conjunto
  10. 10. Qual o ferramental completo? • Integração continua • Testes de aceitação automatizados • Relatório dos testes • Deploy automatico
  11. 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. 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
  13. 13. Criar uma aplicação Rails rails new rails_sample
  14. 14. 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-rails cucumber spork launchy Remover o arquivo “Gemfile” Executar: ruby script/rails generate cucumber:install --capybara --rspec
  15. 15. Feature Scaffold rails generate cucumber:feature user name:string login:string password:string description:text email:string
  16. 16. Geração espontânea de testes
  17. 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. 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. 19. Executar as features existentes rake cucumber Mostra quais testes passaram e qais falharam e porque.
  20. 20. cucumber –f pretty
  21. 21. Implementar as features rails generate scaffold user name:string login:string password:string description:text email:string
  22. 22. Cadastro Automágico
  23. 23. Executar as features existentes rake cucumber Mostra quais testes passaram e qais falharam e porque.
  24. 24. cucumber –f pretty
  25. 25. De volta ao Login
  26. 26. Pedindo ajuda ao Cucumber
  27. 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. 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. 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. 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
  31. 31. Resultado
  32. 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
  33. 33. Tudo Pronto!
  34. 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. 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. 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. 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. 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. 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. 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. 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. 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>
  43. 43. Executar o cucumber cucumber featureslogin.feature
  44. 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
  45. 45. Cucumber gerenciamento de usuários
  46. 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. 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. 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
  49. 49. Instalação de dependencias gem install watir ruby-odbc
  50. 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.
  51. 51. Executando o Cucumber
  52. 52. SQL Server pelo Ruby require 'rubygems„ require „odbc„ @con = ODBC.connect('sample_db',nil,nil)
  53. 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. 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. 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"); } }
  56. 56. 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>
  57. 57. 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
  58. 58. 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')
  59. 59. Executando o Cucumber
  60. 60. http://www.urubatan.com.br rodrigo@urubatan.com.br
  61. 61. 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

×