2013-12-02 3 views
15

У меня есть MiniTest спецификации:Как утверждают содержимое массива, равнодушен упорядоченности

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?

ответ

11

TL; DR самый прямой способ проверить это, чтобы сортировать массивы перед проверкой их равенства.

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

Еще здесь? Хорошо. Давайте поговорим о сравнении элементов в массиве.

Как бы то ни было, карта {} уже не имеет никакого отношения к тесту и добавляет беспорядок, я бы предпочел не добавлять еще больше.

Ну, это часть проблемы. Ваш 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, который делает в значительной степени то, что вы ищете. Он sorts and compares arrays и печатает хорошее сообщение, если они не совпадают. Мы могли бы сделать что-то подобное.

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. Но для этого вы, вероятно, захотите добавить метод 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 
3

У RSpec есть согласный match_array, который выполняет сопоставление двух массивов независимо от порядка. Вы могли бы сделать следующее, чтобы создать подобный пользовательский Искатель в 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 
2

MiniTest Рельсы Shoulda имеет assert_same_elements assertion, который:

утверждает, что два массива содержат те же самые элементы, одинаковое количество раз. По существу ==, но неупорядочен.

assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes 
+0

ВНИМАНИЕ: Вы должны использовать дополнительные драгоценные камни для этой работы, то есть: Shoulda –