【问题标题】:Testing Maps/Sets with QUnit (or other Unit Testing Tool)使用 QUnit(或其他单元测试工具)测试 Maps/Sets
【发布时间】:2015-09-21 19:54:23
【问题描述】:

我们如何断言 ES6 Maps 和 Sets 的相等性?

例如:

// 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.

目的是断言 Sets/Maps 的元素是相等的。两个断言都应该失败。

Sets/Maps 是否有 deepEqual 的等价物?换句话说,除了手动迭代元素,我们如何深入测试 Set/Map 相等性?

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

编辑

在支持 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 Sets 和 Maps。当该拉取请求被合并时,我们应该能够使用deepEqual() 来比较集合和地图。 (-:

【问题讨论】:

  • 既然您已经知道 QUInt 不会为您做到这一点,您的问题就简化为“是否有理解 ES6 Sets & Maps 的单元测试工具”(这应该是标题)根据 SO 规则,这不是一个好问题。或者,您可以根据您的要求寻找基于代码的答案,但您会主动拒绝这样的答案。那么你在这里寻找什么样的答案呢?
  • 我还不知道 QUnit 不能工作。可能有一些方法可以做到这一点,也许不是通过deepEqual。我还不够傲慢地提出“QUnit 不起作用”这样的主张,因此提出了这个问题。而且我并没有积极拒绝基于代码的答案。我只是想强调一下我在 QUnit 中所做的近似,以及它带来的问题。我正在寻找的答案:有没有办法在 QUnit 中比较 Sets/Maps 它?如果没有,我该怎么办?

标签: javascript unit-testing ecmascript-6 qunit


【解决方案1】:

使用高阶函数的详尽地图比较

我将采用与在类似答案中处理数组比较相同的方式:How to compare arrays in JavaScript?

我会一点一点地看代码,但最后我会有一个完整的可运行示例


浅比较

首先,我们将从通用地图比较函数开始。这样我们就可以对 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) thunk。地图有一个内置的生成器.entries(),一旦发现不匹配的键/值对,我们就可以通过返回一个早期的false 答案来利用惰性求值。这意味着我们必须以稍微特殊的方式编写比较器。

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

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

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

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 对象——如果是,我们在嵌套 Maps 上使用 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

好的,只是为了确保。如果我们在嵌套的地图ef 上调用mapEqual 会发生什么?由于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))

【讨论】:

  • 谢谢。这实际上是我目前正在做的事情,但我不禁觉得这不是最好的方法,因为Array.from() 将导致数组比较。这意味着带有元素 {1: 2} 的 Map 将成功匹配带有元素 [1, 2] 的 Set。
  • 不,不会的。 [['1', 2]] !== [1,2]
  • 您好,请参阅问题的编辑部分。我已经在 Firefox 上测试过了。
  • 我不明白你的意思。正确,IE 不支持Array.from,但也不支持MapSet……所以你有一个更大的问题。我更新了我的答案。
  • @light 我认为这可能会对您有所帮助 github.com/Benvie/continuum 而这个也是 stackoverflow.com/questions/13355486/…
【解决方案2】:

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

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.

【讨论】:

  • 你是joking吗?你的意思是你刚刚创建了一个使用lodash.isEqual 破坏assert.deepEqual 的库?如果你打算使用 Lodash,最好直接在测试中明确使用它——即assert.ok(_.isEqual(m1, m2)),而不是完全替换节点的assert.deepEqual
  • 最重要的是,您的测试甚至没有断言您承诺的行为。更不用说针对其他库编写测试是徒劳的......
  • 不,Lodash 是一个实现细节,assert.ok(_.isEqual(m1, m2)) 不像assert.deepEqual 那样显示错误消息中的值。我的测试处理了我关心的第一个案例,因此发布了0.1.1。如果您想添加更多内容,我愿意接受拉取请求。
猜你喜欢
  • 2011-08-17
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
相关资源
最近更新 更多