web-dev-qa-db-ja.com

順序に関係なく、配列の内容をアサートする方法

私はミニテスト仕様を持っています:

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{}はすでにテストとは多少無関係であり、雑然としています。これ以上追加したくないのですが。

enumerator1のすべての項目がすべてenumerator2にあるかどうかをテストするためのアサーションまたはヘルパーはありますか?

21
berkes

TL; DRこれをチェックする最も直接的な方法は、配列が等しいかどうかをチェックする前に配列をソートすることです。

json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort

まだここ?はい。配列内の要素の比較について話しましょう。

現状では、map {}はすでにテストとは多少無関係であり、雑然としています。これ以上追加したくないのですが。

まあ、それは問題の一部です。 JSONにはJSONオブジェクトの配列が含まれていますが、Database.pluckを呼び出すと、他の何か、おそらく整数が返されます。 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

テストではコード行が増えますが、意図をより適切に伝えます。それでも、あなたの「無関係」と「雑然」という言葉が私の心に響き渡るのが聞こえます。私はあなたがこの解決策を気に入らないに違いない。 「大変な作業です!」そして、「なぜ[〜#〜] i [〜#〜]がこれに責任を負わなければならないのですか?」オーケーオーケーより多くのオプションがあります。よりスマートなアサーションはどうですか?

RSpecには match_array という名前の素敵な小さなマッチャーがあります。これはあなたが探していることをほぼ実行します。 配列をソートして比較します 一致しない場合はNiceメッセージを出力します。同様のことができます。

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

「しかし、それは主張であり、期待ではありません!」ええ、私は知っています。リラックス。 infect_an_assertionを呼び出すことにより、アサーションを期待値に変えることができます。ただし、これを正しく行うには、すべてのMinitestテストで使用できるようにアサーションメソッドを追加することをお勧めします。したがって、test_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

これで、アサーションを任意のテストで使用できるようになり、すべてのオブジェクトで期待値を利用できるようになります。

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

MiniTest Rails Shouldaには assert_same_elementsアサーション 、これ:

2つの配列に同じ要素が同じ回数含まれていることを表明します。基本的に==ですが、順序付けされていません。

assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
5
James Martin

RSpecにはmatch_array順序に関係なく2つの配列のマッチングを行うマッチャー。 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
3
Justin Ko

array substraction in Ruby次のように使用できます:

assert_empty(["A", "B"] - ["B", "A"])

ただし、次の点に注意してください。["A"、 "B"]-["B"、 "A"] == [][〜#〜] but [〜#〜 ]["A"、 "B"、 "B"]-["B"、 "A"] == []

したがって、この手法は、固有の値がある場合にのみ使用してください。

2
Bijan

1つのオプションは、繰り返しが問題にならない場合にセットを使用することです(標準のRuby)

 require 'set'
 assert_equals [1,2,3].to_set, [3,2,1].to_set

それ以外の場合は、独自のアサートメソッドを記述します( shoulda から)

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!

または、 shoulda gemを直接追加してください。

2
estani

パフォーマンスが重要ではないテストシナリオでは、反復と assert_include を使用できます。次に例を示します。

test_result_items.each { |item| assert_include(expected_items, item) }

ここで、test_result_itemsはテスト対象のコードの結果を含む配列であり、expected_itemsは予想される項目を(任意の順序で)含む配列です。

すべてのアイテムが存在する(そして余分なアイテムが存在しない)ことを確認するには、上記を配列の長さのチェックと組み合わせます。

assert_equal expected_items.length, test_result_items.length

これは、アイテムが一意である場合にのみ2つの配列が等しいことを確立することに注意してください。 (test_result_items['a', 'a', 'a']には、実際にはexpected_items['a', 'b', 'c']に存在するアイテムしか含まれていないためです。)

0
Jon Schneider