使用QUnit(或其他单元测试工具)测试地图/集合

lig*_*ght 8 javascript unit-testing qunit ecmascript-6

我们如何断言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.
Run Code Online (Sandbox Code Playgroud)

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

是否有deepEqual套装/地图的等价物?换句话说,如果没有手动迭代元素,我们如何深入测试Set/Map的平等?

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

编辑

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

assert.deepEqual(Array.from(m1), Array.from(m2));
Run Code Online (Sandbox Code Playgroud)

但这不适用于其他不支持的浏览器Array.from().即使使用Array.frompolyfill,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.
Run Code Online (Sandbox Code Playgroud)

更新:

补丁目前正在QUnit中进行扩展deepEqual(),以扩展到处理ES6集和地图.当拉取请求合并时,我们应该可以deepEqual()用来比较集合和地图.( - :

Tha*_*you 3

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

\n\n

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

\n\n

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

\n\n
\n\n

浅比较

\n\n

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

\n\n

mapCompare函数符合我们关于如何比较 Map 的直觉 - 我们将 中的每个键m1与 中的每个键进行比较m2。请注意,该特定比较器正在进行浅比较。我们稍后将进行深度比较

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

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

\n\n
const shortCircuitEqualComparator = (a, b, k) =>\n  a === b ? true && k() : false\n
Run Code Online (Sandbox Code Playgroud)\n\n

a和分别b是和的如果两个值严格相等 ( ),则我们才要继续比较 \xe2\x80\x93,在这种情况下,我们返回键/值对比较的剩余部分。另一方面,如果和不匹配,我们可以提前返回并跳过比较其余值 \xe2\x80\x93 即,我们已经知道和不匹配(如果/)m1.get(somekey)m2.get(somekey)===true && k()k()abfalsem1m2 ab不匹配。

\n\n

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

\n\n
const mapEqual = mapCompare (shortCircuitEqualComparator)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
// define two maps that are equal but have keys in different order\nconst a = new Map([[\'b\', 2], [\'a\', 1]])\nconst b = new Map([[\'a\', 1], [\'b\', 2]])\n\n// define a third map that is not equal\nconst c = new Map([[\'a\', 3], [\'b\', 2]])\n\n// verify results\n// a === b should be true\n// b === a should be true\nconsole.log(\'true\', mapEqual(a, b)) // true true\nconsole.log(\'true\', mapEqual(b, a)) // true true\n\n// a === c should be false\n// c === a should be false too\nconsole.log(\'false\', mapEqual(a, c)) // false false\nconsole.log(\'false\', mapEqual(c, a)) // false false\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
\n\n

与《瑞克与莫蒂》的深入比较

\n\n

现在我们有了一个非常甜蜜的mapCompare通用名称,深入比较变得轻而易举。请注意,我们实际上是在mapDeepCompare使用mapCompare自身来实现。

\n\n

我们使用一个自定义比较器来简单地检查 和 是否a都是bMap 对象 \xe2\x80\x93 如果是,我们mapDeepCompare在嵌套 Map 上递归使用;还要注意调用... && k()以确保比较剩余的键/值对。如果 或a是非bMap 对象,则正常比较是使用f,我们k直接传递延续

\n\n
const mapDeepCompare = f => mapCompare ((a, b, k) => {\n  console.log(a, b)\n  if (a instanceof Map && b instanceof Map)\n    return mapDeepCompare (f) (a,b) ? true && k() : false\n  else\n    return f(a,b,k)\n})\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

无需再费周折,mapDeepEqual。重要的是,我们可以重用shortCircuitEqualComparator之前定义的内容。这非常清楚地表明我们的比较器可以(重新)用于浅层深层地图比较。

\n\n
const mapDeepEqual = mapDeepCompare (shortCircuitEqualComparator)  \n
Run Code Online (Sandbox Code Playgroud)\n\n

让我们看看它的工作原理

\n\n
// define two nested maps that are equal but have different key order\nconst e = new Map([\n  [\'a\', 1],\n  [\'b\', new Map([[\'c\', 2]])]\n])\n\nconst f = new Map([\n  [\'b\', new Map([[\'c\', 2]])],\n  [\'a\', 1]\n])\n\n// define a third nested map that is not equal\nconst g = new Map([\n  [\'b\', new Map([\n    [\'c\', 3]  \n  ])],\n  [\'a\', 1]\n])\n\n// e === f should be true\n// f === e should also be true\nconsole.log(\'true\', mapDeepEqual(e, f)) // true\nconsole.log(\'true\', mapDeepEqual(f, e)) // true\n\n// e === g should be false\n// g === e should also be false\nconsole.log(\'false\', mapDeepEqual(e, g)) // false\nconsole.log(\'false\', mapDeepEqual(g, e)) // false\n
Run Code Online (Sandbox Code Playgroud)\n\n

好的,只是为了确定一下。mapEqual如果我们调用嵌套的 Mapse和会发生什么f?由于mapEqual进行了比较,我们期望结果应该是false

\n\n
console.log(\'false\', mapEqual(e, f)) // false\nconsole.log(\'false\', mapEqual(f, e)) // false\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在你就得到了它。ES6 Map 对象的浅层和深层比较。可以编写一组几乎相同的函数来支持 ES6 Set。我将把这个作为练习留给读者。

\n\n
\n\n

可运行代码演示

\n\n

这是将上面的所有代码编译成一个可运行的演示。每次console.log调用都会输出<expected>, <actual>. 因此true, trueorfalse, false将是通过测试 \xe2\x80\x93 而true, false将是失败的测试。

\n\n

\r\n
\r\n
const mapCompare = f => (m1, m2) => {\n  const aux = (it, m2) => {\n    let {value, done} = it.next()\n    if (done) return true\n    let [k, v] = value\n    return f (v, m2.get(k), $=> aux(it, m2))\n  }\n  return aux(m1.entries(), m2) && aux(m2.entries(), m1)\n}\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n