jjzjj

javascript - 使用 QUnit(或其他单元测试工具)测试 Maps/Sets

coder 2024-07-20 原文

我们如何断言 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 的元素是相等的。这两个断言都应该失败。

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

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

编辑

在支持 Array.from() 的 Firefox 中,我一直在比较集合和 map :
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()比较集合和 map 。 (-:

最佳答案

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

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

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

浅比较

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

这个mapCompare函数与我们关于应该如何比较 map 的直觉一致 - 我们比较 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)砰。 map 有一个内置生成器,.entries() ,我们可以通过返回一个早期的 false 来利用惰性求值。找到不匹配的键/值对后立即回答。这意味着我们必须以一种稍微特殊的方式编写比较器。
const shortCircuitEqualComparator = (a, b, k) =>
  a === b ? true && k() : false
abm1.get(somekey) 的值和 m2.get(somekey)分别。 如果这两个值严格相等(===),然后我们才想继续比较——在这种情况下,我们返回 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

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

与 Rick & Morty 的深度比较

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

我们使用自定义比较器来简单地检查 ab都是 Map 对象——如果是,我们使用 mapDeepCompare 进行递归。在嵌套 map 上;还要注意调用... && 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 ,我们可以对嵌套 map 执行任何类型的深度比较。请记住,平等只是我们可以检查的事情之一。

事不宜迟,mapDeepEqual .重要的是,我们可以重用我们的 shortCircuitEqualComparator我们之前定义的。这非常清楚地表明我们的比较器可以(重新)用于浅层或深层 Map 比较。
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 会发生什么?在我们的嵌套 map 上 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))

关于javascript - 使用 QUnit(或其他单元测试工具)测试 Maps/Sets,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31215854/

有关javascript - 使用 QUnit(或其他单元测试工具)测试 Maps/Sets的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