加速测试的方法
这里所说的“速度”有两层含义。
其一,当然是测试运行所用的时间。我们这个小程序的测试已经开始出现慢的趋势假设测试会随着程序一起增长,那么测试速度就会越来越慢。我们的目标是保持代码的可维护行,但又不破坏RSpec为我们提供的代码可读性。
其二,开发人员怎样快速编写清晰明了的测试。
- 第一类,rspec的使用技巧
- 使用let
- 使用驭件(mock)和使用桩件(stub)
- 把慢的测试单独提出来
- 分类Gem包
- 第二类,加载rails的环境
- database_cleaner
- spring
- zeus
- spork
- 第三类,不加载rails环境
- 修改传统测试
使用let
到目前为止,为测试准备通用数据时,我们使用的方法是在before :each块中定义实例变量。还有一种RSpec用户更乐意选择的是使用let()。
let()有两个好处:
- 不用赋值给实例变量就可以缓存值。
- 定义的变量是“惰性计算的”,不调用就不会执行赋值操作。
使用驭件和使用桩件
驭件(mock)是用来替代真实对象的测试对象,也被称为“测试替身”(test double)。 驭件有点类似通过Factory Girl生成的对象,但不会改动数据库中的数据。所以速度快一些。
桩件(stub)是对指定对象方法的重写,返回一个预设的值。也就是说,桩件虽是个虚假方法,但调用时会返回一个真实的值供测试使用。桩件经常用来重写方法的默认功能,特别是在频繁操作数据库或网络密集型交互中。
例如:
要创建联系人的驭件,可以使用Factory Girl提供的build_stubbed()方法。
这个方法会生成一个假冒对象,可以响应很多方法,例如firstname lastname 和fullname。不过所生成的对象不会存入数据库。
要为Contact模型的方法创建桩件,可以使用下面这样的代码,
allow(Contact).to receive(:order).with('lastname,firstname').and_return([contact])
这里我们重现了Contact模型的order作用域,传入一个字符串,指定SQL查询结果的排序方式(按照姓和名字排序) 然后指明希望得到的结果,只有一个元素的数组,元素contact可能是在前面创建的。
传统的慢的测试
describe "GET #show" do let(:widget) { create(:widget) } it "assigns the required 1 to @1" do get :show, id: widget expect(assigns(:widget)).to eq widget end it "renders the :show template " do get :show, id: widget expect(response).to render_template :show end end
bundle exec rspec spec/controllers/widgets_controller_spec.rb --line_number 4
Finished in 0.29162 seconds
改进之后的快的测试
describe "GET #show more faster" do let(:widget){ build_stubbed(:widget, name: 'zhangsan', email: 'zs@126.com') } before :each do Widget.stub(:persisted?).and_return(true) Widget.stub(:order).with('name, email').and_return([widget]) Widget.stub(:find).with(widget.id.to_s).and_return(widget) Widget.stub(:save).and_return(true) end before :each do Widget.stub(:find).with(widget.id.to_s).and_return(widget) get :show, id: widget end it "assigns the requested widget to @widget" do expect(assigns(:widget)).to eq widget end it "renders the :show template" do expect(response).to render_template :show end end
bundle exec rspec spec/controllers/widgets_controller_spec.rb --line_number 18
Finished in 0.06785 seconds
速度提升 76%
分析: 使用let()把一个驭件赋值给widget。然后为Widget 模型和widget实例创建了一些桩件。在控制器中,我们希望能在Widget类和widget实例上调用一些ActiveRecord提供的方法。所以为这些方法创建了桩件,返回的结果和实际的ActiveRecord方法一样。本例中全部的测试数据都由驭件和桩件提供,没有操作数据库,也没有调用Widget模型。
这段测试的优点是,比之前的测试更独立了,现在只需要关注控制器的动作,不用担心模型或数据库等,这么做当然也有缺点,独立是付出了代价的,这段测试的代码量增加了不少。
把慢的测试单独提出来
首先,运行全部的测试 使用 bundle exec rspec spec/ -p 得出最慢的几个测试
然后,把慢的测试都都打上 slow: true 的标签,如下
describe WidgetsController, slow: true do describe "GET #show" do #慢的测试 end end
最后, 在spec/spec_helper.rb文件中
RSpec.configure do |config| #config.filter_run focus: true config.filter_run_excluding slow: trueend
执行,
运行快的测试
bundle exec rspec spec -p
运行慢的测试
bundle exec rspec spec --tag slow -p
分类Gem包
把production、 develop、 test三种环境的Gem包分类加载到
各自的group下,该功能可以提高2%-3%。主要rails启动的时候不需要加载额外的Gem包、从而大幅度提高rails的启动速度。
充分正确使用database_cleaner
为什么要使用database_cleaner?
Rsepc进行测试的时候,如果有一个用例需要创建并保存到数据库中,当再一次进行测试的时候,就会提示该对象已经存在了,创建失败了。所以想要保证每次测试都能正常执行,需要在每次测试用例执行完毕之后将数据库清空。
如:
it { expect {Deal.make!(create_time: Time.now)}.to change{ Deal.count }.from(0).to(1) }
这个测试在运行的时候就会经常出错,所以要使用database_cleaner来清空测试数据库。
database_cleaner 的三种策略
Deletion
This means the database tables are cleaned using a
delete + recreate strategy. In SQL this means using
the DROP TABLE + CREATE TABLE statements. This strategy
would be considered the slowest, since you have to not
only delete the table data, but also the whole
table structure and then recreate it back.
However in case of problems with other methods
this can be considered the safest fallback method.
Truncation
This means the database tables are cleaned using the
SQL TRUNCATE TABLE command. This will simply empty the table
immidiately, without deleting the table structure itself.
Transaction
This means using BEGIN TRANSACTION statements coupled
with ROLLBACK to roll back a sequence of previous
database operations. Think of it as an "undo button"
for databases. I would think this is the most frequently
used cleaning method, and probably the fastest since
changes need not be directly committed to the DB.
我们常用的配置在 spec/spec_helper.rb
Rspec.configure do |config| config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean endend
或者
Rspec.configure do |config|
config.use_transactional_fixtures = false
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation) end
config.before(:each) do
DatabaseCleaner.start end
config.after(:each) do
DatabaseCleaner.clean endend
登录 | 立即注册