var inlineTest = function(fn, tests) {
(tests || []).forEach(function(test){
if (fn.apply(this, test[0]) !== test[1]) {
throw new Error('Failed inline unit test with args: ' + test[0]);
}
});
return fn;
};
var square = inlineTest(function(n){
return n * n;
}, [
[[2],4], // [[arrayOfArguments], expectedResult]
[[3],5] // <-- This test will throw an error
]);
EDIT: Change fn.call to fn.apply Function.prototype.where = function () {
var fn = this, tests = [].slice.apply(arguments);
(tests || []).forEach(function(test){
if (fn.call(this, test[0]) !== test[1]) {
throw new Error('Failed inline unit test with args: ' + test[0]);
}
});
return fn;
};
var square = function(n){
return n * n;
}.where(
[[2], 4], // [[arrayOfArguments], expectedResult]
[[3], 5] // <-- This test will throw an error
);As I mention in the post though, mine is just a very simple macro. But it is enough to show how easy is to modify the language to fit different scenarios, such as testing. The macro could probably be expanded and improved a lot to fit much more complex scenarios like real Contract-based languages have.
I can't imagine writing such code without proper support from IDE like automatic folding and/or special colors.
There is also problem of asynchronous code, events etc. Something that can't be covered with such simple contracts.
- Reporting failures can more easily report context about the function being tested
- Since "where" blocks are actual blocks, we define helper functions and variables inside them that don't need to clutter up the namespace of the current scope
- It's easy to turn the assertions on and off; for example when importing a Pyret module we skip running the checks of that module by default.
- The localized information also plays into our story for type inference (which is work in progress), but starts from the unit tests in the where: block to figure out what the programmer intended for input/output types.
For example, see [1]. This is a very simple example of testing a JavaScript drawing app. (It's from my Let's Code JavaScript screencast [2].) In this example, you'll see several things that don't seem like they'd work well with inline tests:
1. Common setup and teardown. The tests work against the browser DOM, so there's `beforeEach()` and `afterEach()` functions that set up and clean up the DOM. The `afterEach()` function is particularly valuable because it runs even when exceptions occur. How would common setup and teardown be handled with inline tests?
2. Multiple tests per function. There's a fairly loose relationship between the tests and the functions in the production code. Instead, the tests speak to the behavior of the drawing app. If these tests were inline, how would you prevent the test code from overwhelming the production code?
3. Helper functions. The tests have a lot of helper functions to do things like simulate events and parse the DOM. This particular example is worse than most, but helper functions are still fairly common when testing. How do you distinguish test-specific helper functions from production functions when you're using inline tests?
Overall, I wonder if inline tests would make this code harder to read, not easier.
I'm intrigued by the idea, so this isn't meant to be a middlebrow dismissal, but more an invitation to explore real-world problems. Feel free to use my example code at a starting point! I'd love to see a clever transformation of this code to inline testing.
[1] The example code: https://github.com/jamesshore/ll11_front_end_unit_testing/bl...
[2] Let's Code: Test-Driven JavaScript, my screencast: http://www.letscodejavascript.com