Hacker News new | past | comments | ask | show | jobs | submit login
Extending JavaScript with inline unit tests (sergimansilla.com)
32 points by sergimansilla on Nov 10, 2013 | hide | past | favorite | 13 comments



It's an interesting idea. My first reaction is to wonder how it would work with the more complicated tests you typically encounter in real-world JavaScript.

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


This technique is not called "Inline unit tests", it's called contract programming.

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.


Given the simplicity of my macro, I didn't call this 'contract programming' on purpose. For real JavaScript Contract Programming, check out contracts.js (from the same author of sweet.js).


Among others(there are many contract implementations for js on github) this one is pretty average, not modern enough, not compatible with code which uses promises. Such implementation should go beyond direct arguments and result value of function call.


Skipping the `where` and just putting the asserts after the function definition would be as readable, maintainable and skip needing sweet.js processing cost?


A few things that Pyret gets out of "where" blocks that it's not obvious are easy with just assertions after the function.

- 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.


Sure. And so it would in Pyret. It is just some sugar to make it more clear that this code belongs to a particular function. It can also be much more improved and coupled to the 'owner' function, but that's just a simple example of how to make a macro that emulates in part Pyret's inline tests.


Here's a simple implementation without sweet.js:

  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


Nice. If you wanted to go even more inline:

    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
      );


Cool, nice and clean :)

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.


you just gave me an idea for a "typechecking" api.thanks.


I wrote something like this once, never did anything with it but you might find it useful: http://jordanwallwork.co.uk/2013/01/faking-typed-function-ov...


Patching dynamic weak typed system, sigh...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: