吃了大力丸的Ruby

来源:互联网 发布:淘宝网玩具乐高 编辑:程序博客网 时间:2024/04/28 06:00
嗯,我们的功能测试代码常有个小小的需求:当测试代码里的某一个函数抛出异常,系统自动弹出一个调试窗口。调试窗口应该已经包含异常抛出时的上下文信息,以便测试员进行调试。"靠,还以为什么新鲜玩意儿,原来不过是Ruby On Rails玩儿得不爱的breakpoint!“,看贴的老大们开始不屑地评论。呵呵,老大们的话自然不会错,的确是RoR里常用的 breakpoint。但在俺讨论新东西前,先让小的稍稍介绍一下这个牛X的breakpoint。比如说下面这段代码:
02: class T3
03: def test_1
04: a = 0
05: 1/a
06: end
07:
08: def test_2
09: p 'test_2'
10: end
11:
12: end
13:
14: t = T3.new
15: begin
16: t.test_1
17: rescue
18: breakpoint
19: end
20: t.test_2

因为test_1用零除,第18行会被执行,导致如下的irb窗口被弹出。弹出的irb已经包含了执行中断时的所用信息,包括当时的变量,t。我们可以用local_variables查看变量。我们可以用source_lines查看中断前后的代码。我们甚至可以修改test3.rb,然后load 'test3.rb',然后恢复执行。 

为什么要在测试代码里做这些?呵呵。想象一下我们用Watir测试一个复杂的网络应用。在执行20分钟后的第30步时Watir找不到网页上一个按钮了。如果这时这个irb窗口出现,我们通过查看中断时的上下文,发现原来是用来搜索按钮的字串错了。我们于是在测试代码里改动这字串,"reload"改动后的代码或者顺手替Watir点击那个按钮,然后"exit"这个irb窗口,让测试代码继续执行。也就是说,代码出错 -》中断执行 -》调试 -》继续执行。我们都知道,出错时立刻排错的效率可比重头再来高多了。谁都不想重复前29步,等上20分钟。

听上去不错吧?不过俺们是程序员,岂能满足与此。于是新要求来了:如果等到出错时再加begin...rescue breakpoint..end就太晚了。如果没行都加就太麻烦了。能不能在写测试代码时不加任何额外代码,只在一个地方控制一下就行了嗫?比如说,上面的代码改成这样:

 02: class T3
03: def test_1
04: a = 0 05: 1/a
06: end 07:
08: def test_2
09: p 'test_2' 10: end 11:
12: monitor /test/ 13: end 14:
15: t = T3.new
16: t.test_1
17: t.test_2
注意第12行。我们添加一个新函数,凡是名字匹配/test/的函数都会在出错的时候自动弹出breakpoint的irb窗口。
这样就非常方便了。当然,我们也允许monitor :test_1这样粒度更细的语法。这样我们不用多加代码,还可以控制
到底什么时候允许breakpoint生效(比如说,在一个全局配置关闭时让monitor什么都不做)。嗯,AOP的老大们笑
了。用惯Lisp Macro的老大们也笑了。让我们看看Ruby怎么实现吧。同样非常简单。下面是一段基本的代码。我去掉了
许多无关的细节。
我们打开内置的类Module,加入一个关键的instance method, monitor。这个函数,monitor, 把传给它的函数用begin..rescue..breakpoint..end
包装起来。于是,我们的函数自动拥有了breakpoint的功能。简单得令人发指啊。
require 'breakpoint'  
class Module
def match?(element, array)
array.each do |e|
e = e.to_s if e.instance_of?(Symbol)
return true if element.match(e)
end
return false end


def monitored_method_ids(*method_names)
return self.instance_methods(false).inject([]) do |result, method|
result.push(method.to_sym) if match?(method, method_names)
result
end
end


def monitor(*args)
monitored_method_ids(*args).each do |method|

module_eval <<-EVAL_END
alias_method :__#{method.to_i}__, :#{method.to_s}
def #{method.to_s}
begin
__#{method.to_i}__
rescue => e
breakpoint
end
end
EVAL_END
end
end
end
思考题:
  1. 如果我想把上面的monitor实现到测试代码的类里,应该怎么做?提示:class << self
  2. 上面这段代码还有什么重大缺陷?提示:比如说什么信息还不正确?什么已有的功能被破坏?
  3. 那些缺陷怎么修正?提示:breakpoint.rb
  4. 如果我们想把一个函数里的每一行都加上breakpoint功能该怎么做?提示:无损语法树