Ruby Koan是一个学习Ruby语言语法的开源项目,利用测试驱动开发的方式让学习者参与其中,得到及时的学习进度反馈。
它的代码比较简单,本文就通过解读它的关键源码来分析一个简单的单元测试框架是如何识别测试类、测试方法,以及如何显示测试进度的。
测试类
新建一个测试类AboutArrays类继承自Neo::Koan
类,就可以使用Neo::Koan
类中的一系列assert方法了。(这些方法定义在moduleNeo::Assertions
中,通过minxin的方式加入Neo::Koan
类)
# ./Neo.rb Neo::Assertions
def assert(condition, msg=nil)
msg ||= "Failed assertion."
flunk(msg) unless condition
true
end
def assert_equal(expected, actual, msg=nil)
msg ||= "Expected #{expected.inspect} to equal #{actual.inspect}"
assert(expected == actual, msg)
end
当不满足assert条件时,就抛出异常。
# ./Neo.rb Neo::Assertions
FailedAssertionError = Class.new(StandardError)
def flunk(msg)
raise FailedAssertionError, msg
end
统计测试类和测试方法
Neo::Koan
重写了ruby中Class类的子类继承回调和Module类的方法添加回调,当继承了Neo::Koan
类时,子类会被加入Koan的类变量@subclasses
中,子类的"test_"开头的方法会被加入子类的类变量@test_methods
中。由此就记录下了测试类的数量,以及测试方法的数量,并且都可以由Neo::Koan
类访问到。
# ./Neo.rb Neo::Koan
# Class methods for the Neo test suite.
class << self
def inherited(subclass)
subclasses << subclass
end
def method_added(name)
testmethods << name if !tests_disabled? && /^test_/ =~ name.to_s
end
...
测试过程的管理
执行ruby path_to_enlightenment.rb
时,因为引入了Neo.rb
,保证在最后会执行这一句:
END {
Neo::Koan.command_line(ARGV)
Neo::ThePath.new.walk # 关注这里
}
Neo::ThePath
负责迭代测试方法,并用Neo::Sencei
来管理异常、记录进度。walk时,就是将所有的测试方法迭代执行,koan_index+1
就是每个测试类的序号,step_count
就是当前测试的是第几个测试方法。
step_count = 0
Neo::Koan.subclasses.each_with_index do |koan,koan_index|
koan.testmethods.each do |method_name|
step = koan.new(method_name, koan.to_s, koan_index+1, step_count+=1)
yield step
end
end
yield方法最后会调用到Neo::Koan
类的meditate方法,该方法会调用无参数的测试方法,并try-catch捕获异常。
# ./Neo.rb Neo::Koan
def meditate
setup
begin
send(name)
rescue StandardError, Neo::Sensei::FailedAssertionError => ex
failed(ex)
ensure
begin
teardown
rescue StandardError, Neo::Sensei::FailedAssertionError => ex
failed(ex) if passed?
end
end
self
end
每一个meditate方法执行完,Neo:Sensei
类都会去检查结果,发现发生异常后,Neo:Sensei
类会抛出异常中断Neo:Path
的迭代;没有异常发生,就增加@pass_count
。
打印到屏幕上的结果,显示了pass的测试方法、中断测试的fail的测试方法、异常的msg和关键堆栈、测试进度。
# ./Neo.rb Neo::Sensei
def instruct
if failed?
# “ClassName#methodName"
@observations.each{|c| puts c }
guide_through_error
show_progress # passed/total
else
end_screen
end
end
TL;DR
总结下,Ruby Koan测试框架的实现:
Neo::Koan
类提供了assert系列方法,测试类继承后可以使用;Neo::Koan
类变量记录了所有的子类列表,每个子类的类变量又记录了所有'test_'开头的方法列表;- ruby运行测试类时,通过
END
语句,在最后开启测试过程; - 测试中,
Neo::Path
实例迭代执行所有的测试方法,Neo::Sensei
实例检查每个方法的执行; - 发生异常时,
Neo::Sensei
实例中断迭代,打印测试执行的进度,异常的信息。