Reusing JavaScript Tests
Increasing test validity while decreasing costs
Writing tests is hard. 😤
Why do we need them in the first place? 🤔
Code defines a set of instructions to perform a specific operation. Consider this function as performing the well-defined mathematical operation:y = a + b.
Assuming the developer understands the underlying principles, he can write this correct implementation in machine code:
//seems about rightfunction Add(a,b) {
return a + b;
}
However, noise from human error, system constraints, or other bugs may damage the code:
function Add(a,b){
return a + ‘ ’ + b; //woops!
}
This is where tests come in. ✨
Tests describe the operation in behavior-outcome language,
mirroring the code’s machine language.
Damaged code would be reflected as test failures:
import Add;test(‘add should return the sum of two numbers’, () => {
expect(add(2,3)).toBe(5); //error! it is actually ‘2 3’!
});
Increase validity while decreasing costs
Mathematically, “regular” code is a continuous function, while tests are discrete samples of the same operation. Writing and maintaining a sample size big enough to ensure correctness could be just as difficult as writing the ‘regular’ code itself. 😞
So why not reuse existing tests, as we do with our “regular” code? 😃
Reusing tests
I know, reusing tests is an awful experience.
Developing in existing endless interwoven code is hard enough, no matter how well-written it is.
If you ever dared to interweave tests, I bet you got a headache pretty quick.
But, I can imagine at least one use case where this would be applicable:
interface IFileSystem {
writeFile(path, content);
unlink(path)
…
}//common file-system implementation:
class NodeFs implements IFileSystem {
…
}
Most tests written for NodeFS, could, with little adaptation, be written for IFileSystem! 😯
Another file system could have another implementation, and still, use the same tests!
// memory-filesystem
//———————————————// in memory file system for fast short and isolated uses.
class NodeFs implements IFileSystem {
…
}//memory-filesystem.test
//———————————————
import FileSystemTests
import MemFsFileSystemTests.validate(MemFs);
Can we do it today?
Yes! 🥳
The cost of code isolation is falling, and even tests worth the effort.
One of the most exciting tools in the domain of code isolation is Bit (Github). Bit is a tool and platform that helps you isolate code. Bit also offers a quick and easy way to share your isolated components to a collection in bit.dev, so that others (and your future-self) may use and even collaborate on. Bit is usually used for UI components, as a way to gradually build a UI component library but, it can easily be used for Node/JS utility functions and tests.
- Write your test:
import expect from ’chai’; //you gotta get it somewhere
import { IBinaryOperation } from ’math-functions’;
import { toPairs } from ’combinatorics’;function validateAssociativity<T>(
operation: IBinaryOperation<T>,
values: T[]) { toPairs(values).forEach((x, y) => {
expect(binaryOperation(x, y))
.toEqual(binaryOperation(y, x));
}
}export validateAssociativity;
2. Isolate the test into an atomic component using Bit:
§ bit add AssociativeTests.Ts —-id math/tests/associativity
§ bit add -c bit.envs/compilers/typescript
§ bit tag math/tests/associativity
3. Push your component to your collection in bit.dev:
(learn how to create your Bit collection here)
§ bit export collection.name math/tests/associativity
3. Now we can easily reuse our shared reusable test component:
import add from ’./add.js’;
import { validateAssociativity } from ’@bit/collection.name.math.tests.associativity’;test(‘add associativity’, () => {const sample = [1, 5, 3, 1000, 0.3, -17];
// or maybe.. math/samples/realNumbers ..?validateAssociativity(add, sample);}
Pretty neat! 🤯
Will pngSnapshot components be the next hot thing? 🔮
Could we have checklists like Accessibility support, FPS quota, and screen responsiveness?
What do you think? 😍