Ruby TestCase: Define instance variable in `self.startup` method - ruby

I have this Ruby code:
class GoogleTestCase < BaseTestCase
def test_search
#browser.find_element(:name, 'q').send_keys "Hello Ruby"
#browser.find_element(:name, 'btnK')
end
end
And then I run the GoogleTestCase through this file:
...
class BaseTestCase < Test::Unit::TestCase
def self.startup
#browser = Selenium::WebDriver.for :chrome
#browser.get('https://google.com')
end
def self.shutdown
#browser.quit
end
end
exit Test::Unit::AutoRunner.run(true, test_dir)
After launching, everything is fine. Selenium will run Chrome browser, it opens Google web page. But when the test_search method is fired, Ruby can't see #browser variable:
How can I define #browser variable in self.startup method so inside the test_search method I can see it?

The error is because you try to access instance variable #browser, which is defined on class level. Because startup and shutdown are class methods, #browser is class variable accordingly.
You can use ##browser to access class variables from instance level.
class GoogleTestCase < BaseTestCase
def test_search
##browser.find_element(:name, 'q').send_keys "Hello Ruby"
##browser.find_element(:name, 'btnK')
end
end
Keep in mind, that ##browser is the same across all instances of such class.
Also, you can encapsulate the way you access the browser variable in helper method:
class BaseTestCase < Test::Unit::TestCase
def self.startup
#browser = Selenium::WebDriver.for :chrome
#browser.get('https://google.com')
end
def self.shutdown
#browser.quit
end
def browser
##browser
end
end
class GoogleTestCase < BaseTestCase
def test_search
browser.find_element(:name, 'q').send_keys "Hello Ruby"
browser.find_element(:name, 'btnK')
end
end

Related

Ruby instance variables within class?

So I was looking over page objects for Ruby testing here:
http://elementalselenium.com/tips/7-use-a-page-object
And down on the page within this bit of code:
class GoogleSearch
SEARCH_BOX = { id: 'gbqfq' }
SEARCH_BOX_SUBMIT = { id: 'gbqfb' }
TOP_SEARCH_RESULT = { css: '#rso .g' }
attr_reader :driver
def initialize(driver)
#driver = driver
visit
verify_page
end
def visit
driver.get ENV['base_url']
end
def search_for(search_term)
driver.find_element(SEARCH_BOX).clear
driver.find_element(SEARCH_BOX).send_keys search_term
driver.find_element(SEARCH_BOX_SUBMIT).click
end
def search_result_present?(search_result)
wait_for { displayed?(TOP_SEARCH_RESULT) }
driver.find_element(TOP_SEARCH_RESULT).text.include? search_result
end
private
def verify_page
driver.title.include?('Google').should == true
end
def wait_for(seconds=5)
Selenium::WebDriver::Wait.new(:timeout => seconds).until { yield }
end
def displayed?(locator)
driver.find_element(locator).displayed?
true
rescue Selenium::WebDriver::Error::NoSuchElementError
false
end
end
It defines #driver = driver, but then in all the other methods it uses just driver. Shouldn't all the other methods be using the initialized variable #driver? Or is this just simply a case of mis-documentation?
I mean after all what was the point of declaring #driver if we never use it again?
attr_reader :driver is just a helper (macro) that defines following method:
def driver
#driver
end
While attr_writer :driver would define the following:
def driver=(value)
#driver = value
end
and attr_accessor :driver would define both, a reader and writer method.
So whenever you see driver it calls the method that accesses #driver. It is good practice to access instance variable via a getter. Makes the code easier to refactor.
Assuming you want to raise an exception if driver is unset, you can remove the attr_reader and add your own definition of driver without changing the rest of the code:
def driver
raise "no driver set" if !#driver
#driver
end
attr_reader :driver means that #driver is accessible as driver. When the code refers to driver, it is getting #driver.
You can use #driver within the class if you prefer, with or without the attr_reader declaration.
There is this line attr_reader :driver at the top of the class which is a macro for:
def driver
#driver
end
The advantage to prefer this getter methods over direct #driver calls is that it makes future refactorings easier.

