Mastering the World of Android Testing (Part 2)

Aritra Roy
Aritra's Musings
Published in
10 min readApr 7, 2018

--

In the previous part of the series, we tried to make ourselves familiar with the world of testing and more specifically with the world of Android testing. We tried to understand the concepts behind unit tests, integration tests and functional tests. We also tried to understand the importance of writing tests and also made ourselves comfortable with the testing pyramid.

If you haven’t had a chance yet to read the previous part of the series, then it is highly recommended to go ahead and read that first. It will help you a lot in clearing your basics and making the most out of this article too.

Previously on this series

In this part, we will mostly discuss on the importance of having a testable architecture and some of the common mistakes you can avoid ensuring that your app’s architecture is properly testable. We will also dig deeper into unit testing and learn to master them.

Is Your Architecture Testable?

Before you start writing tests for your code, you need to ask yourself one simple question, “Is the architecture of my app testable?”. If it is, then you are good to go. If not, then it is important for you to understand why not and what you can do to make it testable.

I have seen many developers, who got motivated to write tests and started with full enthusiasm only to get frustrated and failed miserably in almost no time, building up a false impression that testing is boring and difficult.

Not true.

Testing is not boring. Testing is not difficult. There can be several reasons for you to find testing difficult, but the most important one of them is probably an untestable architecture.

With an untestable architecture, it’s like rowing a boat against the tide. If your code is not constructed and structured in a proper way, you are definitely going to find it difficult to write tests for it.

Road To A Testable Architecture

So, before kickstarting your testing journey, consider spending some time in architecting your app in the right way.

Getting deep into app architecture is certainly not within the scope of this series and can easily be a good candidate for another dedicated series (which will come soon 😃), but what I can do now is give you some idea on what to do and what (definitely) not to do for you to have a testable architecture.

These are IMHO some of the most common things I have seen in many poorly constructed codebases making testing extremely difficult, if not impossible.

No M-V-Whatever

There are several common app architectures like MVP, MVVM, MVI, etc. that can not only make testing simpler but can also significantly improve your app’s flexibility and maintainability. You will find yourself spending a lot less time and effort in understanding existing features, writing new features and debugging tricky bugs quickly and easily.

Bonus: Here is an excellent resource to read more on different Android app architectures.

No Dependency Injection

If you find your codebase littered with the “new” keyword everywhere, be sure that you will have a hard time testing that codebase. Don’t get me wrong, this doesn’t mean that I am telling you not to create any new object.

You definitely need to create new objects whenever needed, but there needs to be a single central entity responsible for creating objects and providing them to you as needed. All you need to do is just ask it for objects

Whenever you “new up” an object instead of injecting it, you are making it really difficult for yourself to write tests for that code by using test doubles (fake or mock implementation) of that object instead of using the real one.

Tip: Learn about Dependency Injection and how you can make use of tools like Dagger 2 to make your life easier.

Violating SRP

If a class is doing more than it actually should, then it is a clear sign of an untestable architecture and a stinky code smell. I have seen projects where a single class performs multiple responsibilities in one place.

SRP which stands for Single Responsibility Principle, is actually one of the principles in SOLID which simply states that your classes (or methods) should be doing one thing, really just one thing.

In the context of the Single Responsibility Principle (SRP) we define a responsibility as “a reason for change”. If you can think of more than one motive for changing a class, then that class has more than one responsibility.

If it is performing more than one responsibility, then it is time for you to identify those responsibilities first and separating them into their individual implementations making it easy for you to setup and write tests.

Fat Constructors

The sole purpose of a constructor is to initialize the instance variables of a class and it should do just that, nothing more. If a constructor is doing a lot more than it actually should, then you are unknowingly increasing the complexity of testing that particular class.

Using conditional logic, looping statements and making static method calls inside the constructors is a sure sign of an untestable mess. Keep your constructors lean and clean.

A simple example of fat constructors

In this simple code snippet, we just want to ensure that the price of the order is never negative. There are many ways of doing that, but this simple pollution in the constructor can start making testing of this Order class difficult.

Recommended Reading

Statics & Singletons Everywhere

If you are using static methods everywhere in your code, don’t be surprised if testing that code becomes terribly difficult.

Static methods can neither be mocked using standard mocking libraries (there are other pro-methods of doing that — PowerMock) nor can they be subclassed and overridden to facilitate testing. Static methods can be useful sometimes, but can easily be a spoilsport in your testing game.

The idea is similar with Singletons as singleton objects can not only be referenced from tests but from anywhere else in your system as well — violating the “true” isolation of your unit tests.

Singletons also tend to keep state inside them, which gets shared among multiple unit tests making your tests flaky and unreliable.

Not Coding Against Interfaces

Another way to make your testing life complicated is not coding against interfaces. This can make it really complicated for you to test a particular class as you have lost the flexibility of injecting a mock or a fake implementation to substitute the original behavior of its dependencies.

If you are coding against interfaces then it can become really easy for you to swap the implementations of these contracts during tests and provide dummy or mock implementations to facilitate testing of different scenarios.

Violating the Law of Demeter

