我想測試一段代碼執行儘可能少的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