Testing ASP.net and C# using Ruby (NDC2010)
Upcoming SlideShare
Loading in...5
×
 

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

on

  • 3,646 views

Session given at NDC2010, Oslo, Norway on Testing ASP.net using Ruby

Session given at NDC2010, Oslo, Norway on Testing ASP.net using Ruby

Statistics

Views

Total Views
3,646
Views on SlideShare
3,643
Embed Views
3

Actions

Likes
0
Downloads
12
Comments
0

1 Embed 3

http://localhost 3

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • 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
  • 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
  • System brings together different parts to solve a particular problem
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • get_div_id == “Design UI for Testability”
  • Often forgot in the .Net world...Started to come more maintain stream thanks to Aaron Jenson and Scott BellwareBounded contexts
  • 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
  • Feature Coverage
  • 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.

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

  • Testing C# and ASP.net using Ruby
    Meerkatalyst
    @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 Projects
    Co-Author of
    Testing ASP.net Web Applications
    http://www.testingaspnet.com
  • Outside-in development
    UI level testing
    Cucumber
    Object 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 Development
    Cucumber, 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); }
  • WebRat
    http://www.flickr.com/photos/whatwhat/22624256/
  • visit
    click_link
    fill_in
    click_button
    check and uncheck
    choose
    select
    attach_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 testability
    Outside-in development encourages this
  • DATA!
    http://www.flickr.com/photos/gagilas/2659695352/
  • Hard and Important
    Production data in dev anyone?
  • Active Record and SQL Server
    Order.create
  • class Order < ActiveRecord::Base
    set_primary_key :OrderID
    has_many :OrderItem
    has_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_create
    order.Customer = Customer.find_or_create(Faker::Internet.user_name)
    order.save!
    return order
    end
    end
    Legacy Database
    Looking at auto-generating
    model for testing
  • Test Data Builders
    ProductCatalogBuilder
    .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.email
    • dejon@hayeswalsh.uk
    >>> Faker::Name.name
    => "Murray Hermiston"
  • 2 rules of data generation
    Always insert
    Always clean up
  • We still need to write the tests in a successful manner
  • Cucumber
    http://www.flickr.com/photos/vizzzual-dot-com/2738586453/
  • Feature:Search Google
    In order to find information
    As a researcher
    I want to search google
     
     Scenario: Searching for a valid term
    GivenI 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 website
    end
     
    When /^I enter a search for "([^"]*)"$/ do |term|
      fill_in :q, :with => term
    end
     
    When /^I click the "([^"]*)" button$/ do |button|
      click_button button
    end
     
    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 products
    Scenario: 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$/ do
    visit url + '/'
    end
  • “Designed” UI for Testability
    Then /^I should see "([^"]*)" listed under "([^"]*)"$/ do |product, area|
    within div_id(area) do |products|
    products.should contain(product)
    end
    End
    def div_id(area)
    "#" + area.downcase.underscore
    end
  • Using a ‘real’ framework
    Unit testing frameworks for UI testing? Really?!
  • Before do
    open_connection
    empty_database
    end
    at_exit do
    empty_database
    End
    def empty_database
    date_of_last_restore = '01/01/2010'
    OrderItem.delete_all["DateAdded > ?", date_of_last_restore]
    Order.delete_all["CreatedOn > ?", date_of_last_restore]
    end
    Multiple Hooks
  • require 'cucumber_screenshot'
    After do |scenario|
    screenshot if scenario.failed?
    end
    AfterStep('@screenshot') do |scenario|
    screenshot if screenshot_due?
    end
  • def url
    "http://www.website.local"
    end
    Webrat.configure do |config|
    config.mode = :selenium
    config.application_framework = :external
    end
    env_selenium.rb
  • Webrat.configure do |config|
    config.mode = :selenium
    config.application_framework = :external
    config.selenium_browser_key = get_browser_key
    end
    def get_browser_key()
    command = "*firefox"
    command = ENV['BROWSER'] if ENV['BROWSER']
    return command
    end
    env_selenium.rb
  • cucumber --profile selenium browser=*firefox
    cucumber --profile selenium browser=*safari
    cucumber --profile selenium browser=*iexplore
    cucumber --profile selenium browser=*googlechrome
  • env_headless.rb
    Webrat.configure do |config|
      config.mode = :mechanize
    end
    cucumber --profile webrat
  • ANTI-PATTERNS
    http://www.flickr.com/photos/gagilas/2659695352/
  • Noisy, repeated scenarios
    Scenario: 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 examples
    Scenario 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 scenarios
    When I go to the "Checkout" page
    And I enter “Mr A. Nonymous ” in “Card Name” textbox
    And I enter “1111111111111111” in “Card Number” textbox
    And I enter “GU1 2PE” in “Card Post Code” textbox
    And I select “01” in “Card Start Month” textbox
    And I select “09” in “Card Start Year” textbox
    And I select “01” in “Card End Month” textbox
    And I select “19” in “Card End Year” textbox
    And I enter “123” in “Card Validation” textbox
    And I click the "Pay with Card (Add Card)" button
    Then I should be on the "Download" page
  • When I go to the "Checkout" page
    And 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)" button
    Then I should be on the "Download" page
  • Clear, re-useable, readable
    When I go to the "Checkout" page
    And I enter a valid credit card
    And I click the "Pay with Card (Add Card)" button
    Then I should be on the "Download" page
  • Implementation details in steps
    When I go to the "Checkout" page
    And I enter “Mr A. Nonymous ” in “Card Name” textbox
    And I enter “1111111111111111” in “Card Number” textbox
    And I enter “GU1 2PE” in “Card Post Code” textbox
    And I select “01” in “Card Start Month” textbox
    And I select “09” in “Card Start Year” textbox
    And I select “01” in “Card End Month” textbox
    And I select “19” in “Card End Year” textbox
    And I enter “123” in “Card Validation” textbox
    And I click the "Pay with Card (Add Card)" button
    Then I should be on the "Download" page
  • Business focused
    When I go to the "Checkout"
    And I enter a valid credit card
    And pay
    Then I should be taken to my Downloads
  • Conjunction Steps
    Scenario: 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 recombine
    Scenario: 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 frameworks
    Test command line applications
    Test SilverlightFlex
    Test WPF based desktop applications
    Execute in parallel across multiple machines
  • Lonestar
  • Outside-in Development
  • RSpec
    http://www.flickr.com/photos/dodgsun/467076780/
  • Context Specification
  • Shift focus
    Final Intent
  • Code Coverage
    Feature 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 context
    C#
    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
    end
    end
    Thanks to TekPub for Kona sample
  • Create the context
    $: << ‘../../Kona/bin/’
    require ‘Kona.dll’
    Include Kona::Controllers
    describe ProductController, “Product homepage requested" do
    context "when Category ID is empty" do
    end
    end
    Thanks to TekPub for Kona sample
  • Create the context
    require '../spec_helper‘
    describe ProductController, “Product homepage requested" do
    context "when Category ID is empty" do
    end
    end
    Thanks to TekPub for Kona sample
  • Create the context
    require '../spec_helper‘
    describe ProductController, “Product homepage requested" do
    context "when Category ID is empty" do
    before(:each) do
    controller = ProductController.new nil
    category_id = nil
    @result = controller.Indexcategory_id
    end
    end
    end
    Thanks to TekPub for Kona sample
  • Define the spec
    require '../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’
    end
    end
    Thanks to TekPub for Kona sample
  • Stubbing via caricature
    context “with validCategory ID" do
    Before(:each)
    view_model = product_list('Test Category')
    service = isolate Services::IStoreService
    service.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‘
    end
    end
    Thanks 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 tests
    Each test matters.
    But each problem hurts more
  • http://www.flickr.com/photos/leon_homan/2856628778/
  • Focus on finding true value
    Look at other communities for advice, support and inspiration
  • @Ben_Hall
    Ben@BenHall.me.uk
    Blog.BenHall.me.uk
    http://lolcatgenerator.com/lolcat/120/
  • Tests and Databases
    describe Services::StoreService do
    before(:all) do
    session = NHibernate::create_session
    NHibernate::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
    end
    end
    Thanks to TekPub for Kona sample
  • Share specs
    share_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 specs
    def 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 TESTS
    http://www.flickr.com/photos/gagilas/2659695352/
  • Useful Links
    http://www.github.com/BenHall
    http://blog.benhall.me.uk
    http://www.codebetter.com/blogs/benhall
    http://stevehodgkiss.com/2009/11/14/using-activerecord-migrator-standalone-with-sqlite-and-sqlserver-on-windows.html
    http://www.testingaspnet.com
    http://msdn.microsoft.com/en-us/magazine/dd434651.aspx
    http://msdn.microsoft.com/en-us/magazine/dd453038.aspx
    http://www.cukes.info
    http://github.com/aslakhellesoy/cucumber
    http://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
  • Software
    Recommended:
    IronRuby
    Ruby
    Cucumber
    Rspec
    WebRat
    mechanize
    Selenium RC
    selenium-client
    Caricature
    activerecord-sqlserver-adapter
    Optional:
    XSP Mono
    JetBrain’sRubyMine
    JRuby
    Capybara
    Celerity
    Active record
    active-record-model-generator
    Faker
    Guid
  • SQL Server and Ruby
    gem install activerecord-sqlserver-adapter
    Download dbi-0.2.2.zip
    Extract dbdADO.rb to rubysite_ruby1.8DBDADO.rb