Question

I have two functions, foo and bar.

function foo() {
    if (arguments.length !== 1) {
        throw new TypeError('only one argument expected');
    }

    do work;
}

function bar() {
    if (arguments.length !== 3) {
        throw new TypeError('exactly three arguments expected');
    }

    do work;
}

These functions were created according to TDD — e.g. tests were written first, then functions were implemented to make tests pass. List of tests:

  1. foo() is defined
  2. foo() does its job right
  3. foo() throws TypeError when given wrong number of arguments
  4. bar() is defined
  5. bar() does its job right
  6. bar() throws TypeError when given wrong number of arguments

As you see, these functions both have duplicating functionality — they are testing for correctness of arguments each on it's own. As this check is going to be very common among my functions, and may go more complex (check for argument count to be in some range, check argument types etc), I've decided to move it out into separate decorator function check.

So I've written tests for check and implemented check itself.

Now refactoring step — I've took an advantage of check, so functions now look as follows:

foo = check(1, function() {
    do work;
});

bar = check(3, function() {
    do work;
});

What should I do now to tests that check for foo() and bar() take correct number of arguments? Can I drop them?

When I implement function buzz(), that makes use of check() immediately (as it's now used as way to define functions), do I have to write test for it to take correct number of arguments? I'm hesitating, because this test will be always green, as all job is done within decorator, so is there any need for test?

Was it helpful?

Solution

I would avoid to keep multiple unit tests that check the same code, because in the future:

  1. If you'll intentionally change the check function in a way that break your tests, you'll have to change 2 tests instead of one.
  2. If you unintentionally break the check, you'll get two failures: one referring to foo and one referring to bar, and it could not be obvious that what is broken is the check function.

So, I would keep only one set of tests, explicitly named as tests for the check function, and even better, don't use function of your program to test them (not foo neither bar in your example) but use a fake function used only for tests purposes.

In this way, you can in future alter foo or bar to use, e.g., a check2 function without break tests of check function

This is, according to me, the best theoretical approach to go, but be pragmatic: don't try to follow this way if you don't expect to use the check function in a great portion of your code: your target has to have the main program running smoothly, not a beautiful set of tests...

OTHER TIPS

Classical TDD concepts needs some additional modifications to apply them to dynamic language like Javascript (due to lack of classes, specific use of closures ...) That's way we have such testing libraries like Jasminejs. I can't tell you explicitly what to do with your functions, but I know that everything needs not to be tested. Some things really are so obvious that they just need some way of sanity check. Rule of thumb is to test things that can break. For example validations, database access, user input...

I think you are on the right path, TDD make more evident the code duplication (the checking parameters code its duplication that must be removed with or without TDD) and you listen to your test and separate the responsibility of checking, now you have two responsibilities:

  • checking parameters, with his own set of test.
  • The work of the functions, that have his own set test that asume always correct inputs.

At a unit testing level this is perfect i think, perhaps in other point you need some integration or systems test that checks that your composing correctly the functions with his checkers.

If there is a separate statement, you need a test. So in your case, you need to keep the tests that require that both foo() and bar() use the check function. These tests may be simpler than the original tests that you wrote: For example they may only check that an error is thrown, without looking into the error message.

Should the check function now be broken to for example do nothing, there would be multiple tests that detect this. That is not an issue because you would put the test for the check function at the top, and so the first failure will directly point to the root cause.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top