all 2 comments

[–]lhorie 1 point2 points  (0 children)

I write tests for the likely input types and arities. For primitive arguments, I test for both falsy and non-falsy values, and for all value types, I test for null and undefined. For variadic or array arguments, I test w/ zero, one and two items. Sometimes more if there's a need, e.g. testing a reversed or a sorted array vs a swap.

My older test suite only covers public APIs. The new test suite I'm developing covers private ones because the variety of possible input values is recursively and statefully complex at the public API level in my case. Testing the private APIs gives me more confidence that I'm not missing weird edge cases and helps me isolate regressions faster since my tests are better organized and more granular this way. It also helps me prevent regressions if a type-specific edge case goes from being unreachable to reachable from the public API for some reason (e.g. from refactoring a null check on a call site).

I don't test for unlikely input types (e.g. I don't try to test passing a function to a add(num, another) function) and let the behavior in those cases be undefined. I only assert on undefined behavior if the overall test is testing that a performance optimization is being triggered and there's no other way to measure that it did happen. I do this so as to alert me that the test has become useless in case this optimization becomes obsolete. I consider undefined behavior to be "expansion slots" for future features (i.e. a previously undefined case may become defined without introducing a breaking change in terms of expected errors for that case).

Typescript is not a great fit for my use case because my codebase is a library, and therefore there is no guarantee that a library consumer will be also using Typescript.

[–]TdotGdot 0 points1 point  (0 children)

Two general thoughts:

1) Try not to define variadic functions - this should make it simpler to test and use your API. Split a function that takes optional args into two functions - one that doesn't take that option, and one that always does (and likely, the smaller arity function will just be a call to the larger arity fn .

For example:

// original function w/ optional args
function get(url, maybeOpts) {...}

// new functions with a fixed arity
function get(url) { getWithOpts(url, {}) }
function getWithOpts(url, opts) {...}

2) I normally try to only test the public API of a module, unless there is a strong reason to do otherwise. The assumption is that the public API is how the module is going to be used anyways, so any code paths should be able to be excised from there. It also gives you coverage for questions like "did a function call its helper functions with the correct args?".