Day08 测试写起乃-关於测试如何清除test db资料? & 安装 Database Cleaner

在测试这项范例之前我一直搞不懂在过去测试的时候我记得 test db 不会清除资料,後来查资料才发现原来在安装 rspec 时在 rails_helper.rb 预设会有一行指令

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

这行指令会帮你在进行测试的时候将 it 里建立的资料在 it 结束时进行 rollback,以至於在进行测试的时候不会出错,但他只有在测试的时候才会执行

# user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe 'validations' do
    it { is_expected.to validate_presence_of(:name) }
  end

  describe 'count user' do
    let(:user) { User.create!(name: 'test1') }
    it 'no user' do
      expect(User.count).to eq(0)
    end

    it 'one user' do
      user
      expect(User.count).to eq(1)
    end
  end
end

# rspec spec/models/user_spec.rb
D, [2021-09-08T22:26:59.270813 #35829] DEBUG -- :    (1.4ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:26:59.288039 #35829] DEBUG -- :    (1.2ms)  SELECT "ar_internal_metadata"."value" FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ?  [["key", "schema_sha1"]]
D, [2021-09-08T22:26:59.290522 #35829] DEBUG -- :    (0.1ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:26:59.296831 #35829] DEBUG -- :    (0.5ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-09-08T22:26:59.320237 #35829] DEBUG -- :   TRANSACTION (0.3ms)  begin transaction
D, [2021-09-08T22:26:59.363110 #35829] DEBUG -- :   User Exists? (0.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", ""], ["LIMIT", 1]]
D, [2021-09-08T22:26:59.369438 #35829] DEBUG -- :   User Exists? (0.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" IS NULL LIMIT ?  [["LIMIT", 1]]
D, [2021-09-08T22:26:59.424097 #35829] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
.D, [2021-09-08T22:26:59.426800 #35829] DEBUG -- :   TRANSACTION (0.1ms)  begin transaction
D, [2021-09-08T22:26:59.427675 #35829] DEBUG -- :    (0.2ms)  SELECT COUNT(*) FROM "users"
D, [2021-09-08T22:26:59.429779 #35829] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
.D, [2021-09-08T22:26:59.433351 #35829] DEBUG -- :   TRANSACTION (0.3ms)  begin transaction
D, [2021-09-08T22:26:59.434656 #35829] DEBUG -- :    (0.3ms)  SELECT COUNT(*) FROM "users"
D, [2021-09-08T22:26:59.435220 #35829] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
.

Finished in 0.13114 seconds (files took 4 seconds to load)
3 examples, 0 failures

看 log 会发现进行了 3次 rollback transaction 但如果你设成false

config.use_transactional_fixtures = false

执行第一次会正确,但执行第二次时会出现错误

D, [2021-09-08T22:30:32.644791 #36065] DEBUG -- :    (1.3ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:30:32.664166 #36065] DEBUG -- :    (1.3ms)  SELECT "ar_internal_metadata"."value" FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ?  [["key", "schema_sha1"]]
D, [2021-09-08T22:30:32.666283 #36065] DEBUG -- :    (0.1ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:30:32.670514 #36065] DEBUG -- :    (0.4ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-09-08T22:30:32.716741 #36065] DEBUG -- :   User Exists? (0.6ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", ""], ["LIMIT", 1]]
D, [2021-09-08T22:30:32.720539 #36065] DEBUG -- :   User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" IS NULL LIMIT ?  [["LIMIT", 1]]
.D, [2021-09-08T22:30:32.752356 #36065] DEBUG -- :    (0.2ms)  SELECT COUNT(*) FROM "users"
FD, [2021-09-08T22:30:32.778396 #36065] DEBUG -- :   TRANSACTION (0.1ms)  begin transaction
D, [2021-09-08T22:30:32.778836 #36065] DEBUG -- :   User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "test1"], ["LIMIT", 1]]
D, [2021-09-08T22:30:32.780966 #36065] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
F

Failures:

  1) User count user no user
     Failure/Error: expect(User.count).to eq(0)

       expected: 0
            got: 1

       (compared using ==)
     # ./spec/models/user_spec.rb:11:in `block (3 levels) in <top (required)>'

  2) User count user one user
     Failure/Error: let(:user) { User.create!(name: 'test1') }

     ActiveRecord::RecordInvalid:
       Validation failed: Name has already been taken
     # ./spec/models/user_spec.rb:9:in `block (3 levels) in <top (required)>'
     # ./spec/models/user_spec.rb:15:in `block (3 levels) in <top (required)>'

Finished in 0.10143 seconds (files took 3.88 seconds to load)
3 examples, 2 failures

Failed examples:

rspec ./spec/models/user_spec.rb:10 # User count user no user
rspec ./spec/models/user_spec.rb:14 # User count user one user

这次在 it 之间并没有 rollback,而且我们有设定 validates :name, presence: true, uniqueness: true 所以才会出现 Name has already been taken 错误

所以在 it 'no user'的案例时就会捞到上次执行 rspec 时所残留的资料造成此处的 User.count 为 1,我们到 console 看也能发现确实有资料并未被清除

#rails console -e test
2.6.6 :001 > User.all
D, [2021-09-08T22:35:51.565100 #36206] DEBUG -- :    (0.7ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:35:51.566758 #36206] DEBUG -- :   User Load (0.3ms)  SELECT "users".* FROM "users" /* loading for inspect */ LIMIT ?  [["LIMIT", 11]]
 => #<ActiveRecord::Relation [#<User id: 2, created_at: "2021-09-08 14:32:44.779978000 +0000", updated_at: "2021-09-08 14:32:44.779978000 +0000", name: "test1", email: nil, phone: nil>]>

不过请记得他是在测试时将 it 里建立的资料使用完 rollback 回去 所以我们如果到 console 直接建立一笔资料...

#rails console -e test
2.6.6 :001 > User.create!(name: 'test1')
D, [2021-09-08T22:40:44.682262 #36410] DEBUG -- :    (0.8ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:40:44.707086 #36410] DEBUG -- :   TRANSACTION (0.1ms)  begin transaction
D, [2021-09-08T22:40:44.707786 #36410] DEBUG -- :   User Exists? (0.2ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "test1"], ["LIMIT", 1]]
D, [2021-09-08T22:40:44.710169 #36410] DEBUG -- :   User Create (0.5ms)  INSERT INTO "users" ("created_at", "updated_at", "name") VALUES (?, ?, ?)  [["created_at", "2021-09-08 14:40:44.708173"], ["updated_at", "2021-09-08 14:40:44.708173"], ["name", "test1"]]
D, [2021-09-08T22:40:44.713018 #36410] DEBUG -- :   TRANSACTION (2.2ms)  commit transaction
 => #<User id: 3, created_at: "2021-09-08 14:40:44.708173000 +0000", updated_at: "2021-09-08 14:40:44.708173000 +0000", name: "test1", email: nil, phone: nil>

执行 rspec spec/models/user_spec.rb

D, [2021-09-08T22:41:15.880642 #36446] DEBUG -- :    (0.6ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:41:15.889610 #36446] DEBUG -- :    (0.2ms)  SELECT "ar_internal_metadata"."value" FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ?  [["key", "schema_sha1"]]
D, [2021-09-08T22:41:15.891983 #36446] DEBUG -- :    (0.1ms)  SELECT sqlite_version(*)
D, [2021-09-08T22:41:15.896092 #36446] DEBUG -- :    (0.1ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
D, [2021-09-08T22:41:15.911299 #36446] DEBUG -- :   TRANSACTION (0.1ms)  begin transaction
D, [2021-09-08T22:41:15.934959 #36446] DEBUG -- :   User Exists? (0.3ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", ""], ["LIMIT", 1]]
D, [2021-09-08T22:41:15.938151 #36446] DEBUG -- :   User Exists? (0.1ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" IS NULL LIMIT ?  [["LIMIT", 1]]
D, [2021-09-08T22:41:15.974051 #36446] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
.D, [2021-09-08T22:41:15.975775 #36446] DEBUG -- :   TRANSACTION (0.1ms)  begin transaction
D, [2021-09-08T22:41:15.976678 #36446] DEBUG -- :    (0.2ms)  SELECT COUNT(*) FROM "users"
D, [2021-09-08T22:41:15.998021 #36446] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
FD, [2021-09-08T22:41:15.999450 #36446] DEBUG -- :   TRANSACTION (0.2ms)  begin transaction
D, [2021-09-08T22:41:16.002627 #36446] DEBUG -- :   TRANSACTION (0.2ms)  SAVEPOINT active_record_1
D, [2021-09-08T22:41:16.003363 #36446] DEBUG -- :   User Exists? (0.4ms)  SELECT 1 AS one FROM "users" WHERE "users"."name" = ? LIMIT ?  [["name", "test1"], ["LIMIT", 1]]
D, [2021-09-08T22:41:16.005467 #36446] DEBUG -- :   TRANSACTION (0.1ms)  ROLLBACK TO SAVEPOINT active_record_1
D, [2021-09-08T22:41:16.006180 #36446] DEBUG -- :   TRANSACTION (0.1ms)  rollback transaction
F

Failures:

  1) User count user no user
     Failure/Error: expect(User.count).to eq(0)

       expected: 0
            got: 1

       (compared using ==)
     # ./spec/models/user_spec.rb:11:in `block (3 levels) in <top (required)>'

  2) User count user one user
     Failure/Error: let(:user) { User.create!(name: 'test1') }

     ActiveRecord::RecordInvalid:
       Validation failed: Name has already been taken
     # ./spec/models/user_spec.rb:9:in `block (3 levels) in <top (required)>'
     # ./spec/models/user_spec.rb:15:in `block (3 levels) in <top (required)>'

Finished in 0.10111 seconds (files took 3.64 seconds to load)
3 examples, 2 failures

Failed examples:

rspec ./spec/models/user_spec.rb:10 # User count user no user
rspec ./spec/models/user_spec.rb:14 # User count user one user

确实有进行 rollback 但他并不能把 console 建立的 user 删除,而且有时候在测试的时候也会到 console 建立测试资料看看是否正确,如果真的要清乾净的话这时就可以安装 Database Cleaner

安装 Database Cleaner

官方文件有写到

Instead of using the database_cleaner gem directly, each ORM has its own gem. Most projects will only need the database_cleaner-active_record gem

所以只要安装这个即可 gem 'database_cleaner-active_record'

并且依照官方文件可以在spec_helper.rb

# spec_helper.rb
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end

我们在每个测试档案执行之前设定 DatabaseCleaner 的清除方式且在每个 example(每个it) 执行之前进行清除资料库的动作。

我们就能把 config.use_transactional_fixtures 设为 false

执行後确实测试通过且资料都会被清除

至於 DatabaseCleaner.strategy = :transaction 官方提到有三种策略可以清除 :transactionDeletionTruncation 差异在於

transaction(官方提及可以优先使用)=> 执行的 SQL 包在 BEGIN TRANSACTION 里然後用 rollback 回初始状态

truncation => 直接在资料库 TRUNCATE TABLE 把 table 清空

deletion => 直接在资料库 下 SQL语法 DELETE FROM 删除资料,速度较慢

今天就介绍到这边,明天开始介绍 FactoryBot !

参考来源:


<<:  [想试试看JavaScript ] 流程控制 回圈

>>:  Day 8. Hashicorp Nomad: Application Logs

ISO 15408&SAMM&CMMI&FOCI

通用标准(ISO 15408)指定了评估IT产品而不是供应商资格的标准。 -通用标准评估 FOCI(...

Day 04:专案01 - 超简单个人履历03 | CSS简介

CSS是什麽? CSS,全名为Cascading Style Sheets,中文为阶层式样式表。跟H...

Day04:资料结构 - 阵列(Array)

闲话家常 今天来到了铁人赛的第四天了,我们继续来聊聊资料结构,秉持着一天弄懂一个观念,相信30天的铁...

初学者跪着学JavaScript Day21 : 原型毕露(下)

一日客语:中文:圆 客语: 眼ienˇ 学习内容 检查实例的建构器类型:instanceof、con...

创建App-讯息界面

创建App-讯息界面 今天进行讯息界面,本界面会使用Button、Text Field等基本UI来建...