簡體   English   中英

使用chai-almost和sinon`wateWithMatch`測試浮點邏輯

[英]Testing floating point logic using chai-almost and sinon `calledWithMatch`

我有一個失敗的測試用例,因為Number.EPSILON關閉了測試的值。 我理解為什么會發生這種情況並且相信我需要改變我的測試用例以便容忍這種差異。 我相信使用柴幾乎可以幫助這是有道理的,但我正在努力弄清楚如何將chai-almostsinon-chai整合並且正在尋找想法。

具體而言,我使用的calledWithMatch通過提供方法sinon-chai calledWithMatch方法在兩個對象之間執行深度相等檢查,並且不考慮引用相等性。 我想放寬這種方法來容忍Number.EPSILON差異。

下面的代碼片段突出顯示了測試用例失敗的問題。 因為測試失敗的情況下persist被稱為與失敗,因為我們的期望邊框top由被關閉Number.EPSILON 在這種情況下,測試用例應該通過,因為數據沒有任何錯誤。

 mocha.setup('bdd'); const updater = { updateBoundingBox(boundingBox) { const newBoundingBox = { ...boundingBox }; newBoundingBox.top -= .2; newBoundingBox.top += .2; this.persist(newBoundingBox); }, persist(boundingBox) { console.log('persisting bounding box', boundingBox); } }; describe('example', () => { it('should pass', () => { const persistSpy = sinon.spy(updater, 'persist'); const originalBoundingBox = { top: 0.01, left: 0.01, bottom: 0.01, right: 0.01, }; updater.updateBoundingBox(originalBoundingBox); chai.expect(persistSpy).calledWithMatch(originalBoundingBox); }); }); mocha.run(); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/6.1.4/mocha.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/7.3.2/sinon.min.js"></script> <script> "use strict"; /* eslint-disable no-invalid-this */ (function (sinonChai) { // Module systems magic dance. /* istanbul ignore else */ if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { // NodeJS module.exports = sinonChai; } else if (typeof define === "function" && define.amd) { // AMD define(function () { return sinonChai; }); } else { // Other environment (usually <script> tag): plug in to global chai instance directly. /* global chai: false */ chai.use(sinonChai); } }(function (chai, utils) { var slice = Array.prototype.slice; function isSpy(putativeSpy) { return typeof putativeSpy === "function" && typeof putativeSpy.getCall === "function" && typeof putativeSpy.calledWithExactly === "function"; } function timesInWords(count) { switch (count) { case 1: { return "once"; } case 2: { return "twice"; } case 3: { return "thrice"; } default: { return (count || 0) + " times"; } } } function isCall(putativeCall) { return putativeCall && isSpy(putativeCall.proxy); } function assertCanWorkWith(assertion) { if (!isSpy(assertion._obj) && !isCall(assertion._obj)) { throw new TypeError(utils.inspect(assertion._obj) + " is not a spy or a call to a spy!"); } } function getMessages(spy, action, nonNegatedSuffix, always, args) { var verbPhrase = always ? "always have " : "have "; nonNegatedSuffix = nonNegatedSuffix || ""; if (isSpy(spy.proxy)) { spy = spy.proxy; } function printfArray(array) { return spy.printf.apply(spy, array); } return { affirmative: function () { return printfArray(["expected %n to " + verbPhrase + action + nonNegatedSuffix].concat(args)); }, negative: function () { return printfArray(["expected %n to not " + verbPhrase + action].concat(args)); } }; } function sinonProperty(name, action, nonNegatedSuffix) { utils.addProperty(chai.Assertion.prototype, name, function () { assertCanWorkWith(this); var messages = getMessages(this._obj, action, nonNegatedSuffix, false); this.assert(this._obj[name], messages.affirmative, messages.negative); }); } function sinonPropertyAsBooleanMethod(name, action, nonNegatedSuffix) { utils.addMethod(chai.Assertion.prototype, name, function (arg) { assertCanWorkWith(this); var messages = getMessages(this._obj, action, nonNegatedSuffix, false, [timesInWords(arg)]); this.assert(this._obj[name] === arg, messages.affirmative, messages.negative); }); } function createSinonMethodHandler(sinonName, action, nonNegatedSuffix) { return function () { assertCanWorkWith(this); var alwaysSinonMethod = "always" + sinonName[0].toUpperCase() + sinonName.substring(1); var shouldBeAlways = utils.flag(this, "always") && typeof this._obj[alwaysSinonMethod] === "function"; var sinonMethodName = shouldBeAlways ? alwaysSinonMethod : sinonName; var messages = getMessages(this._obj, action, nonNegatedSuffix, shouldBeAlways, slice.call(arguments)); this.assert( this._obj[sinonMethodName].apply(this._obj, arguments), messages.affirmative, messages.negative ); }; } function sinonMethodAsProperty(name, action, nonNegatedSuffix) { var handler = createSinonMethodHandler(name, action, nonNegatedSuffix); utils.addProperty(chai.Assertion.prototype, name, handler); } function exceptionalSinonMethod(chaiName, sinonName, action, nonNegatedSuffix) { var handler = createSinonMethodHandler(sinonName, action, nonNegatedSuffix); utils.addMethod(chai.Assertion.prototype, chaiName, handler); } function sinonMethod(name, action, nonNegatedSuffix) { exceptionalSinonMethod(name, name, action, nonNegatedSuffix); } utils.addProperty(chai.Assertion.prototype, "always", function () { utils.flag(this, "always", true); }); sinonProperty("called", "been called", " at least once, but it was never called"); sinonPropertyAsBooleanMethod("callCount", "been called exactly %1", ", but it was called %c%C"); sinonProperty("calledOnce", "been called exactly once", ", but it was called %c%C"); sinonProperty("calledTwice", "been called exactly twice", ", but it was called %c%C"); sinonProperty("calledThrice", "been called exactly thrice", ", but it was called %c%C"); sinonMethodAsProperty("calledWithNew", "been called with new"); sinonMethod("calledBefore", "been called before %1"); sinonMethod("calledAfter", "been called after %1"); sinonMethod("calledImmediatelyBefore", "been called immediately before %1"); sinonMethod("calledImmediatelyAfter", "been called immediately after %1"); sinonMethod("calledOn", "been called with %1 as this", ", but it was called with %t instead"); sinonMethod("calledWith", "been called with arguments %*", "%D"); sinonMethod("calledOnceWith", "been called exactly once with arguments %*", "%D"); sinonMethod("calledWithExactly", "been called with exact arguments %*", "%D"); sinonMethod("calledOnceWithExactly", "been called exactly once with exact arguments %*", "%D"); sinonMethod("calledWithMatch", "been called with arguments matching %*", "%D"); sinonMethod("returned", "returned %1"); exceptionalSinonMethod("thrown", "threw", "thrown %1"); })); </script> <div id="mocha"></div> 

