testing asp.net and c# using ruby (ndc2010)

92
Testing C# and ASP.net using Ruby @Ben_Hall [email protected] Blog.BenHall.me.uk CodeBetter.com/blogs/benhall Meerkatalyst

Upload: ben-hall

Post on 12-Jan-2015

3.939 views

Category:

Technology


3 download

DESCRIPTION

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

TRANSCRIPT

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

Testing C# and ASP.net using Ruby

@[email protected]/blogs/benhall

Meerkatalyst

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

London based C# MVPWeb Developer @ 7digital.com

Working on a number of Open Source Projects

Co-Author of Testing ASP.net Web Applicationshttp://www.testingaspnet.com

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

• Outside-in development–UI level testing–Cucumber–Object level testing using RSpec

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

WHY?

http://www.flickr.com/photos/atomicpuppy/2132073976/

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

VALUE

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

State of .NET today

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

Realised Ruby doesn’t have as many problems as .net

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

Innovation

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

Outside-in Development

Acceptance Tests

Specifications• ‘Unit Tests’

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

Behaviour Driven Development

Cucumber, RSpec and many others

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

Direct communication with customers

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

Focus

Page 13: Testing ASP.net and C# using Ruby (NDC2010)
Page 14: Testing ASP.net and C# using Ruby (NDC2010)

UI Testing is not hard...

Unless you make an effort

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

Test

Browser

Your ASP.net web application

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

[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); }

What does this do?

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

Application specific DSL

UI Testing DSL

Browser Automation

Browser

Your ASP.net web application

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

WebRat

http://www.flickr.com/photos/whatwhat/22624256/

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

visitclick_link

fill_inclick_button

check and uncheckchooseselect

attach_file

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

visit ‘http://localhost’

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

Reduce Ceremony

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

visit ‘http://www.google.com’fill_in :q, :with => ‘asp.net’click_button ‘Google Search’response_body.should contain(‘Microsoft’)

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

Design UI for testability

Outside-in development encourages this

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

http://www.flickr.com/photos/gagilas/2659695352/

DATA!

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

Hard and Important

Production data in dev anyone?

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

Active Record and SQL Server

Order.create

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

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 endend

Legacy DatabaseLooking at auto-generatingmodel for testing

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

ProductCatalogBuilder .create(10.products) .for_shop(123)

Customer .create(“Bob”) .owning(10.products) .with_credit_card(CreditCard.create) .with_address(Address.create)

Test Data Builders

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

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Þ [email protected]>>> Faker::Name.name=> "Murray Hermiston"

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

2 rules of data generation

Always insertAlways clean up

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

We still need to write the tests in a successful manner

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

Cucumber

http://www.flickr.com/photos/vizzzual-dot-com/2738586453/

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

Feature: Search GoogleIn order to find informationAs a researcherI want to search google Scenario: Searching for a valid term Given I visit "http://www.google.com/" When I enter a search for "asp.net" And I click the "Google Search" button Then I should see the first result "The Official

Microsoft ASP.NET Site"

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

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

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

Thanks to TekPub for Kona sample

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

Thanks to TekPub for Kona sample

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

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"

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

Given /^the featured product "([^\"]*)"$/ do |product_name|

Product.create product_name, ‘featured’end

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

When /^I visit the homepage$/ do visit url + '/'end

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

Then /^I should see "([^\"]*)" listed under "([^\"]*)"$/ do |product, area|

within div_id(area) do |products| products.should contain(product) endEnd

def div_id(area) "#" + area.downcase.underscoreend

“Designed” UI for Testability

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

Using a ‘real’ framework

Unit testing frameworks for UI testing? Really?!

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

Before do open_connection empty_databaseend

at_exit do empty_databaseEnd

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

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

require 'cucumber_screenshot'After do |scenario| screenshot if scenario.failed?end

AfterStep('@screenshot') do |scenario| screenshot if screenshot_due? end

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

def url "http://www.website.local"end

Webrat.configure do |config| config.mode = :selenium config.application_framework = :externalend

env_selenium.rb

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

Webrat.configure do |config| config.mode = :selenium config.application_framework = :external

config.selenium_browser_key = get_browser_keyend

def get_browser_key() command = "*firefox" command = ENV['BROWSER'] if ENV['BROWSER'] return commandend

env_selenium.rb

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

cucumber --profile selenium browser=*firefoxcucumber --profile selenium browser=*safaricucumber --profile selenium browser=*iexplorecucumber --profile selenium browser=*googlechrome

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

cucumber --profile webrat

Webrat.configure do |config| config.mode = :mechanizeend

env_headless.rb

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

http://www.flickr.com/photos/gagilas/2659695352/

ANTI-PATTERNS

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

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"

Noisy, repeated scenarios

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

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" |

Single scenario, multiple examples

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

