Tests are essential in any project. They are the easiest way of knowing that things are working as expected. If you find yourself navigating a large codebase, they become even more helpful both as warnings for when things break but also for saving time you might not have for diving into deep portions.

Testing is not really a thing. It is more of a mindset. The programmer must not just build a structure but also make sure that it works. Being lazy by nature, automating this process seems the most attractive (automating the building itself would be nice (ahem, lisp macros, ahem) but alas this problem would be infinitely recursive!). People in the javascript world use code coverage reporters, of which the most popular is Istanbul. Those who have used it know how brutal it is.

As this guy said, if you think JSLint hurts your feelings, wait until you use Istanbul.

I totally get the appeal of using a coverage reporter. It tells you what holes you missed. What's bad, though, is that it attempts to replace a mindset that a programmer must develop over time.

Understanding how a user might break your code is fundamental to building solid programs. This kind of analysis is like a muscle, it must be used or it will atrophy. Practice of this kind is writing tests that anticipate how things can go wrong. The second a programmer uses a coverage tool to know what to look out for, not only does he never develop the skills to properly analyze his code for faults, the method of checking for flaws becomes standardized.

There's something else as well. The annoyance of having tests break when their functionality doesn't change.

test = (val) ->  
    I = 0
    if val
        i++
    return true;

This function might seem silly, but it represents a real case where a function's signature can remain the same regardless of what happens within its scope.

The return value of this function is always the same: true. But there is a branch in the body that increments a counter which is never used. The test has no way of knowing whether or not changes to a function that don't impact the return value should be tested--only the programmer knows that. A proper test would read:

It 'should return true', ->  
    expect(test()).toBe true

Which would pass, but the coverage reporter would bark at the untested if(val) branch.

There's no precedent for choosing not to use coverage tools, especially for javascript code bases. Programmers tend to follow overly formal notions of code completeness, even if its pointless and gets in the way of shipping code.

I tend to find myself in the same camp of agile practitioners. Functional code is the most reliable marker of success. All other problems can be solved via rapid, frequent iteration. That means anything that gets in the way of shipping should have its place examined thoroughly.

This isn't to say that coverage tools like istanbul are never useful. Their use should be judged on a case-by-case basis, just like any other tool a programmer uses. There is no such thing as a panacea for code reliability. The test is as much part of the process as development. And everything is subordinate to shipping.

So that's my take. Write code. Test it. But always ship.