使用 QUnit(或其他单元测试工具)测试映射/集

Testing Maps/Sets with QUnit (or other Unit Testing Tool)

本文关键字:测试 映射 测试工具 单元 QUnit 其他 使用      更新时间:2023-09-26

我们如何断言 ES6 地图和集合的相等性?

例如:

// ES6 Map
var m1 = new Map();
m1.set('one', 1);
var m2 = new Map();
m2.set('two', 2);
assert.deepEqual(m1,m2);     // outputs: passed.
// ES6 Set
var s1 = new Set();
s1.add(1);
var s2 = new Set();
s2.add(2);
assert.deepEqual(s1,s2);     // outputs: passed.

目的是断言集合/映射的元素是相等的。这两个断言都应该失败。

集合/地图是否有等效的deepEqual?换句话说,除了手动迭代元素之外,我们如何深入测试 Set/Map 相等性?

如果在 QUnit 中没有办法,是否有适用于 ES6 集和地图的单元测试工具?

编辑

在支持Array.from()的Firefox中,我一直通过以下方式比较集合和映射:

assert.deepEqual(Array.from(m1), Array.from(m2));

但这不适用于其他浏览器,这些浏览器不支持 Array.from() .即使使用Array.from的 polyfill,Chrome/IE 也无法正常工作 - 无论设置的内容如何,Array.from(set)总是生成一个空数组。这可能是由于这些浏览器缺乏对通用可迭代对象的支持。

其次,将其简化为数组的比较可能并不总是合适的。我们最终会得到我认为是误报的情况:

var s = new Set();
s.add([1,2]);
var m = new Map();
m.set(1,2);
assert.deepEqual(Array.from(s), Array.from(m));  // outputs: passed.

更新

