How to test Android Apps: Architecture

How you develop your application influences how easy is to test it. Likely, you already know the best practices:

  • Classes should have a single purpose
  • Code should be extendable
  • Avoid having too much dependencies
  • Separate your data layer from your business layer
  • ...

All that helps here. But for easy unit testing, classes need to be decoupled. Classes must specify their needs in abstractions (interfaces). And classes should not be responsible for constructing their own dependencies. Basically, it's just about following the dependency inversion principle.

This means that your objects dependencies are passed in the constructor, so they can be easily changed, a very common process for testing.

Avoid:

class Foo {
    public Foo() {
        Bar bar = new Bar(); // or Bar.getInstance()
        bar.something();
    }
}

new Foo();

And instead use:

class Foo {
    public Foo(Bar bar) {
        bar.something();
    }
}

new Foo(new Bar());

Sounds good right? But what if you have a complex application, with lot of nested dependencies? You can quickly end up writing too much code just to initialize your objects. That's why you need a dependency injection library.

Dependency Injection

For Android, the most popular dependency injection is Dagger library. We ended up using Dagger v1, a simpler version but less performance version of the library. I won't go into the details of integrating Dagger into your application, but there are some goodexamples out there.

You'll just need to annotate the constructor for injection like:

class Foo {
    @Inject
    public Foo(Bar bar) {
        bar.something();
    }
}

And when you inject an instance of Foo in another class, it will already come with Bar automatically instanciated for you.

What about objects you don't have access to the construtor, like Android Activities? There, you need to use Dagger's field injection. But it will be harder to fake those dependencies, unless you add setters just for testing.

Testing Strategy

At first, it might seem that functional tests is all you need. Test your application just like any user would, and you're covered. But you will find shortcomings:

  • Functional tests are 10x slower
  • It's hard to mock external components like web servers, location, etc..
  • When a test fails, you still have to find where the bug is

With unit testing, tests run faster and errors are found faster. And if you keep dependencies decoupled, you can easily mock everything you don't want to test.

A common strategy is to write functional tests for the common scenarios of your application. A sort of smoke test to make sure things are working together correctly. And then, write unit tests to cover all variations of your classes.

Changing implementations

In a normal project, code is going to be refactored and implementations are going to change. Unit tests do need more maintenance to be kept updated, though they are worth it in the long run.

A few tips that help ease the load:

  1. Only test the code inside each class separately, so a single bug doesn't break all your tests
  2. Specify your classes dependencies with Interfaces, instead of concrete classes
  3. Write more and smaller tests, even for the same method, to split behaviors.

You can use Mockito for mocking dependencies and validate calls. The following posts will have code examples.

Resources


How to test Android Apps

  1. Introduction
  2. Architecture
  3. Unit Testing
  4. Instrumentation Testing
  5. Other details