计数执行的查询数量

我想testing一段代码执行尽可能less的SQL查询。

ActiveRecord::TestCase似乎有自己的assert_queries方法,这将做到这一点。 但是由于我没有修补ActiveRecord,所以对我来说没有什么用处。

RSpec或ActiveRecord是否提供了任何官方公开的方法来计算在一个代码块中执行的SQL查询的数量?

我想你通过提到assert_queries回答你自己的问题,但是这里是:

我build议看看assert_queries背后的代码,并使用它来构build自己的方法,您可以使用它来计算查询。 这里涉及的主要魔法是这样的:

 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方法来subscribe Rails中事件的通知。 我们通过我们想要订阅的事件作为第一个参数,然后任何响应的对象作为第二个参数。

在这种情况下,执行查询时,我们的小查询计数器将尽职尽责地增加ActiveRecord :: QueryCounter.query_countvariables,但仅限于真正的查询。

无论如何,这很有趣。 我希望它对你有用。

我对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 / 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) 

这是Ryan和Yuriy的解决scheme的另一个expression式,它只是添加到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 

这里有一个版本,可以很容易地计数匹配给定模式的查询。

 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 

根据Jaime的回答,以下内容支持当前testing用例中查询数量的断言,并且在失败的情况下logging这些语句。 我认为把这样的SQL检查和functiontesting结合起来可能是有用的,因为它减less了设置工作量。

 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 

注意查询断言应该立即发生,以防其他断言本身触发额外的查询。