CSC/ECE 517 Fall 2011/ch2 2e ad: Difference between revisions
Line 114: | Line 114: | ||
The example that we have used below is a simple travel time example. All it does is if you give it a distance between two cities it will calculate the time it will take you to travel from City A to City B. It assumes that you will be driving at 60 miles/Hr with minimal traffic on the road. | The example that we have used below is a simple travel time example. All it does is if you give it a distance between two cities it will calculate the time it will take you to travel from City A to City B. It assumes that you will be driving at 60 miles/Hr with minimal traffic on the road. | ||
<pre> | <pre> | ||
Line 134: | Line 135: | ||
end | end | ||
</pre> | </pre> | ||
The code above has two functions and one initialize function. The initialize function takes in distance between two cities and stores it into distance variable. The first function time_to_travel when called calculates the time it takes to travel by dividing the distance provided during initialize stage by 60 (for 60 miles/hr speed). This function returns a float value as time. The second function format_time_to_travel all it does is if you provide a float number it will format the float number up to two decimal points and return the string. This function will be used in cases where you get a big float number for time like 2.36666667 hrs and you just want it to be 2.37 than you can pass the float number to get 2.37 hrs. Now lets see how this functions can be tested in different testing framework. | The code above has two functions and one initialize function. The initialize function takes in distance between two cities and stores it into distance variable. The first function time_to_travel when called calculates the time it takes to travel by dividing the distance provided during initialize stage by 60 (for 60 miles/hr speed). This function returns a float value as time. The second function format_time_to_travel all it does is if you provide a float number it will format the float number up to two decimal points and return the string. This function will be used in cases where you get a big float number for time like 2.36666667 hrs and you just want it to be 2.37 than you can pass the float number to get 2.37 hrs. Now lets see how this functions can be tested in different testing framework. | ||
'''Test-Unit''' | '''Test-Unit''' | ||
The first test framework that we are going to use is Test-Unit. As you can see from the code below its similar to Java JUnit if you are familiar with JUnit. TimeTravelTest_TU class below has setup function which is used for setting up pretest data before running tests. In our case we have initialized three different instances of TravelTime class with distances 120 150 and 135 miles. The class also has a teardown function which can be used to discard all the data that were created before or during test execution. The remaining three functions are the actual tests that will be performed one this test is executed. | |||
<pre> | <pre> | ||
Line 176: | Line 180: | ||
end | end | ||
</pre> | </pre> | ||
The first test function is to test if you provide the distance of 120 miles does it return the travel time to be 2 hrs. The second test function tests if you provide 150 miles distance does it come back with 2.5 hrs. And the last test function checks if you get travel time in a big floating number like 2.4444445 hours can you correctly format that hours to 2.25 hours. We have used different instances of travel time class for different tests the reason for doing this is to see if the performance suffers between test frameworks due to this. | |||
<pre> | <pre> |
Revision as of 14:20, 21 September 2011
Testing frameworks for Ruby
Introduction
This is an introduction to the subject.
Definition
Test Driven Development (TDD)
Behavior Driven Development(BDD)
Mocking in Testing
Benchmarking in Testing
Testing Framework
Testing Framework Evolution
Explain here how testing in ruby went from regular Test:unit testing to BDD testing.
TDD vs BDD
Give high level example how they differ from each other.
Ruby Testing Frameworks
Compared to five years ago the number of testing framework available for ruby has increased rapidly. Selecting a perfect testing framework for a particular project has become harder.
Overview of testing framework
Selection of testing framework for ruby projects depends on factors like what are we trying to accomplish TDD or BDD, is GUI testing included, is acceptance testing required, do we have to merge legacy ruby test code with new testing framework. Altough GUI testing and acceptance testing are out of scope for this wiki we have made attempt to cover them in some details at the end of this wiki page. Meanwhile, below is the detailed explanation of five most commonly used unit testing framework.
Test:Unit
Test-Unit is one of the oldest testing frameworks for ruby testing. Test-Unit is basically used for performing test driven development same as how Java’s JUnit is used. It’s in a way very similar to java’s JUnit. Test-Unit framework when initially released was part of the ruby distribution package. The newer version since then has been converted to gems and has been provided to developers as a standalone package. Additionally some of the features like notify, capybara, GTK+, Tk, and FOX Toolkit have been split from package and provided as a separate packages.
RSpec
RSpec is a Behavior-Driven Development tool for Ruby programmers. As defined earlier BDD is an approach to software development that combines Test-Driven Development, Domain Driven Design, and Acceptance Test-Driven Planning. RSpec helps you do the TDD part of that equation, focusing on the documentation and design aspects of TDD [1]
RSpec is one of the most widely used testing frameworks for ruby. The main difference between RSpec and Test-Unit is semantics. RSpec provides a way to write tests which are more in human readable form. This reduces the expense for maintaining the test and also provides more easily understandable tests. Additionally when RSpec tests fails it provides more information about what functionality failed and why compared to Test-Unit test where you have to put custom messages on all assertion statements to point out exactly what happened.
Riot
Riot is another BDD ruby unit testing framework. Riot testing framework is a mixture of both RSpec and Test-Unit or should say RSpec and Shoulda (which is based on Test-Unit). The main purpose of the author of Riot testing framework was to come up with some ruby testing framework which is fast in execution but is in more human readable form. As in case of other testing framework Riot does not have variables described or initialized in its test cases. It has a setup block which returns a variable as default. This allows Riot to be fast during execution as it does not have to initialize or do much of pre-test setup. One of the uniqueness about Riot testing framework is that the tests only focuses on small functionality.
Shoulda
Testing Framework Details
Testing Framework | Released Date | Released Version | Current Version | Testing Type Supported | Install Command | Documentation/Version Support |
---|---|---|---|---|---|---|
Test:Unit | 03-20-2008 | 1.2.3 | 2.4.0 | TDD,Mocking | gem install test-unit | Test:Unit Site |
Shoulda | 04-26-2008 | 4.1 | 2.11.3 | BDD,TDD | gem install shoulda | Shoulda Site |
RSpec | 05-26-2008 | 1.1.4 | 2.6.0 | BDD,TDD, DDD (Domain Driven Design) | gem install rspec | RSpec Site |
Riot | 10-19-2009 | 0.9.11 | 0.12.5 | BDD,TDD | gem install riot | Riot Site |
MiniTest | 06-10-2008 | 1.2.1 | 2.6.9 | TDD,Mocking | gem install minitest | Minitest Site |
Cucumber | 05-20-2009 | 0.3.6 | 1.0.6 | Acceptance Testing | gem install cucumber | Cucumber Site |
Examples
Above we described how each ruby testing framework differ from each other. But to better understand each testing framework let go through a single example with tests written in most testing framework described above.
TravelTime
The example that we have used below is a simple travel time example. All it does is if you give it a distance between two cities it will calculate the time it will take you to travel from City A to City B. It assumes that you will be driving at 60 miles/Hr with minimal traffic on the road.
class TravelTime attr_accessor :distance #Divide the distance by 60 and return the float def time_to_travel @time = @distance.fdiv(60) end #Format the float to two decimal and return the string def format_time_to_travel(time) @time = format("%3.2f",time) end def initialize(distance) @distance = distance end end
The code above has two functions and one initialize function. The initialize function takes in distance between two cities and stores it into distance variable. The first function time_to_travel when called calculates the time it takes to travel by dividing the distance provided during initialize stage by 60 (for 60 miles/hr speed). This function returns a float value as time. The second function format_time_to_travel all it does is if you provide a float number it will format the float number up to two decimal points and return the string. This function will be used in cases where you get a big float number for time like 2.36666667 hrs and you just want it to be 2.37 than you can pass the float number to get 2.37 hrs. Now lets see how this functions can be tested in different testing framework.
Test-Unit
The first test framework that we are going to use is Test-Unit. As you can see from the code below its similar to Java JUnit if you are familiar with JUnit. TimeTravelTest_TU class below has setup function which is used for setting up pretest data before running tests. In our case we have initialized three different instances of TravelTime class with distances 120 150 and 135 miles. The class also has a teardown function which can be used to discard all the data that were created before or during test execution. The remaining three functions are the actual tests that will be performed one this test is executed.
gem 'test-unit' require 'test/unit' require File.dirname(__FILE__) + '/travel_time' class TimeTravelTest_TU < Test::Unit::TestCase # Called before every test method runs. Can be used # to set up fixture information. def setup @traveltime1 = TravelTime.new(120) @traveltime2 = TravelTime.new(150) @traveltime3 = TravelTime.new(135) end def teardown # Do nothing end def test_time_to_travel_integer time = @traveltime1.time_to_travel assert_equal 2.0, time end def test_time_to_travel_floatWithOneDecimal time = @traveltime2.time_to_travel assert_equal 2.5, time end def test_time_to_travel_floatWithTwoDecimal temp = @traveltime3.time_to_travel time = @traveltime3.format_time_to_travel(temp) assert_equal "2.25", time end end
The first test function is to test if you provide the distance of 120 miles does it return the travel time to be 2 hrs. The second test function tests if you provide 150 miles distance does it come back with 2.5 hrs. And the last test function checks if you get travel time in a big floating number like 2.4444445 hours can you correctly format that hours to 2.25 hours. We have used different instances of travel time class for different tests the reason for doing this is to see if the performance suffers between test frameworks due to this.
Finished in 0.004 seconds. 3 tests, 3 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed
RSpec testing code
gem 'rspec' require File.dirname(__FILE__) + '/travel_time' #spec TravelTimeTest_RSpec.rb --format specdoc describe TravelTime do it "should be 2.0 hours when distance is 120 miles" do @traveltime1 = TravelTime.new(120) time = @traveltime1.time_to_travel time.should be == 2.0 end it "should be 2.5 hours when distance is 150 miles" do @traveltime1 = TravelTime.new(150) time = @traveltime1.time_to_travel time.should be == 2.5 end it "should be 2.25 hours when distance is 135 miles" do @traveltime1 = TravelTime.new(135) temp = @traveltime1.time_to_travel time = @traveltime1.format_time_to_travel(temp) time.should be == "2.25" end end
TravelTime - should be 2.0 hours when distance is 120 miles - should be 2.5 hours when distance is 150 miles - should be 2.25 hours when distance is 135 miles Finished in 0.284016 seconds 3 examples, 0 failures
Riot testing code
gem 'riot' require 'riot' require File.dirname(__FILE__) + '/travel_time' context TravelTime do setup {TravelTime.new(120)} asserts("Travel time is 2.0 hours when distance is 120 miles"){ topic.time_to_travel == 2.0 } end context TravelTime do setup {TravelTime.new(150)} asserts("Travel time is 2.5 hours when distance is 150 miles"){ topic.time_to_travel == 2.5 } end context TravelTime do setup {TravelTime.new(135)} asserts("Travel time is 2.25 hours when distance is 135 miles"){ topic.format_time_to_travel(topic.time_to_travel) == "2.25" } end
TravelTime + asserts Travel time is 2.0 hours when distance is 120 miles TravelTime + asserts Travel time is 2.5 hours when distance is 150 miles TravelTime + asserts Travel time is 2.25 hours when distance is 135 miles 3 passes, 0 failures, 0 errors in 0.005 seconds
Shoulda testing code
gem 'shoulda' gem 'shoulda-context' gem 'test-unit' require 'test/unit' require File.dirname(__FILE__) + '/travel_time' class TravelTimeTest_Shoulda < Test::Unit::TestCase context TravelTime do setup do @traveltime1 = TravelTime.new(120) @traveltime2 = TravelTime.new(150) @traveltime3 = TravelTime.new(135) end should "be 2.0 hours when distance is 120 miles" do time = @traveltime1.time_to_travel assert_equal 2.0,time end should "be 2.5 hours with one decimal point when distance is 150 miles" do time = @traveltime2.time_to_travel assert_equal 2.5,time end should "be 2.25 hours with two decimal point limit when distance is 135 miles" do temp = @traveltime3.time_to_travel time = @traveltime3.format_time_to_travel assert_equal "2.25",time end end end
Additional Testing Framework
UI Testing Framework
Context
Win32-AutoGUI
Acceptance testing
Cucumber
Capybara
References
- [1] Ruby Forge Test:Unit Site
- [2] Shay Friedman A Mini-Review-Benchmark of Ruby’s Different Testing Frameworks
Note
There are many testing frameworks for Rails, but for this topic, I would like you to concentrate on testing frameworks for Ruby itself. Look up information on these frameworks, and describe their evolution.