【问题标题】:Share state between test classes and tested classes在测试类和测试类之间共享状态
【发布时间】:2013-11-06 14:50:54
【问题描述】:

我正在试验 RSpec。 由于我不喜欢模拟,我想使​​用StringIO 对象模拟控制台打印。

所以,我想测试Logger 类是否将Welcome 写入控制台。为此,我的想法是从规范文件中覆盖 Logger 中使用的 puts 方法,这样在其他地方使用 Logger 时实际上没有任何变化。

这里有一些代码:

describe Logger do
    Logger.class_eval do
        def puts(*args)
            ???.puts(*args)
        end
    end

    it 'says "Welcome"' do
end

这样做,我需要在Logger 类和测试类之间共享一些StringIO 对象(它会去问号所在的位置)。

我发现当我在 RSpec 测试中时,selfClass 的一个实例。我最初的想法是做这样的事情:

Class.class_eval do
    attr_accessor :my_io
    @my_io = StringIO.new
end

然后将??? 替换为Class.my_io

当我这样做时,一千个钟声在我脑海中响起,告诉我这样做不是一个干净的方法。

我能做什么?

PS:我还是不明白:

a = StringIO.new
a.print('a')
a.string # => "a"
a.read # => "" ??? WHY???
a.readlines # => [] ???

还是:StringIO.new('hello').readlines # => ["hello"]

【问题讨论】:

    标签: ruby rspec


    【解决方案1】:

    为了回应您的最后一个问题,StringIO 模拟文件行为。当您对其进行写入/打印时,输入光标位于您最后写入的内容之后。如果你写了一些东西并想读回来,你需要重新定位自己(例如rewindseek等),每http://ruby-doc.org/stdlib-1.9.3/libdoc/stringio/rdoc/StringIO.html

    相比之下,StringIO.new('hello')hello 建立为字符串的初始内容,同时保留在 0 的位置。无论如何,string 方法只返回内容,与位置无关。

    【讨论】:

    • 这确实非常适合:)。谢谢!
    【解决方案2】:

    目前尚不清楚为什么 RSpec 中的测试双重机制存在问题。

    也就是说,您共享方法的方法有效,尽管:

    • self 是 RSpec 的 describe 中的匿名类这一事实并不真正相关
    • 除了使用Class 的实例方法之外,您还可以定义自己的类和关联的类方法并“共享”它们,如下所示:

      类 Foo def self.bar(arg) 放(arg) 结尾 结束

      描述“共享字符串”做

      Foo.class_eval 做 def self.puts(*args) MyStringIO.my_io.print(*args) 结尾 结束

      类 MyStringIO @my_io = StringIO.new def self.my_io ; @my_io ;结尾 结束

      它'说“欢迎”'做 Foo.bar("欢迎") expect(MyStringIO.my_io.string).to eql "Welcome" 结束

      结束

    【讨论】:

    • 很抱歉,我不知道 Logger 是一个 Ruby 模块 :)。我的意思是代表一个要测试的通用类(不幸的是,我也将其命名为Logger)。所以重点是测试一个泛型类。 PS:无论如何,当您创建@my_io 并在class_eval 中引用它时,@my_io 不是指的是Logger 类变量吗? (假设self 指向Logger)。
    • 你是对的,当然。对于那个很抱歉。我的编辑窗口和我正在测试的内容之间存在脱节。 ;-) 无论如何,我更新了答案并考虑到您不打算引用 Ruby 的 Logger。
    【解决方案3】:

    Logger 已经允许在construction 上指定输出设备,因此您可以轻松地直接传入您的StringIO,而无需重新定义任何内容:

    require 'logger'
    
    describe Logger do
    
      let(:my_io) { StringIO.new }
      let(:log)   { Logger.new(my_io) }
    
      it 'says welcome' do
        log.error('Welcome')
        expect(my_io.string).to include('ERROR -- : Welcome')
      end
    end
    

    正如其他发帖人所提到的,目前尚不清楚您是打算测试Logger 还是使用它的一些代码。在后者的情况下,请考虑将 injecting 记录器放入客户端代码中。

    this SO question 的答案还显示了在客户端之间共享公共Logger 的几种方法。

    【讨论】:

    • 有一个误会,看彼得回答的评论。
    • 啊,我明白了。我实际上建议使用与 stdlib Logger 相同的设计:允许注入具体的输出设备(在构造期间或通过设置器)。这使得测试变得非常容易(只需传入StringIO),而且将您的Logger 与实际的日志记录目标解耦 - 所有这些都非常简单且没有黑客攻击。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 2018-12-21
    • 2018-04-01
    • 2022-10-04
    • 1970-01-01
    相关资源
    最近更新 更多