Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
Testing/Testing In Rails 1
Alan and SaskiaAlan and Saskia
2/8/2008
OutlineOutline
• Testing In General
• Unit Testing and Test First!Unit Testing and Test First!
• Integration Testing
• Functional Testing
• Fixture• Fixture
• Mocking with Mocha
Testing In General (1)Testing In General (1)
• Formal Definition– Testing is the process of finding differences g p gbetween the expected behavior specified by system models and the observed behavior of the yimplemented system.
– The goal is to design tests that exercise defects inThe goal is to design tests that exercise defects in the system and to reveal problems
Testing In General (2)Testing In General (2)
• Alternative Definition– Testing has to demonstrate that faults are not gpresent at all.
• Almost impossible to showAlmost impossible to show
• May lead to the selection of test data that have a low probability of causing the program to failp y g p g
• Even more alternative (Wilson Bilkovich) W iti li ti ith t t t k– Writing applications without tests makes you a bad person, incapable of love.
Types of Testing (1)Types of Testing (1)
Types of Testing (2)Types of Testing (2)
Unit Testing in RubyUnit Testing in Ruby
• Unit Testing
– Testing small units of codes (normally methods) • Test::Unit
– Ruby’s built‐in testing framework
– xUnit family (Java’s Junit, .NET’s Nunit)
• Concepts
– Assertions: comparison of expected value and result of an expression
– Failure: assertion failure
– Error: exception or runtime error
– Green vs red bar
Unit Testing in Ruby (2)Unit Testing in Ruby (2)require 'roman'
require 'test/unit'<<Class inclusion
require test/unit
class TestRoman < Test::Unit::TestCase
def setup
<<super class
<<code to be run before all test methodsp
end
<< i ti
<<code to be run before all test methods
def test_simple
assert_equal("i", Roman.new(1).to_s) assert equal("ix" Roman new(9) to s)
<<naming convention
<<assertionsassert_equal( ix , Roman.new(9).to_s)
end
def teardown
end <<code to be run after all test methods
end
Assertions (1)Assertions (1)
• assert(boolean, [ message ] )
– Fails if boolean is false or nil.
• assert_nil(obj, [ message ] )
• assert_not_nil(obj, [ message ] ) – Expects obj to be (not) nil.
• assert_equal(expected, actual, [ message ] ) • assert_not_equal(expected, actual, [ message ] )
– Expects obj to equal/not equal expected, using ==.
• assert_in_delta(expected_float, actual_float, delta, [ message ] ) – Expects that the actual floating‐point value is within delta of the expected value.
Assertions (2)Assertions (2)
( ) { bl k }• assert_raise(Exception, . . . ) { block }
• assert_nothing_raised(Exception, . . . ) { block }
– Expects the block to (not) raise one of the listed exceptionsExpects the block to (not) raise one of the listed exceptions.
• assert_instance_of(klass, obj, [ message ] ) • assert_kind_of(klass, obj, [ message ] )
– Expects obj to be a kind/instance of klass.
• assert_respond_to(obj,message, [ message ] ) ( )– Expects obj to respond to message (a symbol).
• assert_match(regexp, string, [ message ] ) • assert no match(regexp string [ message ] )assert_no_match(regexp, string, [ message ] )
– Expects string to (not) match regexp.
• assert_same(expected, actual, [ message ] ) • assert_not_same(expected, actual, [ message ] )
– Expects expected.equal?(actual).
Assertions (3)Assertions (3)
( b b [ ] )• assert_operator(obj1, operator, obj2, [ message ] )
– Expects the result of sending the message operator to obj1 with parameter obj2 to be true.p j
• assert_throws(expected_symbol, [ message ] ) { block }
– Expects the block to throw the given symbol.
• assert_send(send_array, [ message ] ) – Sends the message in send_array[1] to the receiver in send_array[0],
passing the rest of send array as arguments Expects the return valuepassing the rest of send_array as arguments. Expects the return value to be true.
• flunk(message="Flunked") – Always fail.
Test First!Test First!
• What is it about?– Write the test cases first. Then write minimal codes to pass the tests
– Write more tests Write more minimal codes toWrite more tests. Write more minimal codes to pass the tests
Iterate the process till all functional requirements– Iterate the process till all functional requirements are satisfied ☺
Test First GuidelinesTest First Guidelines
h f h h ld d b h f h d• The name of the test should describe the requirement of the code
• There should be at least one test for each requirement of the code. Each possible path through of the code is a different requirementp p g q
• Only write the simplest possible code to get the test to pass, if you know this code to be incomplete, write another test that demonstrates what else the code needs to doelse the code needs to do
• A test should be similar to sample code, in that it should be clear to someone unfamiliar with the code as to how the code is intended to be used
• If a test seems too large, see if you can break it down into smaller tests
• If you seem to be writing a lot of code for one little test see if there are• If you seem to be writing a lot of code for one little test, see if there are other related tests you could write first, that would not require as much code
• More at http://www.xprogramming.com/xpmag/testFirstGuidelines.htm
Test First DemoTest First Demo
Unit Testing in RailsUnit Testing in Rails
• In rails, unit testing is geared towards testing of individual functions created in a model
• For each model generated, a test file is automatically created in tests/unit directoryautomatically created in tests/unit directory
• script/generate model product name:string description:string
exists app/models/exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/product.rb
create test/unit/product_test.rb
….
Testing/Testing In Rails 1 Saskia
Outline
• Testing In General
U it T ti d T t Fi t!• Unit Testing and Test First!
• Integration Testingg g
• Functional Testing
i• Fixture
• Mocking with Mochag
Types of Testing (1)Types of Testing (1)
Integration Testing in RoRIntegration Testing in RoR
i i• Integration Testing
– story‐level, tests interactions & interfaces of various actions t d b th li ti ll t llsupported by the application, across all controllers
– Find bugs with session management and routing, triggered by certain cruft accumulating in a user’s sessionby certain cruft accumulating in a user s session
• In 't t/i t ti ':• In 'test/integration':
– Create test file with 'script/generate integration_test stories_test'
Cl I t ti T t i h it f T t U it– Class IntegrationTest inherits from Test::Unit
Integration Testing in RoRIntegration Testing in RoR
• Story set describes how the application ought to function
I t f h f i ht d t– Interfaces: exchange of right data
– Interaction: employment of right components
l l l• Example: signing up, getting account, multiple users
• Testing time grows with number of integrated units
– Bottom‐Up: Integrate tested Units to subsystems as new components
Integration Testing in RoRg g
i "#{Fil di ( FILE )}/ /t t h l " l i l i
File 'stories_test.rb' in 'test/integration':require "#{File.dirname(__FILE__)}/../test_helper"
class StoriesTest < ActionController::IntegrationTest
fixtures :users, :accounts
<<class inclusion
<<Integration Test Classfixtures :users, :accounts
def test_stories
get "/signup"<<naming convention
assert_response :success
assert_template "signup/index"
"/ i " "B b" "b b"
<<assertions
post "/signup", :name => "Bob", :user_name => "bob",
:password => "secret"
assert response :redirectassert_response :redirect
follow_redirect!
assert_response :success
assert_template "account/index"
end
Integration Testing in Ruby (2)g g y ( )Run by: rake test_integration or ruby stories_test.rbO t tStarted
.F..E <<Each spot represents a test, sorted alphabetically,
Output:
Finished in 0.01 seconds.
p p p y3 values: . means successful test (pass)
F means failed testE means an error occurred
1) Failure:
where/which test
what went wrong <<Listing of failures & errors:what went wrong
2) Error:
where/which test
<<Listing of failures & errors:Details on where and whatMoves on with first wrong assertion.
/
error_type
what went wrong
5 tests, 9 assertions, 1 failure, 1 error.
Types of Testing (1)Types of Testing (1)
Functional Testing in RubyFunctional Testing in Ruby
• Functional Testingsingle controllers and interactions between the– single controllers and interactions between the models it employs
• In directory 'test/function':
– 'functional controller test rb' stubs for each 'script/generate– functional_controller_test.rb stubs for each script/generate controller'
Functional Testing in RoRFunctional Testing in RoRrequire File.dirname(__FILE__) + '/../test_helper'
require 'home controller' << grab HomeController for testingrequire home_controller
class HomeControllerTest < Test::Unit::TestCase
def setup
<< grab HomeController for testing
<< setup of 3 typical funtional Test objects:p
@controller = HomeController.new
@request = ActionController::TestRequest.new
setup of 3 typical funtional Test objects:* controller to be tested
* TestRequest tosimulate web request
@response = ActionController::TestResponse.new
end
def test index
q* TestResponse to provide
information about test request
def test_index
get :index
assert response :success
<< test of main index page<< get method simulates request on action called index . << assertion assures that request successful_ p
end
end
assertion assures that request successful
5 request types supported as methods in Rails:get, post, put, head, delete
Fixtures in RoRFixtures in RoR• Fixtures: automatically created sample data• Fixtures: automatically created sample data
– To fill testing database with predefined data before tests run
d t b i d d t– database independent
– Require explicit loading with fixtures method within test class
• In directory 'test/fixtures':
– fixture stubs for each 'script/generate model'
• Formats
– YAML: good readability, file extension '.yml'
– CSV: comma‐separated value file format, '.csv', easy reuse of / /existing data in spreadsheet/database (save/export as CSV)
YAML‐FixturesFile 'persons.yml' of YAML-Fixtures in 'text/fixtures':# This is a YAML comment!
david:
id: 1<< name1
id: 1
name: David Dwarf
birthday: 0010‐01‐01 << list of values
profession: slingshoter
goliath: << name2<< fixture-records separated by blank line
id: 2
name: Goliath Giant
bi thd 0000 02 22
each fixture: * 'fixture-name'birthday: 0000‐02‐22
profession: terrorizer* list of colon-separated key/value pairs
CSV‐FixturesS tu esFile 'users.csv' of CSV-Fixtures in 'text/fixtures':id, username, password, intelligent, comments
1, dhow, imstupid, false, I laugh ""Ha! Ho! Hu!""
2 admin ihatedhows true What a mess! << list of value-records
<< header: first line, comma-separated list of fields
2, admin, ihatedhows, true, What a mess!
3, nobody, ilovetomock, true, "Nobody mocks you"
4, nulpe,, false,
<< list of value records,one record per line
format:* each cell stripped of outward facing spaces each cell stripped of outward facing spaces* comma as data: cell must be encased in
quotes* quote as data: must escape it with 2nd quote
CSV fixture names automatically generated: “model‐name”‐”counter”
q p q* no blank lines* nulls achived by placing comma
y gusers‐1
users‐2
...
Fixtures in ActionFixtures in Action# allow this test to hook into the Rails framework# allow this test to hook into the Rails framework
require File.dirname(__FILE__) + '/../test_helper'
# need to include a User for testing
require 'user'
class UserTest < Test::Unit::TestCase
fixtures :users
def test_count_fixtures
<< fixture-load method: * destroys any data in users table* loads fixture data into users table
assert_equal 5, User.count
end
* dumps the data into a variable for direct access
t ti l d f th fi t t th t t f h t t th dend automatic load of the fixtures at the start of each test method
Hashes with FixturesHashes with FixturesFixtures are basically Hash objects:
direct access via generated local variable of the test case- direct access via generated local variable of the test caseFixtures can get form of the original class:- access to methods only available to that class...
fixtures :users, :person
def test user
access to methods only available to that class
<< load multiple fixtures separated by commasdef test_user
users(:nobody) users(:nobody).id
<< returns Hash for fixture named david << returns id-property of david( y)
end
def test_person
p p y
david = users(:david).find
email( david.girlfriend.email, david.illegitimate_children ) d
<< using find method to grab "real" david as Person
<<now: access to methods only available to a Person classend
...
methods only available to a Person class
Mocking with MochaMocking with Mocha
• Problem with Fixtures• Problem with Fixtures– Fixtures makes testing slow (engage the actual DB) – Fixtures allow invalid data
– Maintainability Challengesy g
– Fixtures are brittle
Th t’ h d M h• That’s why we need Mocha (http://mocha.rubyforge.org/) – It provides stubs and mocks to simulate data, especially data in the databasep y
Installing MochaInstalling Mocha
• sudo gem install mocha
• In rails, include in test/test helper.rbIn rails, include in test/test_helper.rb
require ‘mocha’
Do Not Mock My StubDo Not Mock My Stub
• Stubbing (State Verification)
– Stubbing a method is all about replacing the method with d th t t ifi d lt ( h icode that returns a specified result (or perhaps raises a
specified exception).
• Mocking (Behavior Verification)• Mocking (Behavior Verification)
– Mocking a method is all about asserting that a method has been called (perhaps with particular parameters)been called (perhaps with particular parameters).
• it’s difficult (or impossible?) to do mocking without stubbing ‐you need to return from the mocked method, so that theyou need to return from the mocked method, so that the code under test can complete execution
• http://www.martinfowler.com/articles/mocksArentStubs.htmlp // / /
Stubbing example (stub)Stubbing example (stub)
' / 'require 'test/unit'
require 'rubygems'
require 'mocha'require mocha
class TestProduct < Test::Unit::TestCase
def test_product
product = stub('ipod_product', :manufacturer => 'ipod', :price => 100) assert_equal 'ipod', product.manufacturer
assert_equal 100, product.price
endend
end
More stubbing example (stubs)More stubbing example (stubs)class View
attr :document
def initialize(document) require 'test/unit'require 'rubygems'req ire 'mocha'@document = document
end
def print()
require 'mocha'
class ViewTest < Test::Unit::TestCasedef print() if document.print
puts "Excellent!"
def test_should_return_false_for_failed_printdocument = stub("my document") document stubs(:print) returns(true)p
true
else
document.stubs(:print).returns(true)
ui = View.new(document) assert ui.print
puts "Bummer."
false
end
assert ui.printend
endend
end
end
Mocking example (expects)Mocking example (expects)l E i i 't t/ it'class Enterprise
def initialize(dilithium)
require 'test/unit'require 'rubygems'require 'mocha'
@dilithium = dilithium
endclass EnterpriseTest < Test::Unit::TestCase
def test_should_boldly_godilithi k()def go(warp_factor)
warp_factor.times
{ @dilithium.nuke(:anti_matter) }
dilithium = mock()dilithium.expects(:nuke).with(:anti_matter).at_least_onceenterprise = Enterprise.new(dilithium) enterprise.go(2)
end
end
p g ( )end
end
Expects More MockingExpects More Mocking
• Methods in expects:– at least, at least once, at most, at most once, _ , _ _ , _ , _ _ ,in_sequence, multiple_yields, never, once, raises, returns, then, times, when, with, yields , , , , , y