Prev: [ANN] RubyKaigi2010: CRuby Commiter Invitation
Next: Redhat is using ruby and rails in deltacloud effort
From: James Wenton on 23 May 2010 10:00 I'm a little stumped by this problem. Here's some simple code that, for each argument specified, will add specific get/set methods named after that argument. If you write `attr_option :foo, :bar`, then you will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`: module Configurator class Config def initialize() @options = {} end def self.attr_option(*args) args.each do |a| if not self.method_defined?(a) define_method "#{a}" do @options[:"#{a}"] ||= {} end define_method "#{a}=" do |v| @options[:"#{a}"] = v end else throw Exception.new("already have attr_option for #{a}") end end end end end So far, so good. I want to write some RSpec tests to verify this code is actually doing what it's supposed to. But there's a problem! If I invoke `attr_option :foo` in one of the test methods, that method is now forever defined in Config. So a subsequent test will fail when it shouldn't, because `foo` is already defined: it "should support a specified option" do c = Configurator::Config c.attr_option :foo # ... end it "should support multiple options" do c = Configurator::Config c.attr_option :foo, :bar, :baz # Error! :foo already defined # by a previous test. # ... end Is there a way I can give each test an anonymous "clone" of the `Config` class which is independent of the others?
From: Caleb Clausen on 23 May 2010 22:35 On 5/23/10, James Wenton <kkaitan(a)gmail.com> wrote: > I'm a little stumped by this problem. Here's some simple code that, > for each argument specified, will add specific get/set methods named > after that argument. If you write `attr_option :foo, :bar`, then you > will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`: > > module Configurator > class Config > def initialize() > @options = {} > end > > def self.attr_option(*args) > args.each do |a| > if not self.method_defined?(a) > define_method "#{a}" do > @options[:"#{a}"] ||= {} > end > > define_method "#{a}=" do |v| > @options[:"#{a}"] = v > end > else > throw Exception.new("already have attr_option for #{a}") > end > end > end > end > end > > So far, so good. I want to write some RSpec tests to verify this code > is actually doing what it's supposed to. But there's a problem! If I > invoke `attr_option :foo` in one of the test methods, that method is > now forever defined in Config. So a subsequent test will fail when it > shouldn't, because `foo` is already defined: > > it "should support a specified option" do > c = Configurator::Config > c.attr_option :foo > # ... > end > > it "should support multiple options" do > c = Configurator::Config > c.attr_option :foo, :bar, :baz # Error! :foo already defined > # by a previous test. > # ... > end > > Is there a way I can give each test an anonymous "clone" of the > `Config` class which is independent of the others? As I see it, you have several options: 1) remove the exception you are raising at the end of attr_option. 2) intercept and ignore that exception when you call attr_option in your tests. 3) remove the methods you added at the end of each test. Something like this should work: c.send :undef_method, :foo 4) (what you were asking about) make a copy of Configurator::Config before changing it. This should work: c=Configurator::Config.clone PS: c is an especially unlucky choice for a local variable name, I have found. If you ever have to run your program under one of the console-mode debuggers (I do this all the time) it will get confused with the continue command, which is abbreviated c, often with highly frustrating results.
From: Robert Dober on 24 May 2010 07:13 On Sun, May 23, 2010 at 4:00 PM, James Wenton <kkaitan(a)gmail.com> wrote: > I'm a little stumped by this problem. Here's some simple code that, > for each argument specified, will add specific get/set methods named > after that argument. If you write `attr_option :foo, :bar`, then you > will see `#foo/foo=` and `#bar/bar=` instance methods on `Config`: > > module Configurator > class Config > def initialize() > @options = {} > end > > def self.attr_option(*args) > args.each do |a| > if not self.method_defined?(a) > define_method "#{a}" do > @options[:"#{a}"] ||= {} > end > > define_method "#{a}=" do |v| > @options[:"#{a}"] = v > end > else > throw Exception.new("already have attr_option for #{a}") > end > end > end > end > end > > So far, so good. I want to write some RSpec tests to verify this code > is actually doing what it's supposed to. But there's a problem! If I > invoke `attr_option :foo` in one of the test methods, that method is > now forever defined in Config. So a subsequent test will fail when it > shouldn't, because `foo` is already defined: > > it "should support a specified option" do > c = Configurator::Config > c.attr_option :foo > # ... > end > > it "should support multiple options" do > c = Configurator::Config > c.attr_option :foo, :bar, :baz # Error! :foo already defined > # by a previous test. > # ... > end Caleb is right, c is not a good name ;), however lambda{ c.attr_option..... }.should raise_error( WhatWasIt) I call it WhatWasIt because you really should define your own Exception, e.g. IllegalMonitorState = Class::new RuntimeError please take care to subclass RuntimeError, subclassing Exception is waaaay toooo general. HTH R. > > Is there a way I can give each test an anonymous "clone" of the > `Config` class which is independent of the others? > > -- The best way to predict the future is to invent it. -- Alan Kay
From: Ryan Davis on 26 May 2010 17:49
On May 23, 2010, at 07:00 , James Wenton wrote: > So far, so good. I want to write some RSpec tests to verify this code > is actually doing what it's supposed to. But there's a problem! If I > invoke `attr_option :foo` in one of the test methods, that method is > now forever defined in Config. So a subsequent test will fail when it > shouldn't, because `foo` is already defined: > > it "should support a specified option" do > c = Configurator::Config > c.attr_option :foo > # ... > end > > it "should support multiple options" do > c = Configurator::Config > c.attr_option :foo, :bar, :baz # Error! :foo already defined > # by a previous test. > # ... > end Caleb and Robert are nit-picking... 'c' is a perfectly fine name for a variable in a 4 line test/spec. The problem you're having is easily solved by using anonymous subclasses: it "should support a specified option" do c = Class.new(Configurator::Config) c.attr_option :foo # ... end That makes a throwaway class that has all the same features of the superclass without any of the infectious properties of calling your attr methods on the real thing. |