logger in ruby unit test startup or setup

I am writing some tests in ruby and have this question.
class MyTest < Test::Unit::TestCase
LogFile = "test.log"
Log= Logger.new(LogFile)
def startup
#log = Log
end
def shutdown
end
def setup
end
....
end
I am trying to create a logger in startup, however, it does not work well. In the test cases, #log is nil.
I know I can put it in setup, however, I don't want to reassign it for every test case. Why it does not work? What is the correct way to do it?
startup should be used as a class method. There you could define a class variable like ##log.
If you want to use an instance variable #log you must fill it in setup.
Example:
require 'test/unit'
require 'logger'
class MyTest < Test::Unit::TestCase
LogFile = "test.log"
Log= Logger.new(LogFile)
def self.startup
##log = Log
end
def setup
#log = Log
end
def test_class_var
assert_kind_of(Logger,##log)
end
def test_instance_var
assert_kind_of(Logger,#log)
end
end

Service objects pattern in Ruby on Rails

I´m trying to develop a service class that provides payment services in my Rails app, but it´s not working.
Service class (lib/paypal_service.rb) (not sure if it should be placed here, I read it in some posts):
class PaypalService
attr_reader :api #, :express_checkout_response
def initialize()
#api = PayPal::SDK::Merchant::API.new
end
def test()
puts "Congratulations, you have called test"
end
end
Controller (uses service):
class BookingsController < ApplicationController
include BoatsHelper
require 'paypal_service'
def create
PaypalService.test
end
...
In output I get:
NoMethodError (private method `test' called for PaypalService:Class):
It's because you are calling a class method, but you have defined an instance method.
Change you controller to this
def create
PaypalService.new.test
end
Or define a class method and leave your controller as is
class PaypalService
attr_reader :api #, :express_checkout_response
def initialize()
#api = PayPal::SDK::Merchant::API.new
end
def self.test
new.test
end
def test()
puts "Congratulations, you have called test"
end
end
Use PaypalService.new.test instead of PaypalService.test as test is an instance method of class PaypalService and not a class method. Update it as below:
class BookingsController < ApplicationController
include BoatsHelper
require 'paypal_service'
def create
PaypalService.new.test
end
...
NOTE:
If you want to call it as PaypalService.test then you can convert test to a class method as follows:
class PaypalService
attr_reader :api #, :express_checkout_response
def initialize
#api = PayPal::SDK::Merchant::API.new
end
def self.test
puts "Congratulations, you have called test"
end
end

Ruby: assert error message produced by a subclass of Set

require 'set'
require 'test/unit'
class Foo < Set
def to_s
"to_s"
end
alias_method :inspect, :to_s
end
class FooTest < Test::Unit::TestCase
def test1
assert_equal(Foo.new, false)
end
end
Expected output:
test1(FooTest) [test.rb:12]: <to_s> expected but was <false>.
Actual output:
test1(FooTest) [test.rb:12]: <#<Foo: {}>> expected but was <false>.
Edit
Test::Unit uses a strange method called pretty_inspect, which I hear of the first time. Nevertheless, this code will work as expected:
Solution:
require 'set'
require 'test/unit'
class Foo < Set
def to_s
"to_s"
end
alias_method :pretty_inspect, :to_s
end
class FooTest < Test::Unit::TestCase
def test1
assert_equal(Foo.new, false)
end
end
test/unit appears to not rely on an object's to_s or inspect method. Looking at the source, it may be accessing the object's class inspect method directly, but my attempts to redefine Sets inspect instance method didn't work either. Check out the source of test/unit. ;-)

In Ruby's Test::Unit::TestCase, how do I override the initialize method?

I'm struggling with Test::Unit. When I think of unit tests, I think of one simple test per file. But in Ruby's framework, I must instead write:
class MyTest < Test::Unit::TestCase
def setup
end
def test_1
end
def test_1
end
end
But setup and teardown run for every invocation of a test_* method. This is exactly what I don't want. Rather, I want a setup method that runs just once for the whole class. But I can't seem to write my own initialize() without breaking TestCase's initialize.
Is that possible? Or am I making this hopelessly complicated?
As mentioned in Hal Fulton's book "The Ruby Way".
He overrides the self.suite method of Test::Unit which allows the test cases in a class to run as a suite.
def self.suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
Here is an example:
class MyTest < Test::Unit::TestCase
class << self
def startup
puts 'runs only once at start'
end
def shutdown
puts 'runs only once at end'
end
def suite
mysuite = super
def mysuite.run(*args)
MyTest.startup()
super
MyTest.shutdown()
end
mysuite
end
end
def setup
puts 'runs before each test'
end
def teardown
puts 'runs after each test'
end
def test_stuff
assert(true)
end
end
FINALLY, test-unit has this implemented! Woot!
If you are using v 2.5.2 or later, you can just use this:
Test::Unit.at_start do
# initialization stuff here
end
This will run once when you start your tests off. There are also callbacks which run at the beginning of each test case (startup), in addition to the ones that run before every test (setup).
http://test-unit.rubyforge.org/test-unit/en/Test/Unit.html#at_start-class_method
That's how it's supposed to work!
Each test should be completely isolated from the rest, so the setup and tear_down methods are executed once for every test-case. There are cases, however, when you might want more control over the execution flow. Then you can group the test-cases in suites.
In your case you could write something like the following:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
Test::Unit::UI::Console::TestRunner.run(MySuite.new(MyTestCase))
The TestDecorator defines a special suite which provides a setup and tear_down method which run only once before and after the running of the set of test-cases it contains.
The drawback of this is that you need to tell Test::Unit how to run the tests in the unit. In the event your unit contains many test-cases and you need a decorator for only one of them you'll need something like this:
require 'test/unit'
require 'test/unit/ui/console/testrunner'
class TestDecorator < Test::Unit::TestSuite
def initialize(test_case_class)
super
self << test_case_class.suite
end
def run(result, &progress_block)
setup_suite
begin
super(result, &progress_block)
ensure
tear_down_suite
end
end
end
class MyTestCase < Test::Unit::TestCase
def test_1
puts "test_1"
assert_equal(1, 1)
end
def test_2
puts "test_2"
assert_equal(2, 2)
end
end
class MySuite < TestDecorator
def setup_suite
puts "setup_suite"
end
def tear_down_suite
puts "tear_down_suite"
end
end
class AnotherTestCase < Test::Unit::TestCase
def test_a
puts "test_a"
assert_equal("a", "a")
end
end
class Tests
def self.suite
suite = Test::Unit::TestSuite.new
suite << MySuite.new(MyTestCase)
suite << AnotherTestCase.suite
suite
end
end
Test::Unit::UI::Console::TestRunner.run(Tests.suite)
The Test::Unit documentation documentation provides a good explanation on how suites work.
Well, I accomplished basically the same way in a really ugly and horrible fashion, but it was quicker. :) Once I realized that the tests are run alphabetically:
class MyTests < Test::Unit::TestCase
def test_AASetup # I have a few tests that start with "A", but I doubt any will start with "Aardvark" or "Aargh!"
#Run setup code
end
def MoreTests
end
def test_ZTeardown
#Run teardown code
end
It aint pretty, but it works :)
To solve this problem I used the setup construct, with only one test method followed. This one testmethod is calling all other tests.
For instance
class TC_001 << Test::Unit::TestCase
def setup
# do stuff once
end
def testSuite
falseArguments()
arguments()
end
def falseArguments
# do stuff
end
def arguments
# do stuff
end
end
I know this is quite an old post, but I had the issue (and had already written classes using Tes/unit) and ave answered using another method, so if it can help...
If you only need the equivalent of the startup function, you can use the class variables:
class MyTest < Test::Unit::TestCase
##cmptr = nil
def setup
if ##cmptr.nil?
##cmptr = 0
puts "runs at first test only"
##var_shared_between_fcs = "value"
end
puts 'runs before each test'
end
def test_stuff
assert(true)
end
end
I came across this exact problem and created a subclass of Test::Unit::TestCase for doing exactly what you describe.
Here's what I came up with. It provides it's own setup and teardown methods that count the number of methods in the class that begin with 'test'. On the first call to setup it calls global_setup and on the last call to teardown it calls global_teardown
class ImprovedUnitTestCase < Test::Unit::TestCase
cattr_accessor :expected_test_count
def self.global_setup; end
def self.global_teardown; end
def teardown
if((self.class.expected_test_count-=1) == 0)
self.class.global_teardown
end
end
def setup
cls = self.class
if(not cls.expected_test_count)
cls.expected_test_count = (cls.instance_methods.reject{|method| method[0..3] != 'test'}).length
cls.global_setup
end
end
end
Create your test cases like this:
class TestSomething < ImprovedUnitTestCase
def self.global_setup
puts 'global_setup is only run once at the beginning'
end
def self.global_teardown
puts 'global_teardown is only run once at the end'
end
def test_1
end
def test_2
end
end
The fault in this is that you can't provide your own per-test setup and teardown methods unless you use the setup :method_name class method (only available in Rails 2.X?) and if you have a test suite or something that only runs one of the test methods, then the global_teardown won't be called because it assumes that all the test methods will be run eventually.
Use the TestSuite as #romulo-a-ceccon described for special preparations for each test suite.
However I think it should be mentioned here that Unit tests are ment to run in total isolation. Thus the execution flow is setup-test-teardown which should guarantee that each test run undisturbed by anything the other tests did.
I created a mixin called SetupOnce. Here's an example of using it.
require 'test/unit'
require 'setuponce'
class MyTest < Test::Unit::TestCase
include SetupOnce
def self.setup_once
puts "doing one-time setup"
end
def self.teardown_once
puts "doing one-time teardown"
end
end
And here is the actual code; notice it requires another module available from the first link in the footnotes.
require 'mixin_class_methods' # see footnote 1
module SetupOnce
mixin_class_methods
define_class_methods do
def setup_once; end
def teardown_once; end
def suite
mySuite = super
def mySuite.run(*args)
#name.to_class.setup_once
super(*args)
#name.to_class.teardown_once
end
return mySuite
end
end
end
# See footnote 2
class String
def to_class
split('::').inject(Kernel) {
|scope, const_name|
scope.const_get(const_name)
}
end
end
Footnotes:
http://redcorundum.blogspot.com/2006/06/mixing-in-class-methods.html
http://infovore.org/archives/2006/08/02/getting-a-class-object-in-ruby-from-a-string-containing-that-classes-name/
+1 for the RSpec answer above by #orion-edwards. I would have commented on his answer, but I don't have enough reputation yet to comment on answers.
I use test/unit and RSpec a lot and I have to say ... the code that everyone has been posting is missing a very important feature of before(:all) which is: #instance variable support.
In RSpec, you can do:
describe 'Whatever' do
before :all do
#foo = 'foo'
end
# This will pass
it 'first' do
assert_equal 'foo', #foo
#foo = 'different'
assert_equal 'different', #foo
end
# This will pass, even though the previous test changed the
# value of #foo. This is because RSpec stores the values of
# all instance variables created by before(:all) and copies
# them into your test's scope before each test runs.
it 'second' do
assert_equal 'foo', #foo
#foo = 'different'
assert_equal 'different', #foo
end
end
The implementations of #startup and #shutdown above all focus on making sure that these methods only get called once for the entire TestCase class, but any instance variables used in these methods would be lost!
RSpec runs its before(:all) in its own instance of Object and all of the local variables are copied before each test is run.
To access any variables that are created during a global #startup method, you would need to either:
copy all of the instance variables created by #startup, like RSpec does
define your variables in #startup into a scope that you can access from your test methods, eg. ##class_variables or create class-level attr_accessors that provide access to the #instance_variables that you create inside of def self.startup
Just my $0.02!

Resources