Timecop 是一个很棒的宝石!我建议使用Timecop.freeze 而不是为您的实例旅行;你想保持你的测试是确定性的。
据我所知,似乎没有办法模拟 SQL 的函数。 Postgres 等一些语言允许重载函数,但您仍然需要一种插入方式,而且似乎没有一种方法可以在 SQL 中使用环境变量。
一位同事似乎确信您实际上可以放弃系统/语言功能并制作自己的功能,但我担心在您这样做之后如何恢复它们。尝试走那条路听起来很痛苦。
解决方案?
这是我今天在解决这个问题时提出的几个“解决方案”。注意:说实话,我并不真正关心他们,但如果测试到位¯\_(ツ)_/¯ 他们至少提供了一种让事情“工作”的方法。
不幸的是,在 SQL 中没有时髦的 gem 来控制时间。我想你会需要一些疯狂的东西,比如数据库插件、黑客、钩子、中间人、一个容器,你可以欺骗 SQL 使其认为系统时间是别的东西。不幸的是,这些 hack 想法都不会是可移植/平台无关的。
显然有一些方法可以set time in a docker container,但这对于本地测试来说听起来像是一个痛苦的开销,并且不适合要设置的每个测试时间的粒度。
另外需要注意的是,对我来说,我们正在运行大型复杂的原始 SQL 查询,这就是为什么当我运行 SQL 文件进行测试时我可以有正确的日期很重要,否则我只能通过 activerecord 来完成就像你提到的那样。
字符串插值
我在一些正在运行的大型查询中遇到了这个问题。
如果您需要推送一些环境变量,这肯定会有所帮助,并且您可以根据需要注入自己的“current_date”。如果您需要在多个查询中利用特定时间,这也会有所帮助。
my_query.rb
<<~HEREDOC
SELECT *
FROM #{@prefix}.my_table
WHERE date < #{@current_date} - INTERVAL '5 DAYS'
HEREDOC
sql_runner.rb
class SqlRunner
def initialize(file_path)
@file_path = file_path
@prefix = ENV['table_prefix']
@current_date = Date.today
end
def run
execute(eval(File.read @file_path))
end
private
def execute(sql)
ActiveRecord::Base.connection.execute(sql)
end
end
肮脏的更新
这个想法是从 ruby land 更新值,将您的“时间复制”时间推送到数据库中,以覆盖 SQL DB 生成的值。您可能需要对时间的更新进行创意,例如查询大于给定时间的时间,该时间不以您将要更新行的 timecop 时间为目标。
我不关心这种方法的原因是因为它最终感觉你只是在测试 activerecord 的功能,因为你不依赖数据库来设置它应该设置的值。您可能在 SQL 中有计算,然后在测试中重新创建以将某些值设置为正确的日期,然后您不再在 SQL 中进行计算,因此您甚至没有实际测试它。
large_insert.sql
INSERT INTO some_table (
name,
created_on
)
SELECT
name,
current_date
FROM projects
JOIN people ON projects.id = people.project_id
insert_spec.rb
describe 'insert_test.sql' do
ACTUAL_DATE = Date.today
LARGE_INSERT_SQL = File.read('sql/large_insert.sql')
before do
Timecop.freeze Date.new(2018, 10, 28)
end
after do
Timecop.return
end
context 'populated same_table' do
before do
execute(LARGE_INSERT_SQL)
mock_current_dates(ACTUAL_DATE)
end
it 'has the right date' do
expect(SomeTable.last.created_on).to eq(Date.parse('2018.10.28')
end
end
def execute(sql_command)
ActiveRecord::Base.connection.execute(sql_command)
end
def mock_current_dates(actual_date)
rows = SomeTable.where(created_on: actual_date)
# Use our timecop datetime
rows.update_all(created_on: Date.today)
end
有趣的警告:规范包含在自己的事务中(您可以将其关闭,但这是一个不错的功能),因此如果您的 SQL 中有事务,您需要编写代码将其删除以获取规范,或者让您的跑步者在需要时将您的代码包装在事务中。它们会运行,但随后您的 SQL 将终止规范事务,您将度过一段糟糕的时光。如果你在测试期间走清理路线,你可以创建一个spec/support 来帮助解决这个问题,如果我在一个较新的项目中,我会编写一个运行器,如果你需要它们,将查询包装在事务中——甚至尽管这在 SQL 文件#abstraction 中并不明显。
也许有一些东西可以让你设置系统时间,但修改系统的实际时间听起来很可怕。