测试 1000 个小时学会 Rails - 002 测试!测试!

juanito · 发布于 2012年04月23日 · 最后由 juanito 回复于 2016年06月07日 · 9224 次阅读
1510

1000 个小时学会 Rails 系列

上一回:001 你的第一个 Rails 应用

002 测试,测试!

The general idea behind unit testing is that you write a test method that makes certain assertions about your code, working against a test fixture. A bunch of these test methods are bundled up into a test suite and can be run any time the developer wants. The results of a run are gathered in a test result and displayed to the user through some UI. -- Nathaniel Talbott

好了,Rails 真的很牛。Ruby 是如此优雅精妙,那么程序员真的性福美满了吗?那究竟要如何写出可维护的代码呢?拜读松本行弘的程序世界?不是!连读七遍 Programming Ruby 1.9?有可能!每天上 Ruby-china 学习?这就对了!好了那到底是什么?答案是写测试!

为什么要写测试呢?恩,先谈谈两种程序员吧。一种是自负的程序员,他们觉得:“日出东方,唯我代码不坏”,这种思想其实很好,是社会进步的动力之一。这种人要嘛特别牛,要嘛特别。。。0010,但我们要看清一个事实,是人都会犯错。另一种程序员,怎么著都怕出错,在论坛四处发问,哪本书最好,哪个教程最猛,写程序战战兢兢,就跟相亲挑老婆,结果如墨菲先生预言:只要有可能出错,那就一定会出错。第一种程序员,根本不屑写测试,第二种程序员,拼命学写测试,学的连代码都不会写了。该怎么办?

还是写测试!在编码前先写测试,这有什么好处?预测程序的行为、确保程序如你想的那般工作、有写测试让你更有自信,更容易重构代码,理清你的思路,等等好处。那为什么大家都不写测试呢?呃。。。很多原因是老板太凶、日期太紧,但我相信测试可以救你于水火之中。而 Rails 也替你打点了许多的测试工具,让你不用在「编码->储存->刷新->编码->储存->刷新...」、要是一个个视图、一个个模型、一个个控制器都是如此手工测试,搞得你晚上八点还在公司,一出错又不知从何查起,真是一场永远不会醒的恶梦。测试可以使我们确保程序正常运作,将用户行为转为测试,让我们有一个开发的方向。未来出错时,可以改动现有的测试来适应新的情况,让你可以早日回家陪老婆孩子(家庭第一),正所谓,测试写得好,挣钱挣到老,测试写的巧,上班没烦恼。

月有阴晴圆缺,而生命会自己找到出口,就这样,有了测试驱动开发(Test-Driven Development, TDD)以及行为驱动开发(Behavior-Driven Development, BDD)。TDD 又有一个名字叫做 Red-Green-Refactor。先写好测试,运行,错误一堆(满江红),撰写可以通过测试的代码(绿油油),测试通过后再重构,确保代码仍然工作。这样子的好处是?一来先写测试可以先让你想想程序的思路,与下棋有异曲同功之妙,只不过下棋咱是在脑里演练;二来写测试可以确保你的程序正常工作,预期未来可能发生的错误。然而我们从小的教育不是这样,导致我们害怕犯错,以前可以考试先考差了,再让你重写一次考卷嘛?现在机会来了。写测试吧!

恩,至于这 BDD 与 TDD 精确的定义呢,危机、摆渡百科都有,谷歌百度一下吧,就不赘述了,用了就知道!BDD 是基于 TDD 所造出来的,旨在测试整个程序中,各个“正常工作代码”之间的互动。也就是说呢,TDD 搭配 BDD 使用。TDD 的工具我们将粗浅介绍 Test::Unit,BDD 我们将介绍的是 RSpec 搭配 海豚 (Capybara),小黄瓜 (Cucumber)就别用了,太坑爹了,还是留著配炸酱面吧!

还是不怎么信么?那我再好好讲一次测试的好处,你想想,如果你要测试某个页面的表单,填了某个值会发生什么事,要是这个表单一共有九种变化,共有九种不同表单,九九八十一个变化,还不算你手滑按错的错误,这测起来真是想死的心都有了(爱惜生命!)。让计算机自动帮我们测试,比起手工测试好、省时又省心。另一个好处是,测试可以预防你犯傻。当你犯了一个再明显不过的错误时,测试可以替你找出来,替你的程序做一个健康检查,让你获得更多信息,由测试通知你,总比暴跳如雷的老板来得好。用户永远都有无止尽的好奇心,会在你想都想不到的地方,把东西弄坏!有了之前的测试,你可以轻松的改写之前的测试,来满足这个你未预期的情况,这又叫作回归测试 (Regression Test)

现在我们来看怎么用 Test::Unit 写测试吧!

撰写第一个测试

Ruby 的 Test::Unit 写了一堆的函式库,就在哪里,等著、盼著、寂寞地等你调用它们。接下来我来讲一个简单的 Test::Unit 例子。首先建立一个目录,叫作 example ,在这个目录里你创一个文件:example_test.rb 。记得把测试加上 _test 的字尾,这样子一看就知道这是一个测试文件。打开这个 example_test.rb ,敲入以下代码:

require 'test/unit'

class ExampleTest < Test::Unit::TestCase def test_truth assert true end end