QUnit目前正在开发一个补丁,以扩展deepEqual()以处理ES6集和地图。当拉取请求被合并时,我们应该能够使用 deepEqual() 来比较集合和映射。(-:

使用高阶函数进行详尽的映射比较

我将以与在这个类似答案中处理数组比较

相同的方式来处理这个问题:如何在 JavaScript 中比较数组?

我将逐位浏览代码,但最后我将提供一个完整的可运行示例


浅层比较

首先,我们将从一个通用的 Map 比较函数开始。通过这种方式,我们可以对 Map 对象进行各种比较,而不仅仅是测试相等性

这个mapCompare函数符合我们关于如何比较地图的直觉——我们将m1中的每个键与m2中的每个键进行比较。请注意,此特定比较器正在进行浅层比较。我们将在一会儿处理深度比较

const mapCompare = f => (m1, m2) => {
  const aux = (it, m2) => {
    let {value, done} = it.next()
    if (done) return true
    let [k, v] = value
    return f (v, m2.get(k), $=> aux(it, m2))
  }
  return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}

唯一可能不是立即清楚的是$=> aux(it, m2)的笨蛋。映射有一个内置的生成器, .entries() ,我们可以利用惰性求值,一旦找到不匹配的键/值对,就返回早期false答案。这意味着我们必须以一种稍微特殊的方式来编写我们的比较器。

const shortCircuitEqualComparator = (a, b, k) =>
  a === b ? true && k() : false

ab分别是m1.get(somekey)m2.get(somekey)的值如果两个值严格相等(===(,只有这样我们才想继续比较——在这种情况下,我们返回true && k()其中k()是键/值对比较的其余部分。另一方面,如果ab不匹配,我们可以返回早期false并跳过比较其余值——即,我们已经知道,如果任何a/b对不匹配,m1m2不匹配。

最后,我们可以定义mapEqual - 它也很简单。这只是使用我们的特殊shortCircuitEqualComparator mapCompare

const mapEqual = mapCompare (shortCircuitEqualComparator)

让我们快速看一下它是如何工作的

// define two maps that are equal but have keys in different order
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
// define a third map that is not equal
const c = new Map([['a', 3], ['b', 2]])
// verify results
// a === b should be true
// b === a should be true
console.log('true', mapEqual(a, b)) // true true
console.log('true', mapEqual(b, a)) // true true
// a === c should be false
// c === a should be false too
console.log('false', mapEqual(a, c)) // false false
console.log('false', mapEqual(c, a)) // false false

哎呀是的。事情看起来不错...


与瑞克和莫蒂的深度比较

现在我们有了mapCompare通用的甜蜜,深度比较是轻而易举的。请注意,我们实际上是使用 mapCompare 本身来实现mapDeepCompare

我们使用自定义比较器,它只是检查ab是否都是 Map 对象——如果是,我们在嵌套的 Map 上使用mapDeepCompare递归;还要注意调用... && k()以确保比较剩余的键/值对。如果ab是非 Map 对象,则正常比较是使用 f 进行的,我们直接传递延续k

const mapDeepCompare = f => mapCompare ((a, b, k) => {
  console.log(a, b)
  if (a instanceof Map && b instanceof Map)
    return mapDeepCompare (f) (a,b) ? true && k() : false
  else
    return f(a,b,k)
})

现在有了 mapDeepCompare ,我们可以在嵌套地图上执行任何类型的深度比较。请记住,平等只是我们可以检查的事情之一。

事不宜迟,mapDeepEqual.重要的是,我们可以重用我们之前定义的shortCircuitEqualComparator。这非常清楚地表明,我们的比较器可以(重新(用于浅图深图比较。

const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)  

让我们看看它的工作原理

// define two nested maps that are equal but have different key order
const e = new Map([
  ['a', 1],
  ['b', new Map([['c', 2]])]
])
const f = new Map([
  ['b', new Map([['c', 2]])],
  ['a', 1]
])
// define a third nested map that is not equal
const g = new Map([
  ['b', new Map([
    ['c', 3]  
  ])],
  ['a', 1]
])
// e === f should be true
// f === e should also be true
console.log('true', mapDeepEqual(e, f)) // true
console.log('true', mapDeepEqual(f, e)) // true
// e === g should be false
// g === e should also be false
console.log('false', mapDeepEqual(e, g)) // false
console.log('false', mapDeepEqual(g, e)) // false

好的,只是为了确定。如果我们在嵌套的地图上调用mapEqual ef会发生什么?由于mapEqual进行了层比较,因此我们预计结果应该是false

console.log('false', mapEqual(e, f)) // false
console.log('false', mapEqual(f, e)) // false

你有它。ES6 Map 对象的浅层和深度比较。可以编写一组几乎相同的函数来支持 ES6 Set。我将把这作为读者的练习。


可运行代码演示

这是上面编译成单个可运行演示的所有代码。每个console.log调用输出<expected>, <actual> 。因此,true, truefalse, false将是一个及格的测试,而true, false将是一个失败的测试。

// mapCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapCompare = f => (m1, m2) => {
  const aux = (it, m2) => {
    let {value, done} = it.next()
    if (done) return true
    let [k, v] = value
    return f (v, m2.get(k), $=> aux(it, m2))
  }
  return aux(m1.entries(), m2) && aux(m2.entries(), m1)
}
// mapDeepCompare :: ((a, a, (* -> Bool)) -> Bool) -> (Map(k:a), Map(k:a)) -> Bool
const mapDeepCompare = f => mapCompare ((a, b, k) => {
  if (a instanceof Map && b instanceof Map)
    return mapDeepCompare (f) (a,b) ? true && k() : false
  else
    return f(a,b,k)
})
// shortCircuitEqualComparator :: (a, a, (* -> Bool)) -> Bool
const shortCircuitEqualComparator = (a, b, k) =>
  a === b ? true && k() : false
// mapEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapEqual = mapCompare (shortCircuitEqualComparator)
// mapDeepEqual :: (Map(k:a), Map(k:a)) -> Bool
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)
  
// fixtures
const a = new Map([['b', 2], ['a', 1]])
const b = new Map([['a', 1], ['b', 2]])
const c = new Map([['a', 3], ['b', 2]])
const d = new Map([['a', 1], ['c', 2]])
const e = new Map([['a', 1], ['b', new Map([['c', 2]])]])
const f = new Map([['b', new Map([['c', 2]])], ['a', 1]])
const g = new Map([['b', new Map([['c', 3]])], ['a', 1]])
// shallow comparison of two equal maps
console.log('true', mapEqual(a, b))
console.log('true', mapEqual(b, a))
// shallow comparison of two non-equal maps (differing values)
console.log('false', mapEqual(a, c))
console.log('false', mapEqual(c, a))
// shallow comparison of two other non-equal maps (differing keys)
console.log('false', mapEqual(a, d))
console.log('false', mapEqual(d, a))
// deep comparison of two equal nested maps
console.log('true', mapDeepEqual(e, f))
console.log('true', mapDeepEqual(f, e))
// deep comparison of two non-equal nested maps
console.log('false', mapDeepEqual(e, g))
console.log('false', mapDeepEqual(g, e))
// shallow comparison of two equal nested maps
console.log('false', mapEqual(e, f))
console.log('false', mapEqual(f, e))

我刚刚创建了一个库(@nodeguy/断言(来解决这个问题:

const assert = require('@nodeguy/assert')
// ES6 Map
var m1 = new Map();
m1.set('one', 1);
var m2 = new Map();
m2.set('two', 2);
assert.deepEqual(m1,m2);     // outputs: failed.
// ES6 Set
var s1 = new Set();
s1.add(1);
var s2 = new Set();
s2.add(2);
assert.deepEqual(s1,s2);     // outputs: failed.