The Law of Demeter, also known as the “principle of least knowledge” states that a unit should have limited knowledge of other units it coordinates with. The more your code starts depending on internal details of its neighboring objects, the more difficult it is to write tests for them.

You will need to mock every single dependency (which no one cares about for that test) till you get to the object that you actually need to test. It is like finding a needle in a haystack. Everyone loves passing around gigantic objects which knows everything about your system, but doing so can lead to this,
objectA.getObjectB().getObjectC().doSomething(); which could have simply been something like this, objectC.doSomething();

Tip: It is definitely worth reading more about the Law of Demeter here.

Grokking Unit Tests

A unit test can be best described as the scrutinization of the smallest independent testable part of the application. Its main goal is to give you the confidence that certain parts of your app work well in isolation.

Don’t look at unit tests from a QA point of view. These are extremely necessary to give you the dev confidence that whatever code you write works well now and will work in future too.

Unit tests work best with pure functions. These are the kind of functions that give you the same output given the same input and do not have any side-effects (like state mutation, etc.)

Writing “Good” Unit Tests

Good unit tests are not difficult to write, but there are some thumb rules that you should consider following.

Fast

This is an important factor to keep in mind. Unit tests should run fast, I mean really really fast. By fast, I don’t mean that you should be able to run one unit test in a second, rather you should be able to run hundreds of unit tests in under a second.

The faster your unit tests are, the more often you would want to run them. And the more you run them, the more value you can get out from them. If your unit tests are slow, you would tend to skip running them often which would eventually defeat the purpose of having them alltogether.

Small & Targeted

The main idea behind unit testing is to test only a particular unit of your code in complete isolation. You should not be testing multiple units of your code in one single test. You should also refrain from testing multiple scenarios of a particular unit of code inside one single test. Consider writing separate tests for separate scenarios.

Your unit tests should also be physically small in size. That means, your tests should not ideally be more than a few lines of code (with some exceptions of course). Anyone looking at your tests should be able to glance through them and be able to grasp the basic idea behind the functionality under concern quickly and easily.

Reliable

Avoid having flaky unit tests. A particular test should always give you the same result even if you run it for “n” number of times. If a test is meant to pass, then it should pass no matter how many times you run it, and vice-versa.

While developing your app, if you find a failing test, your numero uno priority should be to fix the test first and then carry on with whatever work you were doing. Deferring failed tests for long can only do harm and no good.

But if your test suite is flaky, then you will never know if a failing test was actually supposed to fail. Over time, you will start losing confidence and faith on the tests you have written yourself and start ignoring your failing tests defeating the whole purpose of having those tests all together.

Exhaustive

Another important criteria of a good unit test is to ensure that they are as exhaustive as possible. Your unit tests should be small in size, but large in number. It is completely normal to have thousands of unit tests for a project of decent size.

You should always aim for a comprehensive coverage of your codebase. If a particular functionality needs a certain number of tests to cover all its scenarios, then you should strive to write test cases covering all those scenarios. Try to think of edge cases that are hard to reproduce in real life and get them covered with unit tests.

Always write a test to reproduce a bug before you fix it — Robert C. Martin

Anatomy Of A Test

Now let’s talk a bit about the anatomy or structure of a unit test. A unit test can easily be structured in the form of AAA (Arrange, Act and Assert).

Arrange

This step helps you make the necessary preparations before you want to invoke the method under test. This is basically like the setup phase for an individual test. To understand it better, suppose, you want to start a car, you cannot do it without getting inside the car, putting the key in and then starting the engine.

Now let’s try to understand it better with a contrived example, suppose you want to test the user login feature of your app where the user can only login if the username and password are valid.

when(userRepository.isUsernameValid(username)).thenReturn(true); when(userRepository.isPasswordValid(password)).thenReturn(true);

Here, we are using Mockito to mock the userRepository and returning canned responses for the dependent methods.

Note: This part is optional. In some tests, you might not need to arrange anything at all.

Recommended Reading

Act

This is the main part of the show. This is the part where you actually invoke the method that you want to test.

You can invoke this method and then either analyze the result obtained or you observe any other consequences (like events being triggered, views being shown, etc.) that occurred because of invoking the method.

This is absolutely mandatory as, without the invocation of the method under test, there is absolutely no point in writing the test anyway.

Now, lets see how the act phase will look for our contrived example,

boolean result = authenticator.login(username, password);

Assert

This is the part where you want to make sure that your expectations from the test have met. In this phase, you are supposed to make meaningful assertions on the results obtained from the method under test.

This is how our simple assertion would look like,

assertTrue(result);

Let’s have a look at the entire test to have a clear idea of what’s going on.

It is mandatory to follow this template in order. But for some people, it is more natural to start by writing the act part first and then the assertions and then making any arrangements if necessary.

It’s time to put a stop here. I hope you had a good read. I will come back again with another part of this series very soon. If you found this article useful, consider sharing it with your friends, colleagues etc. via social media.

--

--

Design-focused Engineer | Android Developer | Open-Source Enthusiast | Part-time Blogger | Catch him at https://about.me/aritra.roy