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
I currently have a logging setup that looks like this:
require 'active_support'
require 'singleton'
class Worker
def initialize(logger)
#logger = logger
end
def perform
#logger.with_context(["tag1"], "Started work")
Provider.say_hello
end
end
class Vlogger
include Singleton
attr_accessor :logger
def logger
#logger ||= ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
end
def with_context(tags, msg)
#logger.tagged(*tags) { #logger.info msg }
end
end
class Provider
def self.logger
Vlogger.instance
end
def self.say_hello
logger.with_context(["tag2"], "hello")
end
end
v = Vlogger.instance
v.logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
Worker.new(v).perform
My objective with this is to avoid having to pass around the #logger instance variable found in my Worker class. Ideally, I want the Vlogger class to be available to all classes. I'm finding that I'm having to use this pattern quite a bit in my code:
def self.logger
Vlogger.instance
end
Is there a better way to do this? Is my OO design pattern here inherently flawed? Ideally I'm trying to avoid passing around objects when really the object has no state and it should be available to various classes without introducing class or instance methods. Thoughts?
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. ;-)
I need to access the config variables from inside another class of a module.
In test.rb, how can I get the config values from client.rb? #config gives me an uninitialized var. It's in the same module but a different class.
Is the best bet to create a new instance of config? If so how do I get the argument passed in through run.rb?
Or, am I just structuring this all wrong or should I be using attr_accessor?
client.rb
module Cli
class Client
def initialize(config_file)
#config_file = config_file
#debug = false
end
def config
#config ||= Config.new(#config_file)
end
def startitup
Cli::Easy.test
end
end
end
config.rb
module Cli
class Config
def initialize(config_path)
#config_path = config_path
#config = {}
load
end
def load
begin
#config = YAML.load_file(#config_path)
rescue
nil
end
end
end
end
test.rb
module Cli
class Easy
def self.test
puts #config
end
end
end
run.rb
client = Cli::Client.new("path/to/my/config.yaml")
client.startitup
#config is a instance variable, if you want get it from outside you need to provide accessor, and give to Easy class self object.
client.rb:
attr_reader :config
#...
def startitup
Cli::Easy.test(self)
end
test.rb
def self.test(klass)
puts klass.config
end
If you use ##config, then you can acces to this variable without giving a self object, with class_variable_get.
class Lol
##lold = 0
def initialize(a)
##lold = a
end
end
x = Lol.new(4)
puts Lol.class_variable_get("##lold")
I recommend to you read metaprogramming ruby book.
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!