Testing ASP.net Web Applications using Ruby

Post on 18-May-2015

1.621 views 2 download

Tags:

description

Presentation of Testing ASP.net using Ruby I gave at DDD8, January 30th 2010.

Transcript of Testing ASP.net Web Applications using Ruby

Testing ASP.net using Ruby

@Ben_HallBen@BenHall.me.ukBlog.BenHall.me.uk

Meerkatalyst

1| Why you should care2| Object Level testing3| UI level testing

What do I mean by Testing ASP.net?

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

WHY TEST?

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

It is 2010. Automated testing is no longer controversial.

[TestMethod]public void EditAction_Retrieves_Dinner_1_From_Repo_And_Countries_And_Sets_DinnerViewModel() { // Arrange var controller = CreateDinnersControllerAs("someuser");

// Act ViewResult result = controller.Edit(1) as ViewResult;

// Assert DinnerFormViewModel model = result.ViewData.Model as DinnerFormViewModel; Assert.AreEqual(13, model.Countries.Count());}

http://nerddinner.codeplex.com/SourceControl/changeset/view/23425#439968

[TestMethod]public void EditAction_Retrieves_Dinner_1_From_Repo_And_Countries_And_Sets_DinnerViewModel() { // Arrange var controller = CreateDinnersControllerAs("someuser");

// Act ViewResult result = controller.Edit(1) as ViewResult;

// Assert DinnerFormViewModel model = result.ViewData.Model as DinnerFormViewModel; Assert.AreEqual(13, model.Countries.Count());}

http://nerddinner.codeplex.com/SourceControl/changeset/view/23425#439968

[Fact] public void RsdReturnsValidRsdDoc() { FakeAreaService areaService = new FakeAreaService();

areaService.StoredAreas.Add("test", new Oxite.Models.Area(false, DateTime.MinValue, null, null, Guid.NewGuid(), DateTime.MinValue, "test"));

RouteCollection routes = new RouteCollection();

routes.Add("Posts", new Route("", new MvcRouteHandler()));

UrlHelper helper = new UrlHelper(new RequestContext(new FakeHttpContext(new Uri("http://oxite.net/"),"~/"), new RouteData()), routes);

Site site = new Site() { Host = new Uri("http://oxite.net") };

AreaController controller = new AreaController(site, areaService, null, null, null, null, null) { Url = helper };

ContentResult result = controller.Rsd("test");

Assert.NotNull(result);

XDocument rsdDoc = XDocument.Parse(result.Content); XNamespace rsdNamespace = "http://archipelago.phrasewise.com/rsd"; XElement rootElement = rsdDoc.Element(rsdNamespace + "rsd");

Assert.NotNull(rootElement); Assert.NotNull(rootElement.Attribute("version")); Assert.Equal("1.0", rootElement.Attribute("version").Value); Assert.Equal("Oxite", rootElement.Descendants(rsdNamespace + "engineName").SingleOrDefault().Value); Assert.Equal("http://oxite.net", rootElement.Descendants(rsdNamespace + "engineLink").SingleOrDefault().Value); Assert.Equal("http://oxite.net/", rootElement.Descendants(rsdNamespace + "homePageLink").SingleOrDefault().Value);

XElement apisElement = rootElement.Descendants(rsdNamespace + "apis").SingleOrDefault(); Assert.NotNull(apisElement); Assert.Equal(1, apisElement.Elements().Count());

XElement apiElement = apisElement.Elements().SingleOrDefault(); Assert.NotNull(apiElement); Assert.Equal(rsdNamespace + "api", apiElement.Name); Assert.Equal("MetaWeblog", apiElement.Attribute("name").Value); Assert.Equal(areaService.StoredAreas["test"].ID.ToString("N"), apiElement.Attribute("blogID").Value); Assert.Equal("true", apiElement.Attribute("preferred").Value); Assert.Equal("http://oxite.net/MetaWeblog.svc", apiElement.Attribute("apiLink").Value); }

http://oxite.codeplex.com/SourceControl/changeset/view/54721#419183

[Fact] public void RsdReturnsValidRsdDoc() { FakeAreaService areaService = new FakeAreaService();

areaService.StoredAreas.Add("test", new Oxite.Models.Area(false, DateTime.MinValue, null, null, Guid.NewGuid(), DateTime.MinValue, "test"));

RouteCollection routes = new RouteCollection();

routes.Add("Posts", new Route("", new MvcRouteHandler()));

UrlHelper helper = new UrlHelper(new RequestContext(new FakeHttpContext(new Uri("http://oxite.net/"),"~/"), new RouteData()), routes);

Site site = new Site() { Host = new Uri("http://oxite.net") };

AreaController controller = new AreaController(site, areaService, null, null, null, null, null) { Url = helper };

ContentResult result = controller.Rsd("test");

Assert.NotNull(result);

XDocument rsdDoc = XDocument.Parse(result.Content); XNamespace rsdNamespace = "http://archipelago.phrasewise.com/rsd"; XElement rootElement = rsdDoc.Element(rsdNamespace + "rsd");

Assert.NotNull(rootElement); Assert.NotNull(rootElement.Attribute("version")); Assert.Equal("1.0", rootElement.Attribute("version").Value); Assert.Equal("Oxite", rootElement.Descendants(rsdNamespace + "engineName").SingleOrDefault().Value); Assert.Equal("http://oxite.net", rootElement.Descendants(rsdNamespace + "engineLink").SingleOrDefault().Value); Assert.Equal("http://oxite.net/", rootElement.Descendants(rsdNamespace + "homePageLink").SingleOrDefault().Value);

XElement apisElement = rootElement.Descendants(rsdNamespace + "apis").SingleOrDefault(); Assert.NotNull(apisElement); Assert.Equal(1, apisElement.Elements().Count());

XElement apiElement = apisElement.Elements().SingleOrDefault(); Assert.NotNull(apiElement); Assert.Equal(rsdNamespace + "api", apiElement.Name); Assert.Equal("MetaWeblog", apiElement.Attribute("name").Value); Assert.Equal(areaService.StoredAreas["test"].ID.ToString("N"), apiElement.Attribute("blogID").Value); Assert.Equal("true", apiElement.Attribute("preferred").Value); Assert.Equal("http://oxite.net/MetaWeblog.svc", apiElement.Attribute("apiLink").Value); }

http://oxite.codeplex.com/SourceControl/changeset/view/54721#419183

[Story] public void Should_find_customers_by_name_when_name_matches() { Story story = new Story("List customers by name"); story.AsA("customer support staff") .IWant("to search for customers in a very flexible manner") .SoThat("I can find a customer record and provide meaningful support");

CustomerRepository repo = null; Customer customer = null; story.WithScenario("Find by name") .Given("a set of valid customers", delegate { repo = CreateDummyRepo(); }) .When("I ask for an existing name", "Joe Schmoe", delegate(string name) { customer = repo.FindByName(name); }) .Then("the correct customer is found and returned", delegate {Assert.That(customer.Name, Is.EqualTo("Joe Schmoe"));}); }

http://grabbagoft.blogspot.com/2007/09/authoring-stories-with-nbehave-03.html

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

RECORD AND PLAYBACK

You can make C# readable

But it’s hard

RUBY?

http://www.flickr.com/photos/buro9/298994863/

Natural Language

http://www.flickr.com/photos/mag3737/1914076277/

RSpec

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

Behaviour Driven Development

Intent

[TestMethod]public void EditAction_Retrieves_Dinner_1_From_Repo_And_Countries_And_Sets_DinnerViewModel() { // Arrange var controller = CreateDinnersControllerAs("someuser");

// Act ViewResult result = controller.Edit(1) as ViewResult;

// Assert DinnerFormViewModel model = result.ViewData.Model as DinnerFormViewModel; Assert.AreEqual(13, model.Countries.Count());}

describe

describe “when editing” do

describe “when editing” do itend

describe “when editing” do it “should return countries where dinners can be hosted”end

D:\SourceControl\nerddinner-23425\specs>ispec DinnersController_specs.rb*

Pending:

when editing should return countries where dinners can be hosted (Not Yet Implemented)

./DinnersController_specs.rb:2

Finished in 0.3511328 seconds

1 example, 0 failures, 1 pending

describe NerdDinner::Controllers::DinnersController, “when editing” do

require ‘NerdDinner.dll’describe NerdDinner::Controllers::DinnersController, “when

editing” do

$: << ‘../NerdDinner/bin’require ‘NerdDinner.dll’describe NerdDinner::Controllers::DinnersController, “when

editing” do

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

describe DinnersController, “when editing” do

it “returns countries where dinners can be hosted” do controller = DinnersController.newend

it “returns countries where dinners can be hosted” do controller = DinnersController.new(dinner_repos(dinners))end

it “returns countries where dinners can be hosted” do controller = DinnersController.new(dinner_repos(dinners)) result = controller.Edit(1).ViewData.Modelend

it “returns countries where dinners can be hosted” do controller = DinnersController.new(dinner_repos(dinners)) result = controller.Edit(1).ViewData.Model result.Countries.Count().should == test_data.lengthend

RSpec has really powerful matchers

D:\SourceControl\nerddinner-23425\specs>ispec DinnersController_specs.rbF

1)'NerdDinner::Controllers::DinnersController when editing should return

countries where dinners can be hosted' FAILEDexpected: 13, got: nil (using ==)./DinnersController_specs.rb:8:Finished in 0.4824219 seconds

1 example, 1 failure

D:\SourceControl\nerddinner-23425\specs>ispec DinnersController_specs.rb.

Finished in 0.4355469 seconds

1 example, 0 failures

require ‘caricature’

def dinner_repos(test_data) IDinnerRepository.isolate(:FindUpcomingDinners) {returns test_data} End

def create_dinners(count=13) dinners = [] count.times do |i| dinners << Dinner.new(:country => “Value#{i}”) endend

describe DinnersController, "when editing" do let(:dinners) {create_dinners} let(:controller) {DinnersController.new(dinner_repos dinners)} it "returns countries where dinners can be hosted" do result = controller.Edit(dinners.first.id).view_model result.Countries.Count().should == dinners.length endend

result.Countries.Count().should == dinners.length result.Countries.should have_same_count(dinners)

module Matchers class CountEqual def initialize(expected) @expected = expected end def matches?(actual) actual.Count() == @expected.Count() end end def have_same_count(expected) CountEqual.new(expected) endend

Duck Typing FTW!

describe DinnersController, “Managing dinner reservations” do let(:dinners) { valid_dinners } let(:controller) {DinnersController.new(dinner_repository dinners)}

describe “when editing“ it_should_behave_like “valid dinners” it "returns countries where dinners can be hosted"

end

describe “when saving“ do describe “the validation for invalid dinners” do let(:dinners) { bad_dinners(1) }

it “should reject a dinner without a name” it “should reject a dinner without a email address” it “should accept a dinner if it has a name and email address” end describe “confirmation” do it “should send an email to the organiser once saved” end describe “valid dinners” do it “redirects to thank you page after completing" end endend

describe "NHibernate" do before do config = Configuration.new @cfg = config.configure(File.join(Dir.pwd, "nhibernate.config")) end

it "can create session factory" do session_factory = @cfg.BuildSessionFactory() session_factory.should_not be_nil end

it "can create session" do session_factory = @cfg.BuildSessionFactory() session = session_factory.open_session session.should_not be_nil endend

Outside-in Development

Cucumber

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

Documentation, automated tests and development-aid

[Story] public void Should_find_customers_by_name_when_name_matches() {

Story story = new Story("List customers by name"); story.AsA("customer support staff")

.IWant("to search for customers in a very flexible manner")

.SoThat("I can find a customer record and provide meaningful support");

CustomerRepository repo = null; Customer customer = null; story.WithScenario("Find by name")

.Given("a set of valid customers", delegate { repo = CreateDummyRepo(); })

.When("I ask for an existing name", "Joe Schmoe", delegate(string name) { customer = repo.FindByName(name); })

.Then("the correct customer is found and returned", delegate {Assert.That(customer.Name, Is.EqualTo("Joe Schmoe"));});

}

Feature: List customers by name As a customer support staff I want to search for customers in a very flexible manner So that I can find a customer record and provide meaningful support

Scenario: Find by name Given a set of valid customers When I ask for an existing name Then the correct customer is found and returned

Feature: List customers by name As a customer support staff I want to search for customers in a very flexible manner So that I can find a customer record and provide meaningful support

Scenario: Find by name Given a set of valid customers When I ask for an existing name Then the correct customer is found and returned

D:\SourceControl\nerddinner-23425\features>cucumber list.featureFeature: List customers by name As a customer support staff I want to search for customers in a very flexible manner So that I can find a customer record and provide meaningful support

Scenario: Find by name # list.feature:6 Given a set of valid customers # list.feature:7 When I ask for an existing name # list.feature:8 Then the correct customer is found and returned # list.feature:9

1 scenario (1 undefined)3 steps (3 undefined)0m0.020s

You can implement step definitions for undefined steps with these snippets:

Given /^a set of valid customers$/ do pending # express the regexp above with the code you wish you hadend

When /^I ask for an existing name$/ do pending # express the regexp above with the code you wish you hadend

Then /^the correct customer is found and returned$/ do pending # express the regexp above with the code you wish you hadend

Given /^a set of valid customers$/ do @repo = CreateDummyRepo()end

When /^I ask for an existing name$/ do @customer = @repo.FindByName("Joe Schmoe")end

Then /^the correct customer is found and returned$/ do @customer.Name.should == "Joe Schmoe“end

NOT BEST PRACTICE!!

Given /^customer “([^\"]*)” $/ do |name| @repo = CustomerRepository.new(Customer.new(:name => name)end

When /^I search for customer “([^\"]*)”$/ do |name| @customer = @repo.FindByName(name)end

Then /^”([^\"]*)” should be found and returned$/ do |name| @customer.Name.should == nameend

WebRat

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

visitclick_link

fill_inclick_button

check and uncheckchooseselect

attach_file

EXAMPLESCucumber, WebRat and Automated UI testing

One more thing...

Meerkatalyst

http://blog.benhall.me.uk/2009/12/sneak-peek-at-meerkatalystlonestar.html

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

Expressing intent

Ruby -> C#

http://www.flickr.com/photos/philliecasablanca/2456840986/

@Ben_HallBen@BenHall.me.ukBlog.BenHall.me.uk

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

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’s RubyMine– JRuby – Capybara– Celerity– Active record – active-record-model-

generator– Faker– Guid

Useful Links• http://www.github.com/BenHall• http://blog.benhall.me.uk• http://stevehodgkiss.com/2009/11/14/using-activerecord-migrator-

standalone-with-sqlite-and-sqlserver-on-windows.html• http://www.testingaspnet.com• http://• http://msdn.microsoft.com/en-us/magazine/dd434651.aspx• http://msdn.microsoft.com/en-us/magazine/dd453038.aspx• http://www.cukes.info

Getting SQL Server to work

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