我想测试一段代码执行尽可能少的SQL查询。计算执行的查询的数量
ActiveRecord::TestCase
似乎有自己的assert_queries
方法,这将做到这一点。但由于我没有修补ActiveRecord,所以对我来说没什么用处。
RSpec或ActiveRecord是否提供任何官方公开方法来计算在代码块中执行的SQL查询的数量?
我想测试一段代码执行尽可能少的SQL查询。计算执行的查询的数量
ActiveRecord::TestCase
似乎有自己的assert_queries
方法,这将做到这一点。但由于我没有修补ActiveRecord,所以对我来说没什么用处。
RSpec或ActiveRecord是否提供任何官方公开方法来计算在代码块中执行的SQL查询的数量?
我觉得你提的assert_queries
回答了自己的问题,但在这里有云:
我建议考虑看看后面assert_queries
代码并用它来建立你自己的方法,你可以用它来计算查询。这里涉及的主要法宝是这一行:
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
我有点小炉匠今天上午和拆出来的ActiveRecord的那些查询计数的零件和与此想出了:
module ActiveRecord
class QueryCounter
cattr_accessor :query_count do
0
end
IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]
def call(name, start, finish, message_id, values)
# FIXME: this seems bad. we should probably have a better way to indicate
# the query was cached
unless 'CACHE' == values[:name]
self.class.query_count += 1 unless IGNORED_SQL.any? { |r| values[:sql] =~ r }
end
end
end
end
ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)
module ActiveRecord
class Base
def self.count_queries(&block)
ActiveRecord::QueryCounter.query_count = 0
yield
ActiveRecord::QueryCounter.query_count
end
end
end
您将能够在任何地方参考ActiveRecord::Base.count_queries
方法。将它传递给您的查询已运行的块,它将返回已执行的查询数:
ActiveRecord::Base.count_queries do
Ticket.first
end
对我而言返回“1”。为了使这项工作,把它放在一个文件在lib/active_record/query_counter.rb
并要求其在config/application.rb
文件是这样的:
require 'active_record/query_counter'
变戏法似的!
可能需要一点解释。当我们将这条线称为:
ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)
我们挂钩到Rails 3的小通知框架。对于最新的Rails主要版本来说,这是一个闪光点,没有人真正知道。它允许我们通过使用subscribe
方法来订阅Rails中事件的通知。我们通过我们想要订阅的事件作为第一个参数,然后将任何响应call
的对象作为第二个参数。
在这种情况下,当执行查询时,我们的小查询计数器将尽职地增加ActiveRecord :: QueryCounter.query_count变量,但仅限于实数查询。
无论如何,这很有趣。我希望它对你有用。
我瑞安的脚本(清理了一下,包裹在一个匹配),希望它仍然是实际的人的愿景:
我把这个投机/支持/ query_counter.rb
module ActiveRecord
class QueryCounter
attr_reader :query_count
def initialize
@query_count = 0
end
def to_proc
lambda(&method(:callback))
end
def callback(name, start, finish, message_id, values)
@query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
end
end
end
,并且这是针对spec/support/matchers/exceed_query_limit。RB
RSpec::Matchers.define :exceed_query_limit do |expected|
match do |block|
query_count(&block) > expected
end
failure_message_for_should_not do |actual|
"Expected to run maximum #{expected} queries, got #{@counter.query_count}"
end
def query_count(&block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.query_count
end
end
用法:
expect { MyModel.do_the_queries }.to_not exceed_query_limit(2)
[此要点]中RSpec 3的小更新(https://gist.github.com/rsutphin/af06c9e3dadf658d2293)。 – 2014-07-07 14:41:31
这里是瑞安的另一配方和尤里的解决方案,它只是一个功能添加到您test_helper.rb
:
def count_queries &block
count = 0
counter_f = ->(name, started, finished, unique_id, payload) {
unless payload[:name].in? %w[ CACHE SCHEMA ]
count += 1
end
}
ActiveSupport::Notifications.subscribed(counter_f, "sql.active_record", &block)
count
end
用法就是:
c = count_queries do
SomeModel.first
end
根据Jaime的回答,以下内容支持目前测试用例中查询数量的断言,并且在失败的情况下会记录这些语句。我认为将这样的SQL检查与功能测试结合起来可以很实用,因为它减少了设置工作。
class ActiveSupport::TestCase
ActiveSupport::Notifications.subscribe('sql.active_record') do |name, started, finished, unique_id, payload|
(@@queries||=[]) << payload unless payload[:name].in? %w(CACHE SCHEMA)
end
def assert_queries_count(expected_count, message=nil)
assert_equal expected_count, @@queries.size,
message||"Expected #{expected_count} queries, but #{@@queries.size} queries occurred.#{@@queries[0,20].join(' ')}"
end
# common setup in a super-class (or use Minitest::Spec etc to do it another way)
def setup
@@queries = []
end
end
用法:
def test_something
post = Post.new('foo')
assert_queries_count 1 # SQL performance check
assert_equal "Under construction", post.body # standard functional check
end
注意查询断言应以防其他主张立即发生触发自己额外的查询。
这是一个版本,可以很容易地计算匹配给定模式的查询。
module QueryCounter
def self.count_selects(&block)
count(pattern: /^(\s+)?SELECT/, &block)
end
def self.count(pattern: /(.*?)/, &block)
counter = 0
callback = ->(name, started, finished, callback_id, payload) {
counter += 1 if payload[:sql].match(pattern)
# puts "match? #{!!payload[:sql].match(pattern)}: #{payload[:sql]}"
}
# http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html
ActiveSupport::Notifications.subscribed(callback, "sql.active_record", &block)
counter
end
end
用法:
test "something" do
query_count = count_selects {
Thing.first
Thing.create!(size: "huge")
}
assert_equal 1, query_count
end
(基于海梅湛的答案)
class ActiveSupport::TestCase
def sql_queries(&block)
queries = []
counter = ->(*, payload) {
queries << payload.fetch(:sql) unless ["CACHE", "SCHEMA"].include?(payload.fetch(:name))
}
ActiveSupport::Notifications.subscribed(counter, "sql.active_record", &block)
queries
end
def assert_sql_queries(expected, &block)
queries = sql_queries(&block)
queries.count.must_equal(
expected,
"Expected #{expected} queries, but found #{queries.count}:\n#{queries.join("\n")}"
)
end
end
伟大的脚本。如果您仅将其用于测试,则可以将其放入{spec | test} /support/query_counter.rb文件中。保留应用程序逻辑的lib文件夹。 – Forrest 2012-04-03 03:56:09
任何方式使这项工作.18.7 – 2015-05-01 16:42:08
对于那些寻找RSpec匹配器,这个答案已经变成了一个宝石:['rspec-sqlimit'](https://github.com/nepalez/rspec-sqlimit) 。 – 2017-09-07 18:54:26