Ruby on rails 如何断言数组的内容,而不考虑顺序

Ruby on rails 如何断言数组的内容,而不考虑顺序,ruby-on-rails,minitest,assertion,Ruby On Rails,Minitest,Assertion,我有一个小型测试规范: it "fetches a list of all databases" do get "/v1/databases" json = JSON.parse(response.body) json.length.must_equal Database.count json.map{|d| d["id"]}.must_equal Database.all.pluck(:id) end 然而,这并不能: Expected: [6108973

我有一个小型测试规范:

it "fetches a list of all databases" do
  get "/v1/databases"
  json = JSON.parse(response.body)
  json.length.must_equal           Database.count
  json.map{|d| d["id"]}.must_equal Database.all.pluck(:id)
end
然而,这并不能:

Expected: [610897332, 251689721]
  Actual: [251689721, 610897332]
我可以同时订购,但这会增加混乱:

json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort
事实上,
map{}
已经与测试和添加杂波有些无关,我更愿意不添加更多


是否有一个断言或帮助程序来测试
枚举器1
中的所有项是否都在
枚举器2
中?

RSpec有一个
匹配数组
匹配器,该匹配器不分顺序对两个数组进行匹配。可以执行以下操作在Minitest中创建类似的自定义匹配器:

module MiniTest::Assertions

  class MatchEnumerator
    def initialize(expected, actual)
      @expected = expected
      @actual = actual
    end

    def match()
      return result, message
    end

    def result()
      return false unless @actual.respond_to? :to_a
      @extra_items = difference_between_enumerators(@actual, @expected)
      @missing_items = difference_between_enumerators(@expected, @actual)
      @extra_items.empty? & @missing_items.empty?      
    end

    def message()
      if @actual.respond_to? :to_a
        message = "expected collection contained: #{safe_sort(@expected).inspect}\n"
        message += "actual collection contained: #{safe_sort(@actual).inspect}\n"
        message += "the missing elements were: #{safe_sort(@missing_items).inspect}\n" unless @missing_items.empty?
        message += "the extra elements were: #{safe_sort(@extra_items).inspect}\n" unless @extra_items.empty?
      else
        message = "expected an array, actual collection was #{@actual.inspect}"
      end

      message
    end

    private

    def safe_sort(array)
      array.sort rescue array
    end

    def difference_between_enumerators(array_1, array_2)
      difference = array_1.to_a.dup
      array_2.to_a.each do |element|
        if index = difference.index(element)
          difference.delete_at(index)
        end
      end
      difference
    end
  end # MatchEnumerator

  def assert_match_enumerator(expected, actual)
    result, message = MatchEnumerator.new(expected, actual).match
    assert result, message
  end

end # MiniTest::Assertions

Enumerator.infect_an_assertion :assert_match_enumerator, :assert_match_enumerator
您可以在以下测试中看到此自定义匹配器正在运行:

describe "must_match_enumerator" do
  it{ [1, 2, 3].map.must_match_enumerator [1, 2, 3].map }
  it{ [1, 2, 3].map.must_match_enumerator [1, 3, 2].map }
  it{ [1, 2, 3].map.must_match_enumerator [2, 1, 3].map }
  it{ [1, 2, 3].map.must_match_enumerator [2, 3, 1].map }
  it{ [1, 2, 3].map.must_match_enumerator [3, 1, 2].map }
  it{ [1, 2, 3].map.must_match_enumerator [3, 2, 1].map }

  # deliberate failures
  it{ [1, 2, 3].map.must_match_enumerator [1, 2, 1].map }
end
因此,使用此自定义匹配器,您可以将测试重新编写为:

it "fetches a list of all databases" do
  get "/v1/databases"
  json = JSON.parse(response.body)
  json.length.must_equal           Database.count
  json.map{|d| d["id"]}.must_match_enumerator Database.all.pluck(:id)
end

TL;DR最直接的检查方法是在检查数组是否相等之前对数组进行排序

json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort
还在这儿吗?可以让我们来讨论比较数组中的元素

事实上,映射{}已经与测试无关,并且添加了混乱,我更愿意不添加更多

这是问题的一部分。在调用
数据库时,JSON包含一个JSON对象数组。pull
将返回其他内容,可能是整数。您需要将JSON对象和查询转换为相同的数据类型。所以说
.map{}
是不相关的是不准确的,如果它感觉混乱,那是因为你在断言中做了很多事情。尝试将该行代码拆分并使用显示意图的名称:

sorted_json_ids = json.map{|d| d["id"]}.sort
sorted_db_ids   = Database.order(:id).pluck(:id)
sorted_json_ids.must_equal sorted_db_ids
测试中需要更多的代码行,但更好地传达意图。然而我听到你的话“无关”和“杂乱”在我的脑海中回荡。我打赌你不喜欢这个解决方案。“工作太多了!”还有“我为什么要为此负责?”好吧,好吧。我们有更多的选择。一个更聪明的断言如何

RSpec有一个很好的小匹配器,名为,它能完成你所需要的。如果它们不匹配,它会打印一条漂亮的消息。我们可以做类似的事情

def assert_matched_arrays expected, actual
  assert_equal expected.to_ary.sort, actual.to_ary.sort
end

it "fetches a list of all databases" do
  get "/v1/databases"
  json = JSON.parse(response.body)
  assert_matched_arrays Database.pluck(:id), json.map{|d| d["id"]}
end
“但这是一种断言,而不是期望!”是的,我知道。放松。您可以通过调用
感染一个断言
将断言转换为期望。但是为了正确地执行此操作,您可能需要添加断言方法,以便它可以在每个Minitest测试中使用。因此,在我的
test\u helper.rb
文件中,我要添加以下内容:

module MiniTest::Assertions
  ##
  # Fails unless <tt>exp</tt> and <tt>act</tt> are both arrays and
  # contain the same elements.
  #
  #     assert_matched_arrays [3,2,1], [1,2,3]

  def assert_matched_arrays exp, act
    exp_ary = exp.to_ary
    assert_kind_of Array, exp_ary
    act_ary = act.to_ary
    assert_kind_of Array, act_ary
    assert_equal exp_ary.sort, act_ary.sort
  end
end

module MiniTest::Expectations
  ##
  # See MiniTest::Assertions#assert_matched_arrays
  #
  #     [1,2,3].must_match_array [3,2,1]
  #
  # :method: must_match_array

  infect_an_assertion :assert_matched_arrays, :must_match_array
end

MiniTest Rails应具有以下功能:

断言两个数组包含相同的元素,次数相同。本质上==,但无序

您可以在Ruby中这样使用:

assert_empty(["A", "B"] - ["B", "A"])
但请注意以下几点: [“A”,“B”]-[“B”,“A”]=[] 但是 [“A”、“B”、“B”]-[“B”、“A”]=[]


因此,只有当您在其中具有唯一值时才使用此技术。

在性能不重要的测试场景中,您可以使用迭代,例如:

test_result_items.each { |item| assert_include(expected_items, item) }
其中,
test\u result\u items
是一个包含被测代码结果的数组,
expected\u items
是一个包含预期项目(按任意顺序)的数组

为确保所有项目都存在(且不存在额外项目),请将上述内容与阵列长度检查相结合:

assert_equal expected_items.length, test_result_items.length

请注意,这将仅在项目唯一时确定两个数组相等。(因为带有
['a','a','a']
测试结果项目
确实只有在
['a','b','c']
预期项目
中存在的项目)

如果重复不是问题,一个选项是使用集合(标准ruby)

其他明智的做法是编写自己的断言方法(从)


或者直接添加gem以获得更多信息。

警告:您需要使用其他gem才能实现此功能,即:即使比较大小也不一定能保证相等:
['A','A','B']-['A','B','B']=[]
遵循这样的思路:减去数组不能解决这个问题吗<代码>[:a,:b,:c]-[:a,:a,:a]==[:b,:c]。
test_result_items.each { |item| assert_include(expected_items, item) }
assert_equal expected_items.length, test_result_items.length
 require 'set'
 assert_equals [1,2,3].to_set, [3,2,1].to_set
module Minitest::Assertions
  def assert_same_elements(expected, current, msg = nil)
    assert expected_h = expected.each_with_object({}) { |e, h| h[e] ||= expected.select { |i| i == e }.size }
    assert current_h = current.each_with_object({}) { |e, h| h[e] ||= current.select { |i| i == e }.size}

    assert_equal(expected_h, current_h, msg)
  end
end

assert_same_elements [1,2,3,3], [3,2,1,3] # ok!
assert_same_elements [1,2,3,3], [3,2,1] # fails!