Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

All About AngularJS Unit Testing

DZone's Guide to

All About AngularJS Unit Testing

· Agile Zone
Free Resource

See how three solutions work together to help your teams have the tools they need to deliver quality software quickly. Brought to you in partnership with CA Technologies

Software is highly complex. Even the simplest of programs can have hundreds of lines of code with extensive and complex inter-dependencies. Additionally, the code evolves rapidly as new features are added – and old features have bugs fixed. One of the ways to mitigate these issues is to build a robust unit and functional testing suite, but these tests are successful proportionately to the amount of code and functionality they cover.  Below we’ll look at some of the common metrics used to gauge unit test success and see the best practices for each in AngularJS.

Code Coverage

Code coverage, as a unit testing metric, simply checks how many lines of code in your project have been encountered when running your unit test suite. Tools such as Karma offer code coverage statistics as a built-in feature of the test suite, happily generating a coverage report upon completion of the test run. The general wisdom is that the higher the percentage of code you have covered by your unit tests, the more robust your testing suite is.

However, there’s a common issue to note with code coverage tests. Namely, there is a trivial example to show that 100% code coverage does not catch all of the bugs in the system. Take, for example, the following code:

var custom_divisor = function(op1, op2, modulus) {
    sum = 0;
    if(op1 > 0)
    {
        op1 = op1%modulus;
    }
    op1 = op1/op2;
    return op1;
};

The above code can achieve 100% unit test coverage with a very simple set of inputs. Feeding 1 for each argument will hit every statement in the above function, but still entirely miss the very obvious bug. One of the strategies to mitigate this is known as Branch coverage.

Branch Coverage

Branch coverage expands upon code coverage by tracking the number of branches present in your code. Take, for example, the following if-else statement:

if(i < 0) {
j += 1;
}
else if (j < 0){
i+=1
}

This code has three branches that need to be checked:

1 – The first if statement is true
2 – The first if statement is false, and the else-if is true
3 – The first if statement is false, and the else-if is false.

This tests every branch in the code which, when built upon a metric of high percentage code coverage, can add additional robustness guarantees to your test suite. Tools like Karma offer built-in measurement of branch coverage through the use of Istanbul.

However, once again, branch coverage is not sufficient to hit every bug in your program. Take, for example, the same sample function from above:

var custom_divisor = function(op1, op2, modulus) {
    sum = 0;
    if(op1 > 0)
    {
        op1 = op1%modulus;
    }
    op1 = op1/op2;
    return op1;
};

We can trivially attain full branch and code coverage with two tests:

1 – Test result when op1, op2, and modulus are all equal to 1
2 – Test result when op2 and modulus are equal to 1, but op1 is equal to -1

However, the above once again fails to expose the critical error in division. To finally catch this error, we need to look at input domain coverage.

Input Domain Coverage

Input domain coverage hits the last knob available for testing function interfaces. In short, it covers testing the entire input domain of a function. Let’s say we have an argument to a function op1, and that op1 is of type integer. Full input domain coverage covers all the possible values from INT_MIN to INT_MAX. As every argument to a function will be tested with every potential value, pretty much every possible single-thread-of-execution bug in your code will be found.

However, the problem with this approach should be painfully obvious – there are in excess of four billion possible values! Adding four billion tests to any test suite is obviously counter-productive, and that’s only for a single variable. Full input coverage requires testing all of the possible argument permutations – meaning for three operands of type int you would need the cube of four billion tests!

Finding a Happy Medium

So if we can’t depend upon 100% code coverage OR 100% branch coverage, and if input domain testing is unfeasible, how can we ensure our code is fully-tested? The sad reality is that ultimately you can’t guarantee that your code will be completely free of bugs through unit testing alone. Setting aside the sheer number of tests needed to achieve full code and branch coverage, in most cases the input domain is simply far too wide to allow for absolute certainty in your test suite.

The solution here is to relax on dogmatism when it comes to unit testing. 100% test coverage is an admirable goal, but if it is going to take another week to get from 95% test coverage to 100%, and as we demonstrated above 100% coverage isn’t even guaranteed to catch all the bugs, is it worth spending that time on testing rather than new feature development? Instead, a little bit of planning and thought should be put into each test. Indeed try to test all the code in a function, and try to hit all the branches, but use common sense to determine when – or even if – your testing suite is getting too large. Restrict your testing to common values and a couple outliers that could cause problems for your code, and you should be able to catch the vast majority of bugs in your software – even without full test coverage. Couple this with diligent manual QA of your application, and you code will be as secure as you can make it.

Conclusion

Unit testing is a vital practice for the development of stable software. However, gauging the success of your test suite is a problematic effort. As seen above, the most obvious metrics – code and branch coverage – fail to offer complete assurance that your code is entirely without bugs. In part two of our unit-testing series, we’ll cover some best practices that will mitigate a lot of the concerns of testing, and help you to design a more robust unit-testing suite.

Discover how TDM Is Essential To Achieving Quality At Speed For Agile, DevOps, And Continuous Delivery. Brought to you in partnership with CA Technologies

Topics:
angularjs ,unit testing ,agile ,web dev ,html5 ,javascript

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}