Ruby on Rails教程第11章测试报错:SQLite3数据库锁定异常
Rails教程第11章测试SQLite数据库锁问题
问题现象
运行AccountActivationTest测试类时,只要包含以下测试用例:
test 'should not be able to log in with invalid email' do get edit_account_activation_path(@user.activation_token, email: 'wrong') assert_not is_logged_in? end
就会批量触发错误:
Minitest::UnexpectedError:ActiveRecord::StatementInvalid: SQLite3::BusyException: database is locked
- 单独运行该测试用例时无报错
- 移除该用例后所有测试正常通过
- 仅多个测试组合运行时出现锁冲突
原因分析
SQLite是单文件数据库,同一时间仅允许一个写操作。测试运行时,多个用例的数据库操作(尤其是写操作)可能因事务未及时释放、连续请求处理等原因抢锁,导致数据库锁死。无效邮箱的测试用例可能在请求流程中触发了意外的写操作(如会话记录、错误日志),和其他测试的写操作冲突。
解决方案
1. 优化测试类继承结构
若UsersSignup父类的事务处理存在异常,可改为直接继承集成测试基类,并迁移setup逻辑:
class AccountActivationTest < ActionDispatch::IntegrationTest def setup post users_path, params: { user: { name: 'Example User', email: 'user@example.com', password: 'password', password_confirmation: 'password' }} @user = assigns(:user) end # 原有测试用例保持不变 end
2. 调整测试运行顺序
在test/test_helper.rb中设置固定运行顺序,减少随机顺序导致的锁冲突:
Minitest::Test.run_order = :sorted
3. 修改SQLite测试配置
在config/database.yml的test环境中关闭同步,缩短锁持有时间:
test: adapter: sqlite3 database: db/test.sqlite3 synchronous: 0
4. 测试结束后强制关闭数据库连接
在测试类中添加teardown方法,确保每个测试释放连接:
def teardown super ActiveRecord::Base.connection.close end
5. 优化激活控制器逻辑
确保无效邮箱请求不触发写操作,先验证邮箱有效性再执行后续逻辑:
class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate log_in user redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end
内容的提问来源于stack exchange,提问作者John Sall
相关产品推荐
相关产品推荐

