Our journey to TDD
Steven and I came to Powershop in late 2013 to work on the company’s Android application. The original application had been built by a third-party and had no test coverage at that stage. Neither Steven nor I had experience writing tests other than what we had learnt during our formal education. Jason, our product owner, would do manual testing for both the iOS and the Android applications. We had a considerable number of incidents back then, some of which forced us to make patch releases from time to time.
Soon the amount of effort involved in manual testing increased. To release Jason from (part of) that burden, Jess joined our team as a dedicated tester for both apps. After gradually polishing our development and testing process, the number of bugs that made it to production significantly decreased. We still had a similar amount of bugs in our code, however most of them were now being detected before the release.
As the applications grew, QA and regression testing became slower and often frustrating. We therefore started looking into ways to automate part of our testing. We couldn’t find a way to fit Unit Tests into the existing codebase, so we decided to start with automated UI testing.
Automated UI testing
We chose Espresso as our automated UI testing framework for Android. We wanted to automate most of what our tester had been doing entirely manually so far. Espresso tests would simply interact with the screens and buttons in the app and verify the app behaved as expected.
In the beginning, our Espresso tests were unreliable. They didn’t always work and their success was heavily dependent on the back-end. We lived with the inconsistencies until we decided to use Jenkins, a continuous integration platform, to run our tests automatically when we made a new release. We were receiving plenty of false negatives and had to hack our tests to overcome them.
After a long period of suffering, Steven finally came up with a solution: mocking the back-end. To make it possible we use the Dagger library to apply dependency injection, a technique that enabled us to mock the responses of the API in the app while running our tests. We could now predict what responses we would get from the API in each scenario.
We finally had a more stable suite of automated UI tests as a part our CI (continuous integration) process. However we were still getting crash reports from our production application and a considerable number of issues were still being raised during QA and regression testing. It was time to dig deeper into the architecture of our application.
The MVP Architecture and Unit Tests
After looking around for software architecture patterns, sharing our experiences with colleagues from other companies and some trial and error, we chose to adopt the Model-View-Presenter (MVP) architecture pattern to structure our app. We liked the fact that we could implement it gradually, without having to convert the entire app right away. It gives us better separation of concerns and enables us to ditch much of our monolithic UI code.
It wasn’t long until we had our eureka moment: we could now write Unit Tests for our Presenters. They dealt exclusively with the logic of our presentation layer and we could build them so they would not have any direct dependencies on the Android SDK (which makes writing Unit Tests difficult or sometimes impossible). But what were we going to test? How were we going to do it?
Test Driven Development FTW
This was when all the pieces of the puzzle began to fall into place. After watching a few videos on the subject of Test Driven Development, Steven and I decided to pair-code until we got our head around it and agreed on common practices we would adopt.
How we do it
Depending on the given scenario, a Presenter makes decisions on the input it receives and tells the View to react accordingly. It is the logic behind a View. So we start by writing Unit Tests for the Presenters to verify the outcomes of these interactions. In doing so, we are forced to think about the Story we are working on. As a result, we can spot ambiguities and omissions in the Acceptance Criteria of the story beforehand and contribute to writing a Test Plan upfront.
We write our Unit Tests first and make sure they fail. Then, we write the minimum amount of code to make those tests pass one by one. If we do it right, the tests themselves will force us to gradually increase the complexity of our code to make all tests pass simultaneously. If a test gets too complicated, we often realise it is a good time to modularise our Presenter’s code into other objects (like Helpers, Services or Interactors). As a byproduct, we end up defining an interface for our View, which will later be implemented by a concrete Android Activity or Fragment. As we have not implemented our view yet, we can apply TDD to write our Espresso UI Tests as well.
We are finally at a stage in which we see the structure of our app as a set of layers with very specific concerns. We have a pretty well organised Presentation Layer and are looking into structuring our Domain Layer in a consistent, testable and maintainable manner. TDD has changed the way we think, made our code more readable and maintainable, while enabling us to catch and fix most bugs before the QA testing phase.