KEMBAR78
Testing ASP.net and C# using Ruby (NDC2010) | PPTX
Testing C# and ASP.net using RubyMeerkatalyst@Ben_HallBen@BenHall.me.ukBlog.BenHall.me.ukCodeBetter.com/blogs/benhall
London based C# MVPWeb Developer @ 7digital.com Working on a number of Open Source ProjectsCo-Author of Testing ASP.net Web Applicationshttp://www.testingaspnet.com
Outside-in developmentUI level testingCucumberObject level testing using RSpec
WHY?http://www.flickr.com/photos/atomicpuppy/2132073976/
VALUE
State of .NET today
Realised Ruby doesn’t have as many problems as .net
Innovation
Outside-in Development
Behaviour Driven DevelopmentCucumber, RSpec and many others
Direct communication with customers
Focus
UI Testing is not hard...Unless you make an effort
What does this do?[Test]public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen(){    IE ie = new IE("http://localhost:49992/WatiNSite/");    ie.TextField(Find.ByName(“q")).TypeText("WatiN");    ie.Button(Find.ByValue("Search")).Click();    bool result = ie.Text.Contains("WatiN");    Assert.IsTrue(result); }
WebRathttp://www.flickr.com/photos/whatwhat/22624256/
visitclick_linkfill_inclick_buttoncheck and uncheckchooseselectattach_file
visit ‘http://localhost’
Reduce Ceremony
visit ‘http://www.google.com’fill_in :q, :with => ‘asp.net’click_button ‘Google Search’response_body.should contain(‘Microsoft’)
Design UI for testabilityOutside-in development encourages this
DATA!http://www.flickr.com/photos/gagilas/2659695352/
Hard and ImportantProduction data in dev anyone?
Active Record and SQL ServerOrder.create
class Order < ActiveRecord::Baseset_primary_key :OrderIDhas_many :OrderItemhas_one  :OrderStatus,              :primary_key => "OrderStatusID",              :foreign_key => "OrderStatusID"belongs_to :Customer,                   :primary_key => "UserName",                   :foreign_key => "UserName"  def self.create()    order = Order.new (:modified_on => DateTime.now,                                         :created_on => DateTime.now,                                          :order_id => Guid.new.to_s)order.OrderStatusID = OrderStatus.find_or_createorder.Customer = Customer.find_or_create(Faker::Internet.user_name)order.save!    return order  endendLegacy DatabaseLooking at auto-generatingmodel for testing
Test Data BuildersProductCatalogBuilder          .create(10.products)          .for_shop(123)        Customer        .create(“Bob”)        .owning(10.products)        .with_credit_card(CreditCard.create)        .with_address(Address.create)
Customer.find_or_create(Faker::Internet.user_name)>>> Faker::Internet.user_name"akeem_kris“>>> Faker::Internet.user_name"taylor.hodkiewicz“>>> Faker::Internet.user_name=> "nicolette.nitzsche">>> Faker::Internet.emaildejon@hayeswalsh.uk>>> Faker::Name.name=> "Murray Hermiston"
2 rules of data generationAlways insertAlways clean up
We still need to write the tests in a successful manner
Cucumberhttp://www.flickr.com/photos/vizzzual-dot-com/2738586453/
Feature:Search GoogleIn order to find informationAs a researcherI want to search google  Scenario: Searching for a valid termGivenI visit "http://www.google.com/"    WhenI enter a search for "asp.net"    AndI click the "Google Search" button    ThenI should see the first result "The Official Microsoft ASP.NET Site"
Given /^I visit "([^\"]*)"$/ do |website|  visit websiteend When /^I enter a search for "([^\"]*)"$/ do |term|  fill_in :q, :with => termend When /^I click the "([^\"]*)" button$/ do |button|  click_button buttonend Then /^I should see the first result "([^\"]*)"$/ do |result|  response_body.should contain(result)end
Thanks to TekPub for Kona sample
Thanks to TekPub for Kona sample
Feature: Display Products  In order to browse the catalog  The customer  Needs to see productsScenario: Single featured product on homepage    Given the featured product "Adventure Works 20x30 Binoculars"    When I visit the homepage    Then I should see "Adventure Works 20x30 Binoculars" listed under "Featured"
Given /^the featured product "([^\"]*)"$/ do |product_name|Product.createproduct_name, ‘featured’end
When /^I visit the homepage$/ dovisit url + '/'end
“Designed” UI for TestabilityThen /^I should see "([^\"]*)" listed under "([^\"]*)"$/ do |product, area|  within div_id(area) do |products|products.should contain(product)  endEnddef div_id(area)  "#" + area.downcase.underscoreend
Using a ‘real’ frameworkUnit testing frameworks for UI testing? Really?!
Before doopen_connectionempty_databaseendat_exit doempty_databaseEnddef empty_databasedate_of_last_restore = '01/01/2010'OrderItem.delete_all["DateAdded > ?", date_of_last_restore]Order.delete_all["CreatedOn > ?", date_of_last_restore]endMultiple Hooks
require 'cucumber_screenshot'After do |scenario|   screenshot if scenario.failed?endAfterStep('@screenshot') do |scenario|   screenshot if screenshot_due? end
def url  "http://www.website.local"endWebrat.configure do |config|config.mode = :seleniumconfig.application_framework = :externalendenv_selenium.rb
Webrat.configure do |config|config.mode = :seleniumconfig.application_framework = :externalconfig.selenium_browser_key = get_browser_keyenddef get_browser_key()  command = "*firefox"  command = ENV['BROWSER'] if ENV['BROWSER']  return commandendenv_selenium.rb
cucumber --profile selenium browser=*firefoxcucumber --profile selenium browser=*safaricucumber --profile selenium browser=*iexplorecucumber --profile selenium browser=*googlechrome
env_headless.rbWebrat.configure do |config|  config.mode = :mechanizeendcucumber --profile webrat
ANTI-PATTERNShttp://www.flickr.com/photos/gagilas/2659695352/
Noisy, repeated scenariosScenario: Blowout Specials    Given "Cascade Fur-lined Hiking Boots" in "Blowout Specials"    When I visit the homepage    Then I should see "Cascade Fur-lined Hiking Boots" listed under "Blowout Specials“Scenario: Blowout Specials    Given " Trailhead Locking Carabiner " in "Blowout Specials"    When I visit the homepage    Then I should see "Trailhead Locking Carabiner " listed under "Blowout Specials"Scenario: Blowout Specials    Given " Climbing Rope with Single Caribiner " in "Blowout Specials"    When I visit the homepage    Then I should see " Climbing Rope with Single Caribiner " listed under "Blowout Specials"
Single scenario, multiple examplesScenario Outline: Blowout Specials    Given <product> in "Blowout Specials"    When I visit the homepage    Then I should see <product> listed under "Blowout Specials"    Examples:      | product                                                         |      |  "Cascade Fur-lined Hiking Boots"           |      |  "Trailhead Locking Carabiner"                 |      |  "Climbing Rope with Single Caribiner"   |
Long scenariosWhen I go to the "Checkout" pageAnd I enter “Mr A. Nonymous ” in “Card Name” textboxAnd I enter “1111111111111111” in “Card Number” textboxAnd I enter “GU1 2PE” in “Card Post Code” textboxAnd I select “01” in “Card Start Month” textboxAnd I select “09” in “Card Start Year” textboxAnd I select “01” in “Card End Month” textboxAnd I select “19” in “Card End Year” textboxAnd I enter “123” in “Card Validation” textboxAnd I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
When I go to the "Checkout" pageAnd I enter a new credit card:       | type        | field                         | value                            |       | textbox   | Card Name            | Mr A. Nonymous       |       | textbox   | Card Number        | 4444333322221111 |       | textbox   | Card Post Code     | GU1 2PE                      |       | select      | Card Start Month | 01                                 |       | select      | Card Start Year      | 09                                 |       | select      | Card End Month   | 01                                 |       | select      | Card End Year        | 19                                 |       | textbox   | Card Validation      | 123                              |And I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
Clear, re-useable, readableWhen I go to the "Checkout" pageAnd I enter a valid credit card And I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
Implementation details in stepsWhen I go to the "Checkout" pageAnd I enter “Mr A. Nonymous ” in “Card Name” textboxAnd I enter “1111111111111111” in “Card Number” textboxAnd I enter “GU1 2PE” in “Card Post Code” textboxAnd I select “01” in “Card Start Month” textboxAnd I select “09” in “Card Start Year” textboxAnd I select “01” in “Card End Month” textboxAnd I select “19” in “Card End Year” textboxAnd I enter “123” in “Card Validation” textboxAnd I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
Business focusedWhen I go to the "Checkout"And I enter a valid credit card And payThen I should be taken to my Downloads
Conjunction StepsScenario: Recommendations should include other similar purchased products  Given customer order for "Trailhead Locking Carabiner" and "Climbing Rope with Single Caribiner"  And customer order for "Trailhead Locking Carabiner" and "North Star Compass"Given I have shades and a brand new Mustang
Reuse and recombineScenario: Recommendations should include other similar purchased products  Given the following customers and orders:   |customer      | order_list |   |Customer A  | Trailhead Locking Carabiner, Climbing Rope with Single Caribiner |   |Customer B  | Trailhead Locking Carabiner, North Star Compass |Given I have shades And I have a brand new Mustang
All steps within a single file
Steps focused around application domain
One environment file
Small, focused environment config
Multiple extension frameworksTest command line applicationsTest Silverlight\FlexTest WPF based desktop applicationsExecute in parallel across multiple machines
Lonestar
Outside-in Development
RSpechttp://www.flickr.com/photos/dodgsun/467076780/
Context \ Specification
Shift focusFinal Intent
Code CoverageFeature coverage is far more important
Ruby (via IronRuby)p = ProductController.new(nil)C#public class ProductController : Controller{IStoreService _service;   public ProductController(IStoreService service)   {      _service = service;   }}Thanks to TekPub for Kona sample
Define the contextC#public ActionResult Index(int? id){ProductListViewModel model = null;   if (id.HasValue)      model = _service.GetHomeModel(id.Value);   else      return RedirectToAction("Index", "Home");      return View(model);}Ruby (via IronRuby)describe ProductController, “Product homepage requested" do  context "when Category ID is empty" do  endendThanks to TekPub for Kona sample
Create the context$: << ‘../../Kona/bin/’require ‘Kona.dll’Include Kona::Controllersdescribe ProductController, “Product homepage requested" do  context "when Category ID is empty" do  endendThanks to TekPub for Kona sample
Create the contextrequire '../spec_helper‘describe ProductController, “Product homepage requested" do  context "when Category ID is empty" do  endendThanks to TekPub for Kona sample
Create the contextrequire '../spec_helper‘describe ProductController, “Product homepage requested" do  context "when Category ID is empty" do     before(:each) do        controller = ProductController.new nilcategory_id = nil        @result = controller.Indexcategory_id     end  endendThanks to TekPub for Kona sample
Define the specrequire '../spec_helper‘describe ProductController, “Product homepage requested" do  context “with empty Category ID" do     before(:each) do ... End    it "should redirect to main landing page" do         @result.route_values[‘controller'].should == ‘Index’	  @result.route_values[‘home'].should == ‘Home’    endendThanks to TekPub for Kona sample
Stubbing via caricaturecontext “with validCategory ID" do   Before(:each)view_model = product_list('Test Category')       service = isolate Services::IStoreServiceservice.when_receiving(:get_home_model).with(valid_category_id).return(view_model)       controller = ProductController.new service       @result = controller.Indexvalid_category_id   end   it “returns a view model” do      @result.view_model.should_not == nil   end   it “should return name of selected category” do      @result.view_model.selected_category.name.should == 'Test Category‘   endendThanks to TekPub for Kona sample
http://www.flickr.com/photos/buro9/298994863/WHY RUBY?
[Subject(typeof(HomeController))]public class when_the_navigation_controller_is_asked_for_the_header :           specification_for_navigation_controller{        static ActionResult result;        Establish context = () => identity_tasks.Stub(i => i.IsSignedIn()).Return(true);        Because of = () => result = subject.Menu();        It should_ask_the_identity_tasks_if_the_user_is_signed_in = () => identity_tasks.AssertWasCalled(x => x.IsSignedIn());        It should_return_the_default_view = () => result.ShouldBeAView().And().ViewName.ShouldBeEmpty();        It should_not_use_a_master_page = () => result.ShouldBeAView().And().MasterName.ShouldBeEmpty();        It should_set_the_view_model_property_to_a_new_menu_view_model = () =>result.Model<MenuViewModel>().ShouldNotBeNull();        It should_set_the_properties_of_the_view_model_correctly = () => result.Model<MenuViewModel>().IsLoggedIn.ShouldBeTrue();    }
describe HomeController, “When the navigation controller is asked for the header” do   before(:all) do       @identity_tasks.Stub(i => i.IsSignedIn()).Return(true);       @result = subject.Menu();    end   it “should ask the identity tasks if the user is signed in” do       @identity_tasks.did_receive(:is_signed_in) .should be_successful   end   it “should return the default view” do     @result.shouldbe_view     @result.view_name.shouldbe_empty   end   it “should not use a master page” do     @result.shouldbe_view     @result.master_name.shouldbe_empty   end  it “should set the view model property to a new menu view model” do     @result.shouldbe_view    @result.model.should_notbe_nil  end  it “should set the properties of the view model correctly” do     @result.model.is_logged_in.shouldbe_true  endend
Consider your test suite containing > 500 testsEach test matters.But each problem hurts more
http://www.flickr.com/photos/leon_homan/2856628778/
Focus on finding true valueLook at other communities for advice, support and inspiration
@Ben_HallBen@BenHall.me.ukBlog.BenHall.me.ukhttp://lolcatgenerator.com/lolcat/120/
Tests and Databasesdescribe Services::StoreService do  before(:all) dosession = NHibernate::create_sessionNHibernate::insert_category session    Services::StoreService.new session    @model = service.get_home_model 0  end  it "should return constructed object" do    @model.should_notbe_nil  end  it "should return all categories from database" do    @model.Categories.to_a.Count.should == 1  endendThanks to TekPub for Kona sample
Share specsshare_examples_for "unauthorized user" do    it 'sets an-error message' do result.view_model.error_message.should == "Sorry, you need to login to access."    end    it 'redirects to the login page' do result.view_name.should   end end describe CheckoutController do it_should_behave_like “unauthorized user“describe AccountController doit_should_behave_like “unauthorized user“
Dynamically create specsdef find_products(category)Product.Find(:all, :conditions=> “category #{category}”)enddescribe ‘products should be able to be added to basket' do    before(:all) do     @basket = Basket.new  endfind_products(‘Boots’).each do |p|    it “can add #{p.product_name} to basket" do      @basket.add_item p.id      @basket.should contain(p)     end  endend
INTEGRATION TESTShttp://www.flickr.com/photos/gagilas/2659695352/
Useful Linkshttp://www.github.com/BenHallhttp://blog.benhall.me.ukhttp://www.codebetter.com/blogs/benhallhttp://stevehodgkiss.com/2009/11/14/using-activerecord-migrator-standalone-with-sqlite-and-sqlserver-on-windows.htmlhttp://www.testingaspnet.comhttp://msdn.microsoft.com/en-us/magazine/dd434651.aspxhttp://msdn.microsoft.com/en-us/magazine/dd453038.aspxhttp://www.cukes.infohttp://github.com/aslakhellesoy/cucumberhttp://github.com/brynary/webrat
using Cuke4Nuke.Framework;usingNUnit.Framework;usingWatiN.Core;namespaceGoogle.StepDefinitions{    publicclassSearchSteps    {        Browser _browser;         [Before]        publicvoidSetUp()        {            _browser = new WatiN.Core.IE();        }        [After]        publicvoidTearDown()        {            if (_browser != null)            {                _browser.Dispose();            }        }        [When(@"^(?:I'm on|I go to) the search page$")]        publicvoidGoToSearchPage()        {            _browser.GoTo("http://www.google.com/");        }        [When("^I search for \"(.*)\"$")]        publicvoidSearchFor(string query)        {            _browser.TextField(Find.ByName("q")).TypeText(query);            _browser.Button(Find.ByName("btnG")).Click();        }        [Then("^I should be on the search page$")]        publicvoidIsOnSearchPage()        {            Assert.That(_browser.Title == "Google");        }        [Then("^I should see \"(.*)\" in the results$")]        publicvoidResultsContain(stringexpectedResult)        {            Assert.That(_browser.ContainsText(expectedResult));        }    }}
Given /^(?:I'm on|I go to) the search page$/ do  visit 'http://www.google.com'end When /^I search for "([^\"]*)"$/ do|query|  fill_in 'q', :with => query  click_button 'Google Search'end Then /^I should be on the search page$/ do dom.search('title').should == "Google"end Then /^I should see \"(.*)\" in the results$/ do|text|  response.should contain(text)end
SoftwareRecommended:IronRubyRubyCucumberRspecWebRatmechanizeSelenium RCselenium-clientCaricatureactiverecord-sqlserver-adapterOptional:XSP \ MonoJetBrain’sRubyMineJRubyCapybaraCelerityActive record active-record-model-generatorFakerGuid
SQL Server and Rubygem install activerecord-sqlserver-adapterDownload dbi-0.2.2.zip Extract dbd\ADO.rb to ruby\site_ruby\1.8\DBD\ADO.rb

Testing ASP.net and C# using Ruby (NDC2010)

  • 1.
    Testing C# andASP.net using RubyMeerkatalyst@Ben_HallBen@BenHall.me.ukBlog.BenHall.me.ukCodeBetter.com/blogs/benhall
  • 2.
    London based C#MVPWeb Developer @ 7digital.com Working on a number of Open Source ProjectsCo-Author of Testing ASP.net Web Applicationshttp://www.testingaspnet.com
  • 3.
    Outside-in developmentUI leveltestingCucumberObject level testing using RSpec
  • 4.
  • 5.
  • 6.
  • 7.
    Realised Ruby doesn’thave as many problems as .net
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 14.
    UI Testing isnot hard...Unless you make an effort
  • 16.
    What does thisdo?[Test]public void WatiNSearchText_SearchWatiN_TextDisplayedOnScreen(){    IE ie = new IE("http://localhost:49992/WatiNSite/");    ie.TextField(Find.ByName(“q")).TypeText("WatiN");    ie.Button(Find.ByValue("Search")).Click();    bool result = ie.Text.Contains("WatiN");    Assert.IsTrue(result); }
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    visit ‘http://www.google.com’fill_in :q,:with => ‘asp.net’click_button ‘Google Search’response_body.should contain(‘Microsoft’)
  • 23.
    Design UI fortestabilityOutside-in development encourages this
  • 24.
  • 25.
    Hard and ImportantProductiondata in dev anyone?
  • 26.
    Active Record andSQL ServerOrder.create
  • 27.
    class Order <ActiveRecord::Baseset_primary_key :OrderIDhas_many :OrderItemhas_one :OrderStatus, :primary_key => "OrderStatusID", :foreign_key => "OrderStatusID"belongs_to :Customer, :primary_key => "UserName", :foreign_key => "UserName" def self.create() order = Order.new (:modified_on => DateTime.now, :created_on => DateTime.now, :order_id => Guid.new.to_s)order.OrderStatusID = OrderStatus.find_or_createorder.Customer = Customer.find_or_create(Faker::Internet.user_name)order.save! return order endendLegacy DatabaseLooking at auto-generatingmodel for testing
  • 28.
    Test Data BuildersProductCatalogBuilder .create(10.products) .for_shop(123) Customer .create(“Bob”) .owning(10.products) .with_credit_card(CreditCard.create) .with_address(Address.create)
  • 29.
    Customer.find_or_create(Faker::Internet.user_name)>>> Faker::Internet.user_name"akeem_kris“>>> Faker::Internet.user_name"taylor.hodkiewicz“>>>Faker::Internet.user_name=> "nicolette.nitzsche">>> Faker::Internet.emaildejon@hayeswalsh.uk>>> Faker::Name.name=> "Murray Hermiston"
  • 30.
    2 rules ofdata generationAlways insertAlways clean up
  • 31.
    We still needto write the tests in a successful manner
  • 32.
  • 33.
    Feature:Search GoogleIn orderto find informationAs a researcherI want to search google  Scenario: Searching for a valid termGivenI visit "http://www.google.com/"    WhenI enter a search for "asp.net"    AndI click the "Google Search" button    ThenI should see the first result "The Official Microsoft ASP.NET Site"
  • 34.
    Given /^I visit"([^\"]*)"$/ do |website|  visit websiteend When /^I enter a search for "([^\"]*)"$/ do |term|  fill_in :q, :with => termend When /^I click the "([^\"]*)" button$/ do |button|  click_button buttonend Then /^I should see the first result "([^\"]*)"$/ do |result|  response_body.should contain(result)end
  • 35.
    Thanks to TekPubfor Kona sample
  • 36.
    Thanks to TekPubfor Kona sample
  • 37.
    Feature: Display Products In order to browse the catalog The customer Needs to see productsScenario: Single featured product on homepage Given the featured product "Adventure Works 20x30 Binoculars" When I visit the homepage Then I should see "Adventure Works 20x30 Binoculars" listed under "Featured"
  • 38.
    Given /^the featuredproduct "([^\"]*)"$/ do |product_name|Product.createproduct_name, ‘featured’end
  • 39.
    When /^I visitthe homepage$/ dovisit url + '/'end
  • 40.
    “Designed” UI forTestabilityThen /^I should see "([^\"]*)" listed under "([^\"]*)"$/ do |product, area| within div_id(area) do |products|products.should contain(product) endEnddef div_id(area) "#" + area.downcase.underscoreend
  • 41.
    Using a ‘real’frameworkUnit testing frameworks for UI testing? Really?!
  • 42.
    Before doopen_connectionempty_databaseendat_exit doempty_databaseEnddefempty_databasedate_of_last_restore = '01/01/2010'OrderItem.delete_all["DateAdded > ?", date_of_last_restore]Order.delete_all["CreatedOn > ?", date_of_last_restore]endMultiple Hooks
  • 43.
    require 'cucumber_screenshot'After do|scenario| screenshot if scenario.failed?endAfterStep('@screenshot') do |scenario| screenshot if screenshot_due? end
  • 44.
    def url "http://www.website.local"endWebrat.configure do |config|config.mode = :seleniumconfig.application_framework = :externalendenv_selenium.rb
  • 45.
    Webrat.configure do |config|config.mode= :seleniumconfig.application_framework = :externalconfig.selenium_browser_key = get_browser_keyenddef get_browser_key() command = "*firefox" command = ENV['BROWSER'] if ENV['BROWSER'] return commandendenv_selenium.rb
  • 46.
    cucumber --profile seleniumbrowser=*firefoxcucumber --profile selenium browser=*safaricucumber --profile selenium browser=*iexplorecucumber --profile selenium browser=*googlechrome
  • 47.
    env_headless.rbWebrat.configure do |config|  config.mode= :mechanizeendcucumber --profile webrat
  • 48.
  • 49.
    Noisy, repeated scenariosScenario:Blowout Specials Given "Cascade Fur-lined Hiking Boots" in "Blowout Specials" When I visit the homepage Then I should see "Cascade Fur-lined Hiking Boots" listed under "Blowout Specials“Scenario: Blowout Specials Given " Trailhead Locking Carabiner " in "Blowout Specials" When I visit the homepage Then I should see "Trailhead Locking Carabiner " listed under "Blowout Specials"Scenario: Blowout Specials Given " Climbing Rope with Single Caribiner " in "Blowout Specials" When I visit the homepage Then I should see " Climbing Rope with Single Caribiner " listed under "Blowout Specials"
  • 50.
    Single scenario, multipleexamplesScenario Outline: Blowout Specials Given <product> in "Blowout Specials" When I visit the homepage Then I should see <product> listed under "Blowout Specials" Examples: | product | | "Cascade Fur-lined Hiking Boots" | | "Trailhead Locking Carabiner" | | "Climbing Rope with Single Caribiner" |
  • 51.
    Long scenariosWhen Igo to the "Checkout" pageAnd I enter “Mr A. Nonymous ” in “Card Name” textboxAnd I enter “1111111111111111” in “Card Number” textboxAnd I enter “GU1 2PE” in “Card Post Code” textboxAnd I select “01” in “Card Start Month” textboxAnd I select “09” in “Card Start Year” textboxAnd I select “01” in “Card End Month” textboxAnd I select “19” in “Card End Year” textboxAnd I enter “123” in “Card Validation” textboxAnd I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
  • 52.
    When I goto the "Checkout" pageAnd I enter a new credit card: | type       | field     | value             | | textbox   | Card Name         | Mr A. Nonymous   | | textbox   | Card Number       | 4444333322221111 | | textbox   | Card Post Code   | GU1 2PE           | | select     | Card Start Month | 01               | | select   | Card Start Year  | 09               | | select     | Card End Month   | 01               | | select   | Card End Year    | 19               | | textbox   | Card Validation  | 123              |And I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
  • 53.
    Clear, re-useable, readableWhenI go to the "Checkout" pageAnd I enter a valid credit card And I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
  • 54.
    Implementation details instepsWhen I go to the "Checkout" pageAnd I enter “Mr A. Nonymous ” in “Card Name” textboxAnd I enter “1111111111111111” in “Card Number” textboxAnd I enter “GU1 2PE” in “Card Post Code” textboxAnd I select “01” in “Card Start Month” textboxAnd I select “09” in “Card Start Year” textboxAnd I select “01” in “Card End Month” textboxAnd I select “19” in “Card End Year” textboxAnd I enter “123” in “Card Validation” textboxAnd I click the "Pay with Card (Add Card)" buttonThen I should be on the "Download" page
  • 55.
    Business focusedWhen Igo to the "Checkout"And I enter a valid credit card And payThen I should be taken to my Downloads
  • 56.
    Conjunction StepsScenario: Recommendationsshould include other similar purchased products Given customer order for "Trailhead Locking Carabiner" and "Climbing Rope with Single Caribiner" And customer order for "Trailhead Locking Carabiner" and "North Star Compass"Given I have shades and a brand new Mustang
  • 57.
    Reuse and recombineScenario:Recommendations should include other similar purchased products Given the following customers and orders: |customer | order_list | |Customer A | Trailhead Locking Carabiner, Climbing Rope with Single Caribiner | |Customer B | Trailhead Locking Carabiner, North Star Compass |Given I have shades And I have a brand new Mustang
  • 58.
    All steps withina single file
  • 59.
    Steps focused aroundapplication domain
  • 60.
  • 61.
  • 62.
    Multiple extension frameworksTestcommand line applicationsTest Silverlight\FlexTest WPF based desktop applicationsExecute in parallel across multiple machines
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
    Code CoverageFeature coverageis far more important
  • 70.
    Ruby (via IronRuby)p= ProductController.new(nil)C#public class ProductController : Controller{IStoreService _service; public ProductController(IStoreService service) { _service = service; }}Thanks to TekPub for Kona sample
  • 71.
    Define the contextC#publicActionResult Index(int? id){ProductListViewModel model = null; if (id.HasValue) model = _service.GetHomeModel(id.Value); else return RedirectToAction("Index", "Home"); return View(model);}Ruby (via IronRuby)describe ProductController, “Product homepage requested" do context "when Category ID is empty" do endendThanks to TekPub for Kona sample
  • 72.
    Create the context$:<< ‘../../Kona/bin/’require ‘Kona.dll’Include Kona::Controllersdescribe ProductController, “Product homepage requested" do context "when Category ID is empty" do endendThanks to TekPub for Kona sample
  • 73.
    Create the contextrequire'../spec_helper‘describe ProductController, “Product homepage requested" do context "when Category ID is empty" do endendThanks to TekPub for Kona sample
  • 74.
    Create the contextrequire'../spec_helper‘describe ProductController, “Product homepage requested" do context "when Category ID is empty" do before(:each) do controller = ProductController.new nilcategory_id = nil @result = controller.Indexcategory_id end endendThanks to TekPub for Kona sample
  • 75.
    Define the specrequire'../spec_helper‘describe ProductController, “Product homepage requested" do context “with empty Category ID" do before(:each) do ... End it "should redirect to main landing page" do @result.route_values[‘controller'].should == ‘Index’ @result.route_values[‘home'].should == ‘Home’ endendThanks to TekPub for Kona sample
  • 76.
    Stubbing via caricaturecontext“with validCategory ID" do Before(:each)view_model = product_list('Test Category') service = isolate Services::IStoreServiceservice.when_receiving(:get_home_model).with(valid_category_id).return(view_model) controller = ProductController.new service @result = controller.Indexvalid_category_id end it “returns a view model” do @result.view_model.should_not == nil end it “should return name of selected category” do @result.view_model.selected_category.name.should == 'Test Category‘ endendThanks to TekPub for Kona sample
  • 77.
  • 78.
    [Subject(typeof(HomeController))]public class when_the_navigation_controller_is_asked_for_the_header: specification_for_navigation_controller{ static ActionResult result; Establish context = () => identity_tasks.Stub(i => i.IsSignedIn()).Return(true); Because of = () => result = subject.Menu(); It should_ask_the_identity_tasks_if_the_user_is_signed_in = () => identity_tasks.AssertWasCalled(x => x.IsSignedIn()); It should_return_the_default_view = () => result.ShouldBeAView().And().ViewName.ShouldBeEmpty(); It should_not_use_a_master_page = () => result.ShouldBeAView().And().MasterName.ShouldBeEmpty(); It should_set_the_view_model_property_to_a_new_menu_view_model = () =>result.Model<MenuViewModel>().ShouldNotBeNull(); It should_set_the_properties_of_the_view_model_correctly = () => result.Model<MenuViewModel>().IsLoggedIn.ShouldBeTrue(); }
  • 79.
    describe HomeController, “Whenthe navigation controller is asked for the header” do before(:all) do @identity_tasks.Stub(i => i.IsSignedIn()).Return(true); @result = subject.Menu(); end it “should ask the identity tasks if the user is signed in” do @identity_tasks.did_receive(:is_signed_in) .should be_successful end it “should return the default view” do @result.shouldbe_view @result.view_name.shouldbe_empty end it “should not use a master page” do @result.shouldbe_view @result.master_name.shouldbe_empty end it “should set the view model property to a new menu view model” do @result.shouldbe_view @result.model.should_notbe_nil end it “should set the properties of the view model correctly” do @result.model.is_logged_in.shouldbe_true endend
  • 80.
    Consider your testsuite containing > 500 testsEach test matters.But each problem hurts more
  • 81.
  • 82.
    Focus on findingtrue valueLook at other communities for advice, support and inspiration
  • 83.
  • 84.
    Tests and DatabasesdescribeServices::StoreService do before(:all) dosession = NHibernate::create_sessionNHibernate::insert_category session Services::StoreService.new session @model = service.get_home_model 0 end it "should return constructed object" do @model.should_notbe_nil end it "should return all categories from database" do @model.Categories.to_a.Count.should == 1 endendThanks to TekPub for Kona sample
  • 85.
    Share specsshare_examples_for "unauthorizeduser" do it 'sets an-error message' do result.view_model.error_message.should == "Sorry, you need to login to access." end it 'redirects to the login page' do result.view_name.should end end describe CheckoutController do it_should_behave_like “unauthorized user“describe AccountController doit_should_behave_like “unauthorized user“
  • 86.
    Dynamically create specsdeffind_products(category)Product.Find(:all, :conditions=> “category #{category}”)enddescribe ‘products should be able to be added to basket' do before(:all) do @basket = Basket.new endfind_products(‘Boots’).each do |p| it “can add #{p.product_name} to basket" do @basket.add_item p.id @basket.should contain(p) end endend
  • 87.
  • 88.
  • 89.
    using Cuke4Nuke.Framework;usingNUnit.Framework;usingWatiN.Core;namespaceGoogle.StepDefinitions{    publicclassSearchSteps    {        Browser _browser; [Before]        publicvoidSetUp()        {            _browser = new WatiN.Core.IE();        } [After]        publicvoidTearDown()        {            if (_browser != null)            {                _browser.Dispose();            }        } [When(@"^(?:I'm on|I go to) the search page$")]        publicvoidGoToSearchPage()        {            _browser.GoTo("http://www.google.com/");        } [When("^I search for \"(.*)\"$")]        publicvoidSearchFor(string query)        {            _browser.TextField(Find.ByName("q")).TypeText(query);            _browser.Button(Find.ByName("btnG")).Click();        } [Then("^I should be on the search page$")]        publicvoidIsOnSearchPage()        {            Assert.That(_browser.Title == "Google");        } [Then("^I should see \"(.*)\" in the results$")]        publicvoidResultsContain(stringexpectedResult)        {            Assert.That(_browser.ContainsText(expectedResult));        }    }}
  • 90.
    Given /^(?:I'm on|Igo to) the search page$/ do  visit 'http://www.google.com'end When /^I search for "([^\"]*)"$/ do|query|  fill_in 'q', :with => query  click_button 'Google Search'end Then /^I should be on the search page$/ do dom.search('title').should == "Google"end Then /^I should see \"(.*)\" in the results$/ do|text|  response.should contain(text)end
  • 91.
    SoftwareRecommended:IronRubyRubyCucumberRspecWebRatmechanizeSelenium RCselenium-clientCaricatureactiverecord-sqlserver-adapterOptional:XSP \MonoJetBrain’sRubyMineJRubyCapybaraCelerityActive record active-record-model-generatorFakerGuid
  • 92.
    SQL Server andRubygem install activerecord-sqlserver-adapterDownload dbi-0.2.2.zip Extract dbd\ADO.rb to ruby\site_ruby\1.8\DBD\ADO.rb

Editor's Notes

  • #6 Both to the customer and to the teamCost of defects to 0Every test needs to justify itselfBut not just justify, add value both during the initial development and going forward. After working on a number of large scale .net based automation projects I started to question how much value I was adding to the project. This lead me to look at other communities as inspiration
  • #9 More abstracted away... As such nicer for testingMore fluid, more human readable.Perfect for tests. This is why I looked at how I can start using Ruby both in practice and mindset
  • #11 System brings together different parts to solve a particular problem
  • #41 get_div_id == “Design UI for Testability”
  • #43 get_div_id == “Design UI for Testability”
  • #44 get_div_id == “Design UI for Testability”
  • #47 get_div_id == “Design UI for Testability”
  • #48 get_div_id == “Design UI for Testability”
  • #50 get_div_id == “Design UI for Testability”
  • #51 get_div_id == “Design UI for Testability”
  • #52 get_div_id == “Design UI for Testability”
  • #53 get_div_id == “Design UI for Testability”
  • #54 get_div_id == “Design UI for Testability”
  • #56 get_div_id == “Design UI for Testability”
  • #67 Often forgot in the .Net world...Started to come more maintain stream thanks to Aaron Jenson and Scott BellwareBounded contexts
  • #68 Behaviour can be isolated units like TDDBut also accept that it might involve a number of other classes Defining the behaviour of our system, means we aren’t too focused on
  • #69 Feature Coverage
  • #79 http://whocanhelpme.codeplex.com/Good example of how to use Mspec with MVC. Sadly, I think Rspec has a nicer syntax.Naming + tests are very good.