실행된 쿼리 수 카운트
특정 코드가 가능한 한 적은 수의 SQL 쿼리를 수행하는지 테스트하고 싶습니다.
ActiveRecord::TestCase
나름의 것이 있는 것 같다assert_queries
바로 그렇게 할 수 있는 방법이죠.하지만 Active Record에 패치를 적용하지 않기 때문에 별로 도움이 되지 않습니다.
RSpec 또는 Active Record는 코드 블록에서 실행된 SQL 쿼리 수를 공식적이고 공개적으로 카운트하는 방법을 제공합니까?
제 생각에 당신은 자신의 질문에 대해assert_queries
단, 다음과 같습니다.
이 뒤에 있는 코드를 볼 것을 권장합니다.assert_queries
쿼리 카운트에 사용할 수 있는 독자적인 방법을 구축하기 위해 사용합니다.여기서의 주요 매직은 다음과 같습니다.
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
오늘 아침 조금 손질해서 Active Record에서 쿼리 카운트를 하는 부분을 떼어내서 다음과 같이 생각해 냈습니다.
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에 반짝이는 작은 추가입니다.Rails의 이벤트 알림을 구독할 수 있습니다.subscribe
방법.첫 번째 인수로 서브스크라이브하고 싶은 이벤트를 통과하고 다음으로 응답하는 오브젝트를 통과시킵니다.call
두 번째로서
이 경우 쿼리가 실행되면 작은 쿼리 카운터가 ActiveRecord를 올바르게 증가시킵니다.Query Counter(쿼리 카운터).query_count 변수. 단, 실제 쿼리에 한합니다.
아무튼 재밌었어요.당신에게 도움이 되길 바랍니다.
Ryan의 대본에 대한 나의 비전(약간 청소하고 매처로 감싼 것)은 누군가에게는 여전히 현실적이기를 바란다.
spec/support/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/spec_matchers_limit.spec에 대한 설명
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)
다음은 Ryan과 Yuriy의 솔루션의 또 다른 공식입니다.그것은, 유리에의 유저에 의한 기능에 의한 것입니다.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 Cham의 답변 기준)
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
다음은 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
이 문제를 추상화하기 위해 sql_spy라는 작은 보석을 만들었습니다.
Gemfile에 추가하기만 하면 됩니다.
gem "sql_spy"
를 드드안 wrap드 wrap wrap wrap wrap wrap wrap wrap wrap wrap wrap 。SqlSpy.track { ... }
:
queries = SqlSpy.track do
# Some code that triggers ActiveRecord queries
users = User.all
posts = BlogPost.all
end
...어설션에서는 블록의 반환값을 사용합니다.
expect(queries.size).to eq(2)
expect(queries[0].sql).to eq("SELECT * FROM users;")
expect(queries[0].model_name).to eq("User")
expect(queries[0].select?).to be_true
expect(queries[0].duration).to eq(1.5)
유리의 솔루션을 기반으로 테이블별로 쿼리를 체크하는 기능을 추가했습니다.
# spec/support/query_counter.rb
require 'support/matchers/query_limit'
module ActiveRecord
class QueryCounter
attr_reader :queries
def initialize
@queries = Hash.new 0
end
def to_proc
lambda(&method(:callback))
end
def callback(name, start, finish, message_id, values)
sql = values[:sql]
if sql.include? 'SAVEPOINT'
table = :savepoints
else
finder = /select.+"(.+)"\..+from/i if sql.include? 'SELECT'
finder = /insert.+"(.+)".\(/i if sql.include? 'INSERT'
finder = /update.+"(.+)".+set/i if sql.include? 'UPDATE'
finder = /delete.+"(.+)" where/i if sql.include? 'DELETE'
table = sql.match(finder)&.send(:[],1)&.to_sym
end
@queries[table] += 1 unless %w(CACHE SCHEMA).include?(values[:name])
return @queries
end
def query_count(table = nil)
if table
@queries[table]
else
@queries.values.sum
end
end
end
end
RSpec의 매처는
# spec/support/matchers/query_limit.rb
RSpec::Matchers.define :exceed_query_limit do |expected, table|
supports_block_expectations
match do |block|
query_count(table, &block) > expected
end
def query_count(table, &block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.query_count table
end
failure_message_when_negated do |actual|
queries = 'query'.pluralize expected
table_name = table.to_s.singularize.humanize.downcase if table
out = "expected to run a maximum of #{expected}"
out += " #{table_name}" if table
out += " #{queries}, but got #{@counter.query_count table}"
end
end
RSpec::Matchers.define :meet_query_limit do |expected, table|
supports_block_expectations
match do |block|
if expected.is_a? Hash
results = queries_count(table, &block)
expected.all? { |table, count| results[table] == count }
else
query_count(&block) == expected
end
end
def queries_count(table, &block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.queries
end
def query_count(&block)
@counter = ActiveRecord::QueryCounter.new
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
@counter.query_count
end
def message(expected, table, negated = false)
queries = 'query'.pluralize expected
if expected.is_a? Hash
results = @counter.queries
table, expected = expected.find { |table, count| results[table] != count }
end
table_name = table.to_s.singularize.humanize.downcase if table
out = 'expected to'
out += ' not' if negated
out += " run exactly #{expected}"
out += " #{table_name}" if table
out += " #{queries}, but got #{@counter.query_count table}"
end
failure_message do |actual|
message expected, table
end
failure_message_when_negated do |actual|
message expected, table, true
end
end
사용.
expect { MyModel.do_the_queries }.to_not meet_query_limit(3)
expect { MyModel.do_the_queries }.to meet_query_limit(3)
expect { MyModel.do_the_queries }.to meet_query_limit(my_models: 2, other_tables: 1)
언급URL : https://stackoverflow.com/questions/5490411/counting-the-number-of-queries-performed
'programing' 카테고리의 다른 글
angular view에서 full momentjs api를 얻는 방법? (0) | 2023.03.12 |
---|---|
커스텀 업로드 폼을 사용하여 워드프레스에 파일을 업로드하려면 어떻게 해야 하나요? (0) | 2023.03.12 |
Wordpress .htaccess www. 강제 적용 안 함 (0) | 2023.03.12 |
리액트 앱에서 joke를 사용하여 description이 정의되지 않았습니다. (0) | 2023.03.12 |
메뉴의 워드프레스 포스트 태그 (0) | 2023.03.12 |