I have an existing codebase of unit tests where the same classes are defined for each test, and a program that iterates over them. Something like this:
test_files.each do |tf|
load "tests/#{tf}/"+tf
test= ::Kernel.const_get("my_class")
Test::Unit::UI::Console::TestRunner.run( test )
While working on these tests, I've realized that ruby allows you to require classes with the same name from different files, and it merges the methods of the two. This leads to problems as soon as the class hierarchy is not the same: superclass mismatch for class ...
Unfortunately, simply unloading the class is not enough, as there are many other classes in the hierarchy that remain loaded.
Is there a way to execute each test in a different global namespace?
While I figure that using modules would be the way to go, I'm not thrilled with idea of changing the hundreds of existing files by hand.
--EDIT--
Following Cary Swoveland's suggestion, I've moved the test running code to a separate .rb file and am running it using backticks. While this seems to work well, loading the ruby interpreter and requireing all the libraries again has a considerable overhead. Also, cross-platform compatibility requires some extra work.
first I wanted to propose the following solution:
#main.rb
modules = []
Dir.foreach('include') do |file|
if file =~ /\w/
new_module = Module.new
load "include/#{file}"
new_module.const_set("StandardClass", StandardClass)
Object.send(:remove_const, :StandardClass)
modules << new_module
end
end
modules.each do |my_module|
my_module::StandardClass.standard_method
end
#include/first.rb
class StandardClass
def self.standard_method
puts "first method"
end
end
#include/second.rb
class StandardClass
def self.standard_method
puts "second method"
end
end
this wraps each class with it's own module and calls the class inside it's module:
$ruby main.rb
second method
first method
But as you answered there are references to other classes so modules can break them.
I checked this by calling the third class from one of the wrapped classes and got:
include/second.rb:5:in `standard_method': uninitialized constant StandardClass::Independent (NameError)
so probably using backticks is better solution but I hope my answer will be helpful for some other similar situations.
Related
I have a Ruby module in a file called my_module.rb:
module My_module
def my_module_method
puts 'inside my method'
end
end
In a file my_class.rb in the same folder, I have a class contained within the module.
module My_module
class My_class
def my_object_method
My_module.my_module_method
end
end
end
My_module::My_class.new.my_object_method => 'undefined method 'my_module_method''
I was not expecting this error. I assumed that Ruby would run into the line 'My_module.my_module_method' and search for a module called 'My_module' and a method within it called 'my_module_method.' This is what Java does, for example. However, Ruby does not do this. In order to get my_object_method to work, I have to write in my_class.rb:
require 'my_module.rb'
Why doesn't Ruby search for My_module when I call my_object_method? It seems obvious what it should search for and therefore redundant to require the programmer to explicitly write 'yes, Ruby, please allow me to make calls to module-wide methods.' What am I missing?
Ruby doesn't automatically load files. If you need a code from some file, you have to load it (by calling require) explicitly.
Thus, when you run "ruby my_class.rb" it loads only this file and you have to define dependencies between files by yourself.
You seem to have a misunderstanding of how to define a class method. In order to make your method call work, you could define it as def self.my_method_name.
In both classes and modules, methods work the same when you define them as class methods using self. or alternatively the class << self syntax. However instance methods (methods without the self.) work differently in these 2 cases. In classes, as you seem to understand, they're accessible once you instantiate the class using .new. In modules, they're only accessible if you include or extend.
See also:
difference between class method , instance method , instance variable , class variable?
http://www.rortuts.com/ruby/ruby-include-vs-extend/
Oh any by the way. Ruby doesn't enforce any convention where you have 1 file per class (named identically). You need to manually require files wherever you need them. Although there are some frameworks such as Rails which auto-require files, and enforce naming conventions.
I'm writing a Ruby library - call it module MyLibrary - which depends upon a data parser. The parser is specific to the library, but may need to be swapped out for a different parser at a later date, so it makes sense to have it be a class nested in MyLibrary - class MyLibrary::Parser.
MyLibrary exposes a few exception types to the client, and the Parser may hit situations where it wants to raise these exceptions (and MyLibrary is happy to pass them through). So I have the following arrangement:
my_library.rb:
require 'parser'
module MyLibrary
class SomeException < RuntimeError
end
def self.do_it
parser = Parser.new
parser.parse_it
end
end
parser.rb
module MyLibrary
class Parser
def parse_it
if bad_stuff
raise SomeException, "Argh"
end
end
end
end
Now, I want to unit-test Parser using rspec.
parser_spec.rb
require 'parser'
RSpec.describe Parser do
it 'raises when bad stuff happens' do
expect { Parser.new.parse_it }.to raise_error(MyLibrary::SomeException)
end
end
But because I'm unit-testing the Parser and my_library.rb hasn't been required in, SomeException isn't defined.
What's my best method for providing SomeException within Parser's spec? Or am I going about this in completely the wrong way?
I think there are 2 things you could consider.
Building on maxple's comment, unless you have a specific reason for it, the SomeException class could just be defined in parser.rb. That class is the one raising the error, the spec test would work, and anyone requiring that file would have access to that exception. Since you don't even reference it in my_library.rb it seems like it would be fine. Another behavioral reason that supports this approach is if the error is a "Parsing" error, the exception name should reflect that and the Parser is the class that should own that. If the MyLibrary class is just forwarding any exception along, it doesn't matter what it is and it shouldn't be defined there.
If you want to keep the exception defined in my_library.rb, then I would suggest using a spec_helper.rb file, that lives in your spec folder, to load your full library. This is usually done by requiring your top level lib/my_library.rb file. Rspec commonly adds your top level lib folder to the ruby include path for just this purpose. Then each and every spec test file you write should require 'spec_helper'. In my experience this is probably the most idiomatic way to load all common dependencies for unit testing in rspec.
I haven't done much in rspec but wouldn't it help if you move the require 'parser' line from my_library.rb to parser.rb? The way I see it it's the Parser that needs that exception not the other way around.
Like I said my knowledge in this case is limited but I hope that will help, if not good luck in the search! :)
EDIT
In hindsight, it doesn't make any sense to call These::Encryption without actually requiring the dependency first, so the error I get about a missing constant is in fact a valid error.
And regarding These::Helpers - which can be optionally required - I am actually optionally including it by defining it before the rspec run begins.
I'll leave this question open for now, in case anyone has any more insights.
I have a method looking like this:
module My
class Service
extend Forwardable
def_delegators :#helpers, *These::Helpers.delegatable if defined?(These::Helpers)
ENCRYPTED_KEY_PASSWORD = These::Encryption.get('my_password')
end
end
It depends on two external classes helper and encryption. However, I want to test in full-isolation and stub out these two, as they have been tested elsewhere.
How can I do this in the context of the class?
The problem I am facing is that the class gets loaded when I require it, which triggers both the if defined? and #get methods, both failing, because I haven't had the opportunity yet to stub out these classes and their required methods.
One solution I used was to simply define the required method before the require statement:
require 'spec_helper'
module These
class Helpers
def self.delegatable
[:attribute]
end
end
end
require 'service'
describe My::Service do
...
end
But this can become unwieldable fast, and at this point I am doing RSpec's job.
Are there any better ways to solve this issue? Or should I just accept that in this case, the dependencies have to be loaded for the tests to work?
I'd like a very basic example of a tiny base program, that reads in two plugins and registers them. These two plugins hook into the base program in the same way in a unconflicting manner.
I'm very new to metaprogramming in any programming language for that matter, I'm not sure where to start.
i've been working on this very problem for a while now. i've tried a number of different ways to go about doing it and sought advice from a lot of people on it. i'm still not sure if what i have is 'the right way', but it works well and is easy to do.
in my case, i'm specifically looking at configuration and bringing in configuration plugins, but the principle is the same even if the terminology for mine is specific to cnfiguration.
at a very basic level, i have a Configuration class with nothing in it - it's empty. I also have a Configure method which returns the configuration class and lets you call methods on it:
# config.rb
class Configuration
end
class MySystem
def self.configure
#config ||= Configuration.new
yield(#config) if block_given?
#config
end
Dir.glob("plugins/**/*.rb").each{|f| require f}
end
MySystem.configure do |config|
config.some_method
config.some_value = "whatever"
config.test = "that thing"
end
puts "some value is: #{MySystem.configure.some_value}"
puts "test #{MySystem.configure.test}"
to get the some_method and some_value on the configuration class, I have the plugins extend the configuration object via modules:
# plugins/myconfig.rb
module MyConfiguration
attr_accessor :some_value
def some_method
puts "do stuff, here"
end
end
class Configuration
include MyConfiguration
end
and
# plugins/another.rb
module AnotherConfiguration
attr_accessor :test
end
class Configuration
include AnotherConfiguration
end
to load up the plugins, you only need one of code to look for the .rb files in a specific folder and 'require' them. this code can live anywhere as long as it's run right away when the file that contains it is loaded... i would probably put it in the class definition for MySystem or something like that to start with. maybe move it somewhere else when makes sense.
Dir.glob("plugins/**/*.rb").each{|f| require f}
run config.rb and you'll get output that looks like this:
do stuff, here
some value is: whatever
test that thing
there are a lot of options for implementing the various parts of this, but this should get you down the path.
It looks like this project may help: https://github.com/eadz/plugman
I haven't however found anything that will handle embedded (gem) dependencies however. Including Ruby files is straightforward but once your plugins start requiring other libraries then this simple model falls apart (either all the deps have to be installed with your application, or you need some other mechanism to get the plugin dependency gems into the process).
I encountered a problem when trying to test a module with Test::Unit. What I used to do is this:
my_module.rb:
class MyModule
def my_func
5 # return some value
end
end
test_my_module.rb:
require 'test/unit'
require 'my_module'
class TestMyModule < Unit::Test::TestCase
include MyModule
def test_my_func
assert_equal(5, my_func) # test the output value given the input params
end
end
Now the problem is, if my_module declares an initialize method, it gets included in the test class and this causes a bunch of problems since Test::Unit seems to override/generate an initialize method. So I'm wondering what is the best way to test a module?
I'm also wondering wether my module should become a class at this point since the initialize method is made for initializing the state of something. Opinions?
Thanks in advance !
Including an initialize method in a module feels very wrong to me, so I'd rethink that at the very least.
To answer your question about testing this as a module more directly, though, I would create a new, empty class, include your module in it, create an instance of that class, and then test against that instance:
class TestClass
include MyModule
end
class TestMyModule < Unit::Test::TestCase
def setup
#instance = TestClass.new
end
def test_my_func
assert_equal(5, #instance.my_func) # test the output value given the input params
end
end
Yeah, your initialize should definitely suggest that you're going towards a class. A module in ruby often feels like an interface in other languages, as long as you implement some basic things when you include the module you'll get a lot for free.
Enumerable is a great example, as long as you define [] and each when you include Enumerable you suddenly get pop, push, etc.
So my gut feeling about testing modules is that you should probably be testing classes that include the module rather than testing the module itself unless the module is designed to not be included in anything, it's simply a code storage mechanism.