Test::Unit 测试呢,首先引入 Ruby 标准函式库的 'test/unit' 。这让你可以从 Test::Unit::TestCase 继承它所有的东西(要是可以 require '李嘉诚' 就好了,我也不用。。。)。那究竟继承了什么?继承了让你可以在类里面,将名字取为 test_ 打头的方法来写测试的功能,test_xxxx 会被识别成是测试。

第一个测试写好了!!!!!!!(咆哮体),让我们来运行看看:

ruby example_test.rb

你会看到像是这样的输出:

.

Finished tests in 0.000866s, 1154.7344 tests/s, 1154.7344 assertions/s.

1 tests, 1 assertions, 0 failures, 0 errors, 0 skips

那个奇怪的点是什么呢?这是 Test::Unit 告诉你一个测试成功通过的方式, F 代表不通过的测试, E 代表有错误的测试,那 (.)(.) 呢?哈哈开个玩笑,确保你有认真看。跟著点之后是一些统计数据,告诉你一个测试、一个断言 (assertion)、零个失败、零个错误、零个跳过。

在你的测试中你用了一个 assert 方法,你断言传入的参数为真。然而我们传入的是真(非 nilfalse 亦为真),假如不知为何的这个方法失败了,会引起一个 exception 告诉你。

如果你的方法名不是 test_ 打头:

def truth assert true end

你再运行看看 (ruby example_test.rb),会发现

No tests were specified. 1 tests, 1 assertions, 1 failures, 0 errors

这是因为 Test::Unit 调用了缺省的 default_test 缘故,若你使用的是 1.9.3+ 的 Ruby 只会显示没有测试。

所以记得把测试方法名用 test_ 开头!

接下来让我们考虑一个更复杂的方法,建立一个 love_test.rb

require 'test/unit' class LoveTest < Test::Unit::TestCase def test_saved assert Love.saved? end end

当然了,你想确保你的愛情有保存,但是当你运行时 ruby love_test.rb ,哎呀:

NameError: uninitialized constant LoveTest::Love

哪知道你根本还没有愛情,让我们来定义爱情:

class Love

end

那这个愛情要放在测试之前呢,还是测试之后呢?在 Test::Unit 里是都可以,在 Ruby 里面东西都是得先定义才能使用,但是 Test::Unit 会先把所有的代码求值,再来运行测试,所以现在 love_test.rb

require 'test/unit'

class Love

end

class LoveTest < Test::Unit::TestCase def test_saved assert Love.saved? end end

接下来再运行看看 ruby love_test.rb

NoMethodError: undefined method `saved?' for Love:Class

错误信息明确的告诉我们,愛情是不能保质的,看来 Ruby 也挺懂人情事理的,但我們可以自己定義一個保存的方法:

class Love def self.saved? true end end

接著运行你会如预期的发现测试通过,但是有一天,就是風雨交加的那天,不知为何某女把你的测试改成 false

F

Finished tests in 0.001137s, 879.5075 tests/s, 879.5075 assertions/s.

1) Failure: test_saved(LoveTest) [love_test.rb:14]: Failed assertion, no message given.

失败,最惨的是 no message given 。。。

居然什么信息也不给,唉,其实这是 Test::Unit 一贯回报测试失败的方式,

然而某女突然觉得过意不去,又回头给你加上一个更清楚的信息:

assert Love.saved?, "咱俩不合适! ..."

现在你再运行测试,你会得到一个清楚的错误信息

1) Failure: test_saved(LoveTest) [love_test.rb:16]: 咱俩不合适!

嗯,你懂得,我的朋友。。。。。他说风雨中这点痛算什么,擦乾泪不要问,至少我们还有梦!

这就是使用 Test::Unit 非非非非非非常基本的 TDD 了, Test::Unit 是 Rails 缺省的测试框架,在我们漫长的学习 Rails 的旅途中,你将会常常看到它,并且克制不住的或是被逼迫的使用它。

记得哦,爱情是没有保质期的,亲。

距离学会 Rails 还有 965 个小时。。。

待续。。。

延伸阅读

CodeSchool: Rails Testing for Zombies slides

官方:A Guide to Testing Rails Applications

iHower 测试 Testing

Ruby Koans 藉由单元测试学 Ruby

Ruby-china 上关于测试的帖子

Ruby Test::Unit 函式库

为什么女朋友不要我了

下一回:003 RSpec 行为测试简介

共收到 14 条回复
1498

哈哈连延伸阅读里都出现「为什么女朋友不要我了」了…

1510

我写的只是抛砖引玉,重点是推荐看 Code School 给的 PDF,我存到微盘了,大家低调下载。。。

1342

这个系列真是太。。。刺激了

96

我爱你LZ 先留言占座 慢慢看 啊哈

1498

#2楼 @Juanito 楼主是台北人?打简体字不会有压力么…

1510

#5楼 @kfll 跟 DHH 學來的化繁為簡。。。

1904

给力,@Juanito 把教程都写的那么的,恩。。。生动,实在佩服!哈哈

279

楼主的风格很生动啊。。。

861

很好,要是整理成一本书就更方便了

1031

很好~~

2909

很好,估计女孩子看了都会爱上你吗? 太有才了

5599

人才啊,很诙谐啊,深入浅出

17617

写的非常好,人才!

24494

谢谢楼主的才华,高产还质量高。

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