My favorites | Sign in
Google
                
Search
for
Updated Aug 20, 2009 by aeagle22206
Labels: Featured
HowItWorks  
How it Works

Introduction

For those who are interested how the numbers are computed you can find the inner workings here.

What is Testable?

Injectability is Good

We consider a class testable if it would be easy for us to exercise all paths of execution of the class without exercising the rest of the system. (The classic definition of 'unit test' only test one class at a time) In order to test only the class, we need to be able to intercept any calls going out of the class, (or at least be sure that those few calls which we can not intercept are inexpensive).

Holding all other things equal, we believe that for two classes the one which allows interception of outbound calls will be easier to test, because it can be isolated in a test.

We intercept calls by:

In order to be able to override method, or pass in a mock, we must control object construction, so that we can construct a mock or a subclass of the class under test. Lets look at an example:

public class SumOfPrimes1 {
  private final Primeness primeness = new Primeness();
  public int sum(int max) {
    int sum = 0;
    for (int i = 0; i < max; i++) {
      if (primeness.isPrime(i)) {
        sum += i;
      }
    }
    return sum;
  }
}

In the code above, there is no way to test SumOfPrimes1 class without exercising Primeness class as well. This is because we can not intercept the call to primeness.isPrime(). This is because in order to override the method we would need to pass in a subclass of Primeness, but the test does not control the construction of the Primeness and hence can not intercept it.

In this case the cost of primeness.isPrime() is low and is not an issue. But imagine if the the call talked to an external system and charged a credit card. In the real world, interception becomes top priority.

In the similar code below the call to primeness.isPrime() can be intercepted in the test. Primeness is set via the constructor, so the test can easily pass in a subclass of Primeness with its isPrime() method stubbed out. We therefore believe that this class is easier to test.

public class SumOfPrimes2 {
  private final Primeness primeness;
  public SumOfPrimes2(Primeness primeness) {
    this.primeness = primeness;
  }
  public int sum(int max) {
    int sum = 0;
    for (int i = 0; i < max; i++) {
      if (primeness.isPrime(i)) {
        sum += i;
      }
    }
    return sum;
  }
}

We say that the field primeness is injectable. This implies that any method dispatch (except final/private/static) on the primeness field can be intercepted.

Injectability is Transitive

The tool heuristically looks at a class and identifies all variables/fields/parameters which are injectable (i.e. can be controlled from the outside).

The heuristics to calculate injectability are:

Global State is Undesirable

Many software developers are of the opinion that a global state is undesirable. A few of the reasons are:

NOTE: It is not that all globals are bad, only those which are mutable. The immutable ones are constants for all practical purposes.

Global variables are transitive

  public static class Gadget {
    public static final Gadget instance = new Gadget("Global", 1);
    public final String id;
    public int count;

    private Gadget(String id, int count) {
      this.id = id;
      this.count = count;
    }
  }

In the example above all global (static) fields are declared as final hence one would think that there is no mutable global state. But this is not true. The field countis mutable and is globally reachable Gadget.instance.count. therefore if there is code which Gadget.instance.count it will incur global cost. The fields Gadget.instance is static and hence is marked global. However, because it is final it is immutable. However, traversing Gadget.instance makes the Gadget instance global. Accessing any fields on Gadget will be considered global access and accessing any non final fields (either read or write) will incur a global cost.

Summary

We believe that high injectability and low global state leads to testable code. High injectability is good because it gives the test plenty of choices where to intercept the code under tests and make the test as small as possible. Similarly low global state will aid in isolating the tests from each other. Both of which are highly desirable qualities.


Comment by sarthak.83, Jun 09, 2009

Awesome stuff... I would like to give this a shot... !!

Comment by marlena.compton, Jul 30, 2009

"Injectability" looks similar to what I learned in design patterns about "programming to an interface" and that objects should be "open for extension but closed for modification." Is this where test patterns meets design patterns?

Also, the metric you describe of calculating complexity for method cost comes very close to the CK metric weighted methods per class. Backing off by 1 is very clever.

This is a very enjoyable read :o)


Sign in to add a comment