我有一个最小的规格:
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
?
答案 0 :(得分:14)
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
您的测试中有更多代码行,但更好地传达了意图。然而,我听到你的话语“无关紧要”和“混乱”在我脑海中回荡。我打赌你不喜欢这个解决方案。 “它的工作太多了!”并且“为什么我必须对此负责?”好吧好吧。我们有更多选择。一个更明智的断言怎么样?
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
将断言转换为期望。但要做到这一点,您可能希望添加断言方法,以便可以在每个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
答案 1 :(得分:5)
MiniTest Rails Shoulda有assert_same_elements
assertion,其中:
断言两个数组包含相同的元素,次数相同。基本上==,但无序。
assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
答案 2 :(得分:3)
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 :(得分:2)
您可以像这样在Ruby中使用array substraction:
assert_empty(["A", "B"] - ["B", "A"])
但是请注意以下几点: [“ A”,“ B”]-[“ B”,“ A”] == [] 但是 [“ A”,“ B”,“ B”]-[“ B”,“ A”] == []
因此,仅当其中具有唯一值时才使用此技术。
答案 4 :(得分:1)
如果重复不是问题(标准红宝石),一个选项是使用集合
require 'set'
assert_equals [1,2,3].to_set, [3,2,1].to_set
以其他方式编写自己的assert方法(来自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宝石。
答案 5 :(得分:0)
在性能不是很关键的测试场景中,您可以使用迭代和assert_include
,例如:
return function (dispatch, getState)
其中test_result_items.each { |item| assert_include(expected_items, item) }
是一个数组,其中包含要测试的代码的结果,而test_result_items
是一个数组,其中包含您期望的项(以任何顺序)。
要确保所有项目都存在(并且不存在其他项目),请将以上内容与数组长度的检查结合起来:
expected_items
请注意,如果项目是唯一的,这只会确定两个数组相等。 (因为带有assert_equal expected_items.length, test_result_items.length
的{{1}}实际上确实只有test_result_items
的{{1}}中存在的项目。)