测试概念之stub篇

学习测试的时候,一直不大懂stub,后来刷多了课程,感觉好像不那么懵逼了。

决定结合自己的学习与思考,说说stub到底是个啥。

BTW,if you want to be a rocking unit tester, you must know stub and mock.:P

正文

先来看一个问题:

你准备测试method A,method A 依赖于method B,比如method A要调用远端的API,而你只想测试method A有没有被正确调用,method B 存在与否,是不是返回了预期的结果,你并不care, 那么要怎么做才能保证测试的独立性?

这样的测试,就要用到stub。

下面以一个movie model来说明一下如何使用stub。

设想一个简单的movie影片查询情景:

用户在TMDb页面中输入影片名称,点击查询按钮,这时MoviesController 中对应的 search_tmdb method会去调用movie model里面真正执行search行为的method,这个method在数据库中进行搜索匹配,最后返回搜索结果,随后controller的 search_tmdb会render到搜索结果的页面,用户看到搜索结果,这样整个的搜索动作才算完成。

现在的情况是,movie model里面没有定义任何执行search 行为的method,而我们只想要验证controller的search_tmdb 调用了model里面执行search的method,不管这个method的存不存在,有没有返回预期结果,我们不care, 我们只想确定controller的search_tmdb做了正确的事,怎么破?

用stub来打破依赖。用一个伪造的method去替代,确保这个伪造的method被调用即可。

你的movies_controller.rb中定义了一个search_tmdb,但是里面啥也没有:

def search_tmdb
end

你的test文件长这样:

这时运行测试文件,会报错,因为你的search_tmdb并没有说让Movie去调用这个find_in_tmdb,如何让测试通过?

movies_controller.rb中修改search_tmdb method,使它长这样:

def search_tmdb
+ Movie.find_in_tmdb(params[:search_terms])
end

再次运行测试文件,通过!!

这里值得注意的是,你的movie model中,根本就没有find_in_tmdb这个method,很神奇是不是?

事实上,为了打破method之间的依赖,保证测试的独立性,这里伪造了一个针对find_in_tmdb的调用,即使这个find_in_tmdb本身存在model中,也会被忽视,overriding it

这就是Stub了,很特别是不是?我第一次接触的时候,觉得很special。

看第二个测试: selects the Search Results template for rendering

这里我们要确认controller的search_tmdb在调用find_in_tmdb后,是否render到了特定的template,即search_tmdb.html.erb。由于find_in_tmdb并没有定义,故这里也要用stub。怎么写呢?

[这里用了matchers: render_template, response.]

Movie.stub(:find_in_tmbd): 假定执行了find_in_tmdb

expect(response).to render_template: 验证最后的结果,是否render到了search_tmbd.html.erb这个template

运行测试文件,pass。

这里要注意,如果测试失败了,请检查一下你的app/views/movies/下是否存在search_tmbd.html.erb,如果不存在,新建,不然这个测试是无法通过的。

The End

记住一句话,有助于理解stub: The code you wish you had。