测试 真实代码是如何于测试代码双向交互的?

nightire · 2012年09月09日 · 最后由 nightire 回复于 2012年09月09日 · 2751 次阅读

新手,问的问题有可能比较愚蠢,见谅。

我正在读 The RSpec Book,读到 69-70 页左右的时候遇到了怎么都想不通的问题。

我可以理解 RSpec double 了伪的 output 对象用来通过测试,但真实的代码里,当 Game 类实例化的时候,它是怎么知道 output 对象是哪里来的?

没错,我们是在 step_definitions 里写了 Output 类,并且实例化了 output 对象,但 step_definitions 难道不是属于测试的代码么?为什么真实的代码也可以接收到来自测试代码中的对象呢?

而且,我也能理解 Cucumber 在 env.rb 文件中,将真实代码所在的路径导入到了 $LOAD_PATH 里,所以 Cucumber 可以接触到真实代码以完成测试这个一点也不意外,但是真实代码里面既没有 require 测试代码,也没有类似 env.rb 这样的机制,它究竟是怎么知道 output 对象来自何处呢?

真心迷惑啊,吃不下饭了都😭...求高手指教!

回答这个问题的话,需要自己去翻书,看了上下文后才能回复你。 除非楼下有其他朋友正在看这本书且跟你进度一样,否则你还是把书上得前后上下文交待一下吧。

#1 楼 @lgn21st 是的是的,我忘了说是请了解这本书的高手帮我解答,不好意思。

书我只看到 70 页,内容不算多,所以这两天我已经反复读了三遍,依然摸不着头脑,干脆我把源码贴出来吧,我感觉我可能是卡在一个愚蠢的问题上了,但我就是找不到答案。

应用结构:

- codebreaker/
  - features/
    - step_definitions/
      - game_steps.rb    #1
    - support/
      -env.rb    #2
    - codebreaker_starts_game.feature
  - lib/
    - codebreaker/
      - game.rb    #3
    - codebreaker.rb
  - spec/
    - codebreaker/
      - game_spec.rb    #4
    - spec_helper.rb

问题里提及的几个文件,分别用 #1 ~ #4 标注,下面依次是源码:

#1 game_steps.rb

# 这里定义了用于输出信息的 Output 类
class Output
  def messages
    @messages ||= []
  end

  def puts(message)
    messages << message
  end
end

# 这里实例化……问题是,真实代码如何知道 `output` 对象在这?
def output
  @output ||= Output.new
end

# 以下是 Cucumber 的 Step Definitions
Given ... do
end

When ... do
  # 难道是这里传给 Game,所以真实代码知道的?
  # 问题这是测试代码啊,应用真实运行的时候并不会调用测试代码,对吗?
  game = Codebreaker::Game.new(output)
  game.start
end

Then ... do |message|
  output.messages.should include message
end

#2 env.rb

# 这里导入了应用程序的 PATH,所以 Cucumber 知道我们要处理的是 Ruby
# 这里应该是单向的,对吧?也就是说**测试** 通过这里知道 **应用**,但**应用**并不因为这里而知道**测试**,我说的对吗?

$LOAD_PATH << File.expand_path('../../../lib/', __FILE__)
require 'codebreaker'

#3 game.rb

module Codebreaker
  class Game
    # 这里就是我的疑惑产生的地方:
    # Game 初始化的时候传递了 `output` 进来,它怎么知道 `output` 从哪里来的?
    # output 实例化的时候不是在 #1 的测试代码里面吗?这里又没有 `require`……
    def initialize(output)
      @output = output
    end

    def start
      @output.puts 'Welcome to Codebreaker!'
    end
  end
end

#4 game_spec.rb

require 'spec_helper'

module Codebreaker
  describe Game do
    describe "#start" do
      it "sends a welcome message" do
        # 这里也不是非常理解,`double` 方法是“伪”了一个 output 对象,所以它不关心 output 从哪儿来,只要证明 output 对象有 puts 方法就好了,对吗?
        # 那么 既然 output 是“伪”的,RSpec 又何从知道它一定 should_receive(:puts) 呢?
        output = double('output')
        game = Game.new(output)

        output.should_receive(:puts).with('Welcome to Codebreaker!')
        game.start
      end
      it "prompts for the first guess"
    end
  end
end

所以总结一下我的疑惑就是: 已知:output对象来自于测试代码 #1,它属于 Cucumber,Cucumber 通过 #2 env.rb 接触到真实代码 #3 那么:#1 以及 属于 RSpec 的 #4 又是怎么接触到存在于 Cucumber 中的 output 对象的?这是一种什么原理或机制?

Sorry guys...

看到 p57,我忽然就懂了:

为了让真实的代码能够正确运行,需要建立一个简单的运行脚本,如下:

#! /usr/bi/env ruby
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
require 'codebeaker'

game = Codebreaker::Game.new(STDOUT)
game.start

事实证明,应用环境下传进去的不是什么 output 对象,而是 STDOUT 啊啊啊啊啊! 对不起各位,占用大家宝贵时间了,俺实在是单纯,真的以为写 output 参数就是在找那个坑爹的 output 对象,竟然连 formal parameter 都忘了……我错了😢

需要 登录 后方可回复, 如果你还没有账号请 注册新账号