我不確定從哪里開始。 如果我直接使用這兩個實體,而不是使用calledWithMatch ,我會使用chai-almost顯式檢查topbottomleftright值。 類似的東西:

expect(newBoundingBox.top).to.almost.equal(boundingBox.top)
expect(newBoundingBox.bottom).to.almost.equal(boundingBox.bottom)
expect(newBoundingBox.left).to.almost.equal(boundingBox.left)
expect(newBoundingBox.right).to.almost.equal(boundingBox.right)

但是在使用calledWithMatch時我無法看到實現這一目標的方法。

我錯過了什么嗎? 有一個簡單的方法嗎?

編輯:當我修補時更新這個。

我認為正確的方法是使用自定義匹配器,但我還沒有工作代碼: https//sinonjs.org/releases/latest/matchers/#custom-matchers

它看起來像的功能相當於calledWithMatch(foo)calledWith(sinon.match(foo))這使得它更清楚了解如何引入一個自定義的匹配器的使用。

好吧,我想通了。

關鍵是要更換sinon-chai方法calledWithMatch其下級實施calledWith(sinon.match 。這使一個自定義一個匹配。我展示一個我在下面的例子中去。

 mocha.setup('bdd'); const updater = { updateBoundingBox(boundingBox) { const newBoundingBox = { ...boundingBox }; newBoundingBox.top -= .2; newBoundingBox.top += .2; this.persist(newBoundingBox); }, persist(boundingBox) { console.log('persisting bounding box', boundingBox); } }; describe('example', () => { it('should pass', () => { const persistSpy = sinon.spy(updater, 'persist'); const originalBoundingBox = { top: 0.01, left: 0.01, bottom: 0.01, right: 0.01, }; updater.updateBoundingBox(originalBoundingBox); chai.expect(persistSpy).calledWith(sinon.match((boundingBox) => { if (!boundingBox) return false; const isLeftEqual = Math.abs(originalBoundingBox.left - boundingBox.left) < Number.EPSILON; const isRightEqual = Math.abs(originalBoundingBox.right - boundingBox.right) < Number.EPSILON; const isTopEqual = Math.abs(originalBoundingBox.top - boundingBox.top) < Number.EPSILON; const isBottomEqual = Math.abs(originalBoundingBox.bottom - boundingBox.bottom) < Number.EPSILON; return isLeftEqual && isRightEqual && isTopEqual && isBottomEqual; })); }); }); mocha.run(); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/6.1.4/mocha.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/4.2.0/chai.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/7.3.2/sinon.min.js"></script> <script> "use strict"; /* eslint-disable no-invalid-this */ (function (sinonChai) { // Module systems magic dance. /* istanbul ignore else */ if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { // NodeJS module.exports = sinonChai; } else if (typeof define === "function" && define.amd) { // AMD define(function () { return sinonChai; }); } else { // Other environment (usually <script> tag): plug in to global chai instance directly. /* global chai: false */ chai.use(sinonChai); } }(function (chai, utils) { var slice = Array.prototype.slice; function isSpy(putativeSpy) { return typeof putativeSpy === "function" && typeof putativeSpy.getCall === "function" && typeof putativeSpy.calledWithExactly === "function"; } function timesInWords(count) { switch (count) { case 1: { return "once"; } case 2: { return "twice"; } case 3: { return "thrice"; } default: { return (count || 0) + " times"; } } } function isCall(putativeCall) { return putativeCall && isSpy(putativeCall.proxy); } function assertCanWorkWith(assertion) { if (!isSpy(assertion._obj) && !isCall(assertion._obj)) { throw new TypeError(utils.inspect(assertion._obj) + " is not a spy or a call to a spy!"); } } function getMessages(spy, action, nonNegatedSuffix, always, args) { var verbPhrase = always ? "always have " : "have "; nonNegatedSuffix = nonNegatedSuffix || ""; if (isSpy(spy.proxy)) { spy = spy.proxy; } function printfArray(array) { return spy.printf.apply(spy, array); } return { affirmative: function () { return printfArray(["expected %n to " + verbPhrase + action + nonNegatedSuffix].concat(args)); }, negative: function () { return printfArray(["expected %n to not " + verbPhrase + action].concat(args)); } }; } function sinonProperty(name, action, nonNegatedSuffix) { utils.addProperty(chai.Assertion.prototype, name, function () { assertCanWorkWith(this); var messages = getMessages(this._obj, action, nonNegatedSuffix, false); this.assert(this._obj[name], messages.affirmative, messages.negative); }); } function sinonPropertyAsBooleanMethod(name, action, nonNegatedSuffix) { utils.addMethod(chai.Assertion.prototype, name, function (arg) { assertCanWorkWith(this); var messages = getMessages(this._obj, action, nonNegatedSuffix, false, [timesInWords(arg)]); this.assert(this._obj[name] === arg, messages.affirmative, messages.negative); }); } function createSinonMethodHandler(sinonName, action, nonNegatedSuffix) { return function () { assertCanWorkWith(this); var alwaysSinonMethod = "always" + sinonName[0].toUpperCase() + sinonName.substring(1); var shouldBeAlways = utils.flag(this, "always") && typeof this._obj[alwaysSinonMethod] === "function"; var sinonMethodName = shouldBeAlways ? alwaysSinonMethod : sinonName; var messages = getMessages(this._obj, action, nonNegatedSuffix, shouldBeAlways, slice.call(arguments)); this.assert( this._obj[sinonMethodName].apply(this._obj, arguments), messages.affirmative, messages.negative ); }; } function sinonMethodAsProperty(name, action, nonNegatedSuffix) { var handler = createSinonMethodHandler(name, action, nonNegatedSuffix); utils.addProperty(chai.Assertion.prototype, name, handler); } function exceptionalSinonMethod(chaiName, sinonName, action, nonNegatedSuffix) { var handler = createSinonMethodHandler(sinonName, action, nonNegatedSuffix); utils.addMethod(chai.Assertion.prototype, chaiName, handler); } function sinonMethod(name, action, nonNegatedSuffix) { exceptionalSinonMethod(name, name, action, nonNegatedSuffix); } utils.addProperty(chai.Assertion.prototype, "always", function () { utils.flag(this, "always", true); }); sinonProperty("called", "been called", " at least once, but it was never called"); sinonPropertyAsBooleanMethod("callCount", "been called exactly %1", ", but it was called %c%C"); sinonProperty("calledOnce", "been called exactly once", ", but it was called %c%C"); sinonProperty("calledTwice", "been called exactly twice", ", but it was called %c%C"); sinonProperty("calledThrice", "been called exactly thrice", ", but it was called %c%C"); sinonMethodAsProperty("calledWithNew", "been called with new"); sinonMethod("calledBefore", "been called before %1"); sinonMethod("calledAfter", "been called after %1"); sinonMethod("calledImmediatelyBefore", "been called immediately before %1"); sinonMethod("calledImmediatelyAfter", "been called immediately after %1"); sinonMethod("calledOn", "been called with %1 as this", ", but it was called with %t instead"); sinonMethod("calledWith", "been called with arguments %*", "%D"); sinonMethod("calledOnceWith", "been called exactly once with arguments %*", "%D"); sinonMethod("calledWithExactly", "been called with exact arguments %*", "%D"); sinonMethod("calledOnceWithExactly", "been called exactly once with exact arguments %*", "%D"); sinonMethod("calledWithMatch", "been called with arguments matching %*", "%D"); sinonMethod("returned", "returned %1"); exceptionalSinonMethod("thrown", "threw", "thrown %1"); })); </script> <div id="mocha"></div> 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM