Rspec的數據庫事務:如何清理陳舊數據?
本文轉載自公眾號“讀芯術”(ID:AI_Discovery)。
測試用例之間的陳舊數據是RSpec中競態條件的主要原因之一,包括數據庫Redis、文件等。本文就將討論如何清理數據庫中的陳舊數據。
Rails Rspec后臺默認事務
如果使用rails-rspec,默認情況下,spec/rails_helper.rb中會啟用以下配置:
- RSpec.configuredo |config|
- config.use_transactional_fixtures=true
- end
這意味著“在事務內運行每個示例”,即在示例結束時,所有數據庫的變更都將回滾。
如何讓“事務裝置”實現“在事務內運行每個示例”?
在深入研究Rails 4代碼庫,了解了它在后臺的實際工作之后,我發現了以下內容。setup_fixtures函數中,Rails為每個數據庫連接調用begin_transaction。

在teardown_fixtures函數中,Rails為每個數據庫連接調用rollback_transaction。

這也意味著,如果在應用程序中使用多個數據庫,那么應用程序將為所有數據庫創建事務。
在示例之外創建的數據庫記錄將不會回滾
由于數據庫事務圍繞著每個示例,因此在示例范圍之外創建的任何數據庫記錄都不會回滾,也就是說,在before(:all)、before(:context)或before(:suite)代碼塊中創建的任何數據庫記錄都不會回滾。
這可能導致示例組之間而不是同一組的示例之間的競態條件,因此處理hook問題時要十分小心。
- context'context 1'do
- before(:context) do
- create(:user) # WON'T BE ROLLED-BACK
- end
- beforedo
- create(:user) # will be rolled-back
- end
- # ...
- end
- context'context 2'do
- before(:context) do
- create(:user) # WON'T BE ROLLED-BACK
- end
- # ...
- end
- # BY NOW, THERE ARE 2 USER RECORDS COMMITED TO DATABASE
手動設置數據庫事務
你還可以選擇使用hook手動設置數據庫事務。
- RSpec.configuredo |config|
- config.use_transactional_fixtures=false# DISABLE DEFAULT TRANSACTIONS
- end
- before(:example) do
- ActiveRecord::Base.connection.begin_transaction
- end
- after(:example) do
- conn =ActiveRecord::Base.connection
- conn.rollback_transactionif conn.transaction_open?
- end
- # OR
- around(:example) do |example|
- ActiveRecord::Base.transactiondo
- example.run
- # ROLLBACK after the example finishes.
- # This exception is silently swallowed by ActiveRecord.
- raiseActiveRecord::Rollback
- end
- end
[Rails 4 & Rails 5.0.x]數據庫事務是按線程執行的

由上可知,ActiveRecord數據庫連接是按線程執行的。因此,Rails通過use_transactional_fixtures管理的默認數據庫事務只在主線程中可用。
從技術上說,根據事務回滾策略,一個線程的數據庫記錄將獨立于其他線程。需要訪問其他線程中的某個線程的數據庫數據時請注意這一點,例如Selenium。
[Rails 4 & Rails 5.0.x]JavaScript驅動程序(Selenium)和Capybara Webkit的驗收測試問題。
Selenium在另一個線程上運行,因此它不能與運行RSpec的主線程共享事務。為了讓客戶端應用程序訪問數據庫中的數據,RSpec需要將改動提交。這類情況下可以允許提交數據,然后手動清理數據。
[Rails 4 & Rails 5.0.x]DatabaseCleaner——回滾策略
要解決上述問題,首先需要禁用Rails派生的事務,將config.use_transactional_fixtures設置為false,或干脆刪除它。DatabaseCleaner是一個gem,它提供了清理數據庫的高級策略,例如刪節、事務處理或刪除。
下面是利用DatabaseCleaner處理上述JS驅動程序問題的著名gist:

[從Rails 5.1起]數據庫事務在測試線程之間共享
線程之間的共享數據庫事務由Rails團隊的Eileen完成,并作為Rails 5.1的一部分內容發布。


此更新允許將啟用JS的驗收測試封裝在RSpec的默認事務中,這已經消除了對DatabaseCleaner的需求。
希望本文能幫助你更好地理解RSpec中的數據庫事務。