When 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

Long scenarios

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

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

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

When 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

Clear, re-useable, readable

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

When 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

Implementation details in steps

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

When I go to the "Checkout"And I enter a valid credit card And payThen I should be taken to my Downloads

Business focused

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

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"

Conjunction Steps

Given I have shades and a brand new Mustang

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

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 |

Reuse and recombine

Given I have shades And I have a brand new Mustang

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

All steps within a single file

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

Steps focused around application domain

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

One environment file

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

Small, focused environment config

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

Multiple extension frameworks

Test command line applicationsTest Silverlight\Flex

Test WPF based desktop applications

Execute in parallel across multiple machines

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

Lonestar

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

Outside-in Development

Acceptance Tests

Specifications• ‘Unit Tests’

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

RSpec

http://www.flickr.com/photos/dodgsun/467076780/

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

Context \ Specification

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

Shift focus

Final Intent

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

Code Coverage

Feature coverage is far more important

Page 69: Testing ASP.net and C# using Ruby (NDC2010)
Page 70: Testing ASP.net and C# using Ruby (NDC2010)

C#public class ProductController : Controller{ IStoreService _service;

public ProductController(IStoreService service) { _service = service; }}

Ruby (via IronRuby)p = ProductController.new(nil)

Thanks to TekPub for Kona sample

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

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 endend

Thanks to TekPub for Kona sample

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

Create the context

$: << ‘../../Kona/bin/’require ‘Kona.dll’Include Kona::Controllers

describe ProductController, “Product homepage requested" do context "when Category ID is empty" do endend

Thanks to TekPub for Kona sample

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

Create the context

require '../spec_helper‘

describe ProductController, “Product homepage requested" do context "when Category ID is empty" do endend

Thanks to TekPub for Kona sample

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

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.Index category_id end endend

Thanks to TekPub for Kona sample

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

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’ endend

Thanks to TekPub for Kona sample

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

Stubbing via caricaturecontext “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.Index valid_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‘ endend Thanks to TekPub for Kona sample

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

WHY RUBY?http://www.flickr.com/photos/buro9/298994863/

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

[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(); }

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

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.should be_view @result.view_name.should be_empty end

it “should not use a master page” do @result.should be_view @result.master_name.should be_empty end

it “should set the view model property to a new menu view model” do @result.should be_view @result.model.should_not be_nil end it “should set the properties of the view model correctly” do @result.model.is_logged_in.should be_true endend

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

Consider your test suite containing > 500 tests

Each test matters.But each problem hurts more

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

http://www.flickr.com/photos/leon_homan/2856628778/

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

Focus on finding true value

Look at other communities for advice, support and inspiration

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

@[email protected]

http://lolcatgenerator.com/lolcat/120/

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

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_not be_nil end

it "should return all categories from database" do @model.Categories.to_a.Count.should == 1 endend

Thanks to TekPub for Kona sample

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

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 do it_should_behave_like “unauthorized user“

Share specs

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

def find_products(category) Product.Find(:all, :conditions => “category #{category}”)end describe ‘products should be able to be added to basket' do before(:all) do @basket = Basket.new end

find_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

Dynamically create specs

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

http://www.flickr.com/photos/gagilas/2659695352/

INTEGRATION TESTS

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

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

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

using Cuke4Nuke.Framework;using NUnit.Framework;using WatiN.Core;namespace Google.StepDefinitions{ public class SearchSteps { Browser _browser; [Before] public void SetUp() { _browser = new WatiN.Core.IE(); } [After] public void TearDown() { if (_browser != null) { _browser.Dispose(); } } [When(@"^(?:I'm on|I go to) the search page$")] public void GoToSearchPage() { _browser.GoTo("http://www.google.com/"); }

[When("^I search for \"(.*)\"$")] public void SearchFor(string query) { _browser.TextField(Find.ByName("q")).TypeText(query); _browser.Button(Find.ByName("btnG")).Click(); } [Then("^I should be on the search page$")] public void IsOnSearchPage() { Assert.That(_browser.Title == "Google"); } [Then("^I should see \"(.*)\" in the results$")] public void ResultsContain(string expectedResult) { Assert.That(_browser.ContainsText(expectedResult)); } }}

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

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

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

Software

• Recommended:– IronRuby– Ruby– Cucumber– Rspec– WebRat– mechanize– Selenium RC– selenium-client– Caricature– activerecord-sqlserver-

adapter

• Optional:– XSP \ Mono– JetBrain’s RubyMine– JRuby – Capybara– Celerity– Active record – active-record-model-

generator– Faker– Guid

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

SQL Server and Ruby

1. gem install activerecord-sqlserver-adapter1. Download dbi-0.2.2.zip 2. Extract dbd\ADO.rb to ruby\site_ruby\1.8\DBD\

ADO.rb