assertions - Cómo afirmar el contenido de una matriz, indiferente del ordenamiento.
minitest (4)
MiniTest Rails Shoulda tiene una aserción assert_same_elements
, que:
Afirma que dos arreglos contienen los mismos elementos, el mismo número de veces. Esencialmente ==, pero desordenado.
assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
Tengo una pequeña especificación:
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
Esto, sin embargo, falla:
Expected: [610897332, 251689721]
Actual: [251689721, 610897332]
Podría ordenarlos a ambos, pero eso agrega desorden:
json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort
Tal como está, el map{}
ya es algo irrelevante para la prueba y al agregar el desorden, prefiero no agregar aún más.
¿Hay una afirmación o un ayudante para comprobar si todos los elementos en enumerator1
están todos en enumerator2
?
Puedes usar la resta de matriz en Ruby así:
assert_empty(["A", "B"] - ["B", "A"])
Pero tenga en cuenta lo siguiente: ["A", "B"] - ["B", "A"] = [] PERO ["A", "B", "B"] - ["B", "A"] = []
Entonces solo usa esta técnica cuando tengas valores únicos ahí. De lo contrario, necesitas comparar los tamaños de matriz, supongo.
RSpec tiene un match_array
matcher que hace coincidir de 2 matrices sin importar el orden. Puedes hacer lo siguiente para crear un comparador personalizado similar en 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
Puede ver este emparejador personalizado en acción en la siguiente prueba:
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
Entonces, con este emparejador personalizado, podría volver a escribir su prueba como:
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 La forma más directa de verificar esto es ordenar las matrices antes de verificar su igualdad.
json.map{|d| d["id"]}.sort.must_equal Database.all.pluck(:id).sort
¿Aún aquí? Bueno. Hablemos de comparar elementos en una matriz.
Tal como está, el mapa {} ya es algo irrelevante para la prueba y al agregar el desorden, prefiero no agregar aún más.
Bueno, eso es parte del problema. Su JSON contiene una matriz de objetos JSON, mientras que llamar a Database.pluck
devolverá algo más, probablemente números enteros. Necesita convertir sus objetos JSON y su consulta para ser el mismo tipo de datos. Por lo tanto, no es exacto decir que el .map{}
es irrelevante, y si se siente como un desorden, eso se debe a que está haciendo muchas cosas en su afirmación. Intente dividir esa línea de código y usar nombres que revelen la intención:
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
Es más líneas de código en su prueba, pero comunica mejor la intención. Y sin embargo escucho sus palabras "irrelevante" y "desorden" haciendo eco en mi mente. Apuesto a que no te gusta esta solución. "Es demasiado trabajo!" Y "¿Por qué tengo que ser responsable de esto?" Bien bien. Tenemos más opciones. ¿Qué tal una afirmación más inteligente?
RSpec tiene un pequeño y agradable emparejador llamado match_array
que hace prácticamente lo que estás buscando. Ordena y compara matrices e imprime un buen mensaje si no coinciden. Podríamos hacer algo similar.
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
"¡Pero eso es una afirmación y no una expectativa!" Si lo se. Relajarse. Puede convertir una aserción en una expectativa llamando a infect_an_assertion
. Pero para hacer esto correctamente, es probable que desee agregar el método de afirmación para que se pueda utilizar en cada prueba de Minitest. Así que en mi archivo test_helper.rb
agregaría lo siguiente:
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
Ahora su aserción se puede utilizar en cualquier prueba, y su expectativa estará disponible en cada objeto.
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