iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
自我挑戰組

Rails測試寫起乃!!!系列 第 8

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 !

參考來源:


上一篇
Day07 測試寫起乃- let、let!、subject
下一篇
Day09 測試寫起乃-FactoryBot(1)
系列文
Rails測試寫起乃!!!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言