Skip to content

An Introduction to Test-Driven-Development (TDD) for Android

I’ve been looking at the jobs going around in the Android community and many of them require the same high-value skills that are a powerful add-on to any developer’s toolkit.

One of them is Test-Driven-Development or TDD: a form of extreme programming that relies on a very short development cycle (definition based on Technology Conversations).

“First the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.”

For Android, most of these tests come in the form of Unit and Instrumented tests built in the app’s (test) and (androidtest) directories. Typically, you’d start running your tests and simply wait for them to identify any errors in your code.

So why is TDD such a high-value skill in the eyes of many employers?

Benefits of TDD

1. Makes debugging quicker (and thus cuts development costs)

This is the obvious one. Running your tests only requires a couple clicks to get going and can almost immediately identify any bugs made by newer changes in your code. It also shows you the situation in which your code failed and thus, you can locate the bug quickly and deal with it.

2. Avoid users running into bugs

For many of the same reasons above, by identifying bugs in your app before any of your users do, you spare yourself from glorious discontent from your users and you almost immediately become a more reputable programmer.

3. App becomes more scalable and easier to maintain (Acceptance)

Not only will identifying bugs make it easier to make improvements to your code without breaking other sections of your app, but writing tests means you have to keep thinking about what you want in your code. Almost like putting yourself in a user’s perspective, thus, it keeps you in the right direction moving your app forward.

These are just a couple among many advantages of TDD. Other people even say focus, fast feedback, developer confidence, and they’d all be right.

Unit Tests vs Instrumented Tests

Let’s go back to the 2 Android directories where we write our tests.

Unit Tests

Unit tests are written in the (test) directory. These tests run directly on the Java Virtual Machine (JVM) and test your code directly without the Android framework (so no Context and such). Unit tests make use of the Junit and Mockito libraries.

@Test
fun convertCelsiusToKelvin() {
    val input = 20
    val expected = 293

    val mainActivity = MainActivity()
    val output = mainActivity.convertCelsiusToKelvin(input)
    assertEquals(expected, output)
}

Instrumented Tests

Instrumented tests are written in the (androidtest) directory. These tests require a device or emulator to run but are able to test your code with the Android framework so you can acccess objects like Context and Views. Instrumented tests make use of the Espresso library.

@Test
fun messageShows() {
    onView(withId(R.id.firstName)).perform(typeText("John"))
    onView(withId(R.id.secondName)).perform(typeText("Smith"))
    onView(withId(R.id.button)).perform(click())
    onView(withId(R.id.message)).check(matches(withText("Welcome, John Smith!")))
}

When to consider writing a test

As your app grows, the chances of it failing become bigger. Many production apps have hundreds or even up to a couple thousand tests. There aren’t any hard-spoken rules for when to write a test, but I think it’s good to write a tests whenever you design a new feature.

This becomes especially true if you can identify any areas of code that have any chance of failing as a result of the code itself or its interaction with other parts of the app.

How to run tests

Before we’ve written any tests, Android Studio generates one unit test and one intrumented test just for the sake of it. We’ll use these just to get on to running tests.

It’s super simple. Just right-click on the corresponding test directory and click “Run Tests”.

This will automatically start running all the tests in the chosen test directory in a random order. Once finished, Studio will present you with the results of each test. For presentation sake, I purposely failed one of the tests.

The best thing about this is you could run these tests while making your morning cup of coffee. Amazing!

How to write Unit Tests

We’ll start with simple unit tests. First, make sure you have the Junit dependency installed in your app/build.gradle file.

testImplementation 'junit:junit:4.12'

Unit tests are centered around assert functions that compare expected values to actual outputs produced by your app’s code. Just remember you can’t test functions that make use of the Android framework (not without the use of other libraries, but more on that later).

Let’s take for example, this simple function to convert Celcius to Kelvin.

fun convertCelsiusToKelvin(celsius: Int): Int {
    return celsius + 273
}

Super simple function, and I can definitely tell before running the code what the output should be.

Therefore, in the test, I’m going to hardcode the expected output and compare it to the actual output produced by the function.

@Test
fun convertCelsiusToKelvin() {
    val input = 20
    val expected = 293

    val mainActivity = MainActivity()
    val output = mainActivity.convertCelsiusToKelvin(input)
    assertEquals(expected, output)
}

Now I’m going to run the test and see…

We passed. All good. I suggest try changing the expected value to make the test fail on purpose. A test that never fails is a crappy one.

How to write Instrumented Tests

Instrumented tests can be done with just the Junit class, and we want to make sure the default test implementation is set to AndroidJUnitRunner in the app/build.gradle file.

android {
    defaultConfig {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

On top of that, have these dependencies installed.

androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'

You don’t necessarily need the Espresso library to do bare instrumented tests, but it does however let us do tests with the UI.

Here’s our app that we’ll be testing with. Forget the bottom part for now, what it does is lets the user type his name and it shows a suited welcome message when the button is clicked.

Now with Instrumented Tests we have access to Android components like Context. We can still use assert functions in our tests and that will be perfectly fine, but I’m going to create a UI Test using Espresso.

@Test
fun messageShows() {
    onView(withId(R.id.firstName)).perform(typeText("John"))
    onView(withId(R.id.secondName)).perform(typeText("Smith"))
    onView(withId(R.id.button)).perform(click())
    onView(withId(R.id.message)).check(matches(withText("Welcome, John Smith!")))
}

What I’m doing here is letting the test simulate user actions like typing texts in EditTexts and clicking Buttons, then I’m going to test that my message TextView displays the message I’m expecting to see.

Further Reading

What we’ve covered here are the very basics. Fortunately for us, we have access to Google Code Samples which can help us see how test classes are properly implemented. I’ll myself will be going deeper into TDD in the following weeks. Happy Coding.