qunitjs / qunit

🔮 An easy-to-use JavaScript unit testing framework.
https://qunitjs.com
MIT License
4.02k stars 783 forks source link

Facilitate "close to" number equal assertion #1735

Closed Krinkle closed 1 month ago

Krinkle commented 6 months ago

What are you trying to do?

Assert that X is equal to Y within a certain tolerance, eg allow a delta of 0.1 above or below it. It most cases, I believe what people actually mean is to round the number to a certain decimal place before comparing it (except with deltas like 1.0 or higher

Prior art

Plugin: https://github.com/JamesMGreene/qunit-assert-close, https://www.npmjs.com/package/qunit-assert-close

This has been unmaintained for a while and currently has some compat issues with QUnit 2 Assertion API (ie context-based instead of global state). This is for example forked and worked around in https://github.com/jquery/jquery-ui/commit/546214e86956804a1b02da173a4c6c5ddea11454.

Other JS test frameworks:

Other languages:

Proposal

In keeping with my philosophy on assertions, I find myself biased towards not supporting these kind of loose assertions. However, there's no denying that this kind of method is commonly built-in for most test frameworks. That doesn't mean we need to copy it, but it does mean we need to at least have an easily found example and answer for how we approach this situation.

Unlike other loose assertions, this is a case where existing options for partial assertions (such as propEqual and propContains) don't help. And, ad-hoc expressions passed to assert.true seem unreasonably non-trivial, because historically the language hasn't come with an easy way to round a float to a given set of decimal places. Besides, such method would bring unpleasant semantics into it given what a float is. From what I can see, this remains true as of ES2022 (Number toPrecision, toFixed, Math.trunc, Math.round, all come close though).

The approaches I see are to either provide a utility method that "does what I mean" and would be used in conjunction with assert.equal, like assert.equal(q.round(x, 0.1), 2.4)), or to add a new assertion method for this purpose, and thus allow it to be used for the wider set of use cases that include more questionable deltas like fractions larger than 1.0 such as 15.6.

Python's approach based on decimal places is interesting as well. It seems to encourage strictness and not support a false sense of security through arbitrary and needlessly precise deltas.

On the other hand, there is also an argument to be made for easing switching costs, and meeting surface-level expectations, allowing devs to "choose your own adventure". Especially given that we have ESLint nowadays, where the "recommended" preset could discourage use of this method, or limit its use to only passing 1 as the significant digit of the delta parameter (eg 0.1, 0.01, 0.001, etc).

gibson042 commented 6 months ago

I support including this, and there's even a use case in jQuery for asserting that early progress of an element animation approximates the pre-animation state (rather than e.g. introducing a discontinuity based on unit mismatch) at https://github.com/jquery/jquery/blob/99151d7ab0923aa3aeeb1b957a9063e4e20d31ae/test/unit/effects.js#L1830-L1832 .

Krinkle commented 1 month ago

Peer review of implementations:

I'm going to go with @JamesMGreene 's qunit-assert-close for the most part, with an added type check in case the delta argumet is missing, to catch otherwise-subtle mistakes.

JamesMGreene commented 1 month ago

Just wanted to swing by to say thanks for the note, Timo! ❤

I added a README notice in https://github.com/JamesMGreene/qunit-assert-close/commit/e0300bae14cccdb7ab8ca44d68928e18fb590aea and archived the repo. 🗄 🧹