As software developers, we know that tests are important. Many famous software bugs, such as the Starbucks system failure, the Windows calculator bug, and the Heathrow Terminal 5 opening day failure, could have been avoided with a (more) rigorous suite of automated software tests. If bugs go undetected in finance software, aviation systems, or medical equipment, they can cost lives and millions of dollars. Even when the stakes are not that high, no client is happy with bug-riddled software. Android developers should try to follow Google's recommendations for testing, using iterative test-driven development (TDD).
In practice, testing is often neglected in Android projects. Usually, the reason for this is a lack of time. Many projects have tight deadlines, and many project estimates fail to factor in the additional time it takes to write automated tests. In the past, Android testing was not very well-supported by Google, so writing Android tests could be messy and time-consuming. Although Android testing support has been improving in recent years, the testing culture of many development teams has not kept up with those changes.
In the absence of automated tests, we must rely on manual testing done by the development team, a QA team, or the client. There are inherent problems with each of these:
- Development team: Developers tend to have blind spots since they write the code. If they had been aware of any bugs, they would have fixed them already.
- QA team: This is expensive and time-consuming. Many clients do not have the budget.
- Client team: When clients find bugs, it makes the developers look bad. Also, sometimes clients wait until near the project end to test. When they inevitably find bugs, it makes it difficult to meet the project deadline.
These problems can be reduced if Android teams adopt a culture of test-driven development (TDD). TDD would also provide additional benefits, such as:
- Simpler code reviews: The tests should find most of the bugs, so developers can focus on test coverage, code style, and design when doing code reviews.
- Easier onboarding of new developers: Developers unfamiliar with some part of the codebase can make changes without fear of breaking it.
Unfortunately, Android development teams cannot easily incorporate a TDD culture on their own. TDD takes more time than writing code without tests, and that difference must be reflected in software development estimates. Therefore, at minimum, a TDD culture requires the participation of project managers, sales teams, and clients, along with Android project leads and developers.
Behavior-Driven Development
In web and front-end development, Behavior-Driven Development (BDD) libraries such as Cucumber seem especially well-suited for organizing projects so that TDD is supported by all team members and stakeholders, because project managers can easily adopt BDD, and BDD can be integrated into the process of estimation.
The central idea in BDD is that all tests – unit, integration, instrumented end-to-end tests, etc. – should be defined in plain English before any test code is written. The test definition is written using the English words Given (a condition), When (an action is taken), and Then (an expected result). Here is an example:
Since this definition is written in English from a user's point of view, it can be written by non-developers. This is a huge benefit. It means that tests can be defined as the first part of the estimation process when app requirements are being determined. Designers, project managers, and the client can collaborate to define the entire suite of tests as requirements. The project estimator then calculates the amount of time required to write each automated test, then make it pass. This estimation process should improve how well estimates account for the time required by TDD.
The test definition shared above is an example of a user-facing end-to-end test. End-to-end tests provide coverage for a larger portion of the app's code, making smaller unit tests less necessary. During estimation, when we are describing exactly what the app needs to do, we usually define end-to-end tests. Later, when development is in progress, project leads can use ideas from BDD to help delegate and define the scope of tasks, by writing other kinds of tests, such as unit tests:
The project lead can put this test definition into a JIRA ticket or a GitHub issue, then assign the task of writing the automated test, and making it pass, to another team member. The written test definition helps clarify the problem and specify the task scope. This helps avoid any misunderstandings during task delegation.
Integrating BDD into the code
Once a test is defined and assigned as a task to a developer, it is time to make it into an automated test. For this, the Android project must have a good architecture with separation of concerns. In our recent Android projects at Nagarro Digital Ventures, we use the MVVM architecture, as recommended by Google. Dependency Injection should be used wherever possible.
Assuming the above requirements are met, we can set up the Android project for BDD in two ways:
1. Add a BDD library, such as Cucumber, as a dependency.
2. If you do not want to add a dependency that is not in common usage, it is possible to set up a Kotlin Android project without adding Cucumber or any other BDD library. Check the reference at the end of this article for simple instructions.
Once the project is set up, the English test definitions can be directly integrated into the code. We can convert the GIVEN/THEN/WHEN (GWT) statements into functions, using back-ticks to ensure the function names match the English of the GWT statements:
Now, we just need to write an automated test that looks like the original test definition, using these GWT functions:
Since the back-ticked function names match the English text of the definitions, any test reports we generate for clients and project managers will be easy to read.
The GWT functions are reusable. This is important because it is common for test definitions to share statements between tests. With the above example, for instance, it is likely that we will require a second, similar, unit test, as follows:
Since the same GIVEN statement is used in the two test definitions, we can reuse the same function in both automated tests. Only a new function for WHEN and THEN will be needed.
Potential downsides
BDD helps ensure that project managers and clients are on board with TDD and helps organize the entire team around the idea of software testing. From a developer's perspective, BDD reduces some of the messiness and inelegance of Android tests; the test code is easier to read than regular Android test code that does not conform to any standards.
There are only a few potential drawbacks. One legitimate criticism is that BDD is a more process and planning-oriented way of working than many team members might be used to. It may not be easy for everyone to adjust to it. Also, from an organizational perspective, somebody will have to keep track of all the tests. In large projects, there can be a lot of tests to track, and the test definitions can be verbose.
Each team will need to evaluate whether BDD is right for them, but adopting BDD is an excellent way for Android development teams to institute a culture of Test-Driven Development!
References:
S. Andreev, Android BDD with Robolectric and Espresso, May 6, 2021.