学习测试的时候,一直不大懂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。