Chapters

Hide chapters

Android Test-Driven Development by Tutorials

First Edition · Android 10 · Kotlin 1.3 · AS 3.5

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section II: Testing on a New Project

Section 2: 8 chapters
Show chapters Hide chapters

Section III: TDD on Legacy Projects

Section 3: 9 chapters
Show chapters Hide chapters

2. What Is a Test?
Written by Fernando Sproviero

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

A test is a manual or automatic procedure used to evaluate if the System Under Test (SUT) behaves correctly.

The SUT may be a method, an entire class, a module or even a whole application.

From now on, when mentioning anything related to writing a test this book will be referring to the automatic procedure form of a test.

To write a test, you need to understand the feature, specification or requirement of the component you are implementing. That component may be an Activity, Fragment, View Model or several of these components working together. These may come in different forms, such as user stories, use cases or some other kind of documentation.

Testing is an important part of software development. By including tests along with your code, you can ensure that your code works and that later changes to the code won’t break it. Tests can give you the peace of mind you need to develop quickly and catch bugs before they’re released.

Essentially, there are two approaches to writing tests:

  • Write tests before you write the feature.
  • Write tests after you write the feature.

This book primarily focuses on writing tests first versus writing them after a feature has been implemented.

Why should you test?

Writing tests can take more time up front, and it is code you write that the client won’t “see”, which is why tests are sometimes skipped by developers. However, having tests can speed up development down the road and it presents some advantages.

Change/refactor confidence

You have probably run into a scenario in which you have a section of your application that works correctly before adding new functionality to the application. After adding new functionality, either in Quality Assurance (QA) or after it is released to customers you discover that this new functionality broke the previously working section. That is called a regression.

Documentation

Some companies and developers treat tests as a complementary documentation to explain how the implementation of a feature works. When you have well-written tests, they provide an excellent description of what your code should do. By writing a test, its corresponding implementation and repeating this until a feature is completed, bearing in mind that these tests can be treated as specifications, will help you and your team when a refactor or a modification of the feature is required.

How to write a test

There are many things to bear in mind when writing a test. You’ll understand them by reading this book and practicing writing tests. However, the most important aspects of writing a test are as follows:

fun whenAnsweringCorrectly_shouldIncrementCurrentScore() {
  ...
}
fun whenIncrementingScore_shouldIncrementCurrentScore() {
  val score = Score(0)

  score.increment()

  if (score.current == 1) {
    print("Success")
  } else {
    throw AssertionError("Invalid score")
  }
}
fun whenIncrementingScore_aboveHighScore_shouldAlsoIncrementHighScore() {
  val score = Score(0)

  score.increment()

  if (score.highest == 1) {
    print("Success")
  } else {
    throw AssertionError("Invalid high score")
  }
}

What should you test?

You should test code that is related to the logic of your app. This may include code that you have to write to:

Code that breaks often

If you have a legacy project without tests, and it breaks often whenever you modify its code, it’s useful to have tests for them, so that the next time you make a modification you will be sure that it won’t keep breaking.

Code that will change

If you know that some code will be refactored in the near future, tests will be useful here, too, because if you wrote tests for this feature, you can support on them to refactor the code and be sure you don’t break anything.

What should you not test?

External dependencies

You should assume that all dependencies (libraries and frameworks, including those from the Android SDK) have been tested. Thus, you shouldn’t test functionality of external libraries because the goal is to test things that you control, not a third party tool created by someone else.

Autogenerated code

You shouldn’t write tests for autogenerated code. Following the previous principle, it’s supposed to be that the library or tool that generates code is tested properly.

When should you not test?

Throwaway/prototype code

Usually, when writing a Minimal Viable Product (MVP), you should focus on just writing the features so the client can get a feeling of what the final product could be.

Code you don’t have time to test

This is a controversial topic. Often, developers get stuck in a rut wherein they are fighting fires instead of proactively writing quality code, and they are not given the time to address code quality.

Code spikes

At some point, you may find yourself working with a new library or, perhaps, you may realize that you aren’t sure how to implement something. This makes it very difficult to write a test first because you don’t know enough about how you are going to implement the functionality to write a meaningful failing test. In these instances, a code spike can help you figure things out.

What is test coverage?

You can measure how many lines of code of your app have been executed when you run your tests. An app with a high test coverage percentage “suggests” that it works as expected and has a lower chance of containing bugs.

Criterion

To measure, there are several coverage criterion that you may choose. The most common are:

fun getFullname(firstName: String?, lastName: String?): String {
  var fullname = "Unknown"
  if (firstName != null && lastName != null) {
    fullname = "$firstName $lastName"
  }
  return fullname
}

Tools

There are tools that can assist you to measure the test coverage metric.

100% coverage?

In real-world apps, reaching a test coverage of 100%, no matter which criterion you use, is almost impossible to achieve. It often doesn’t add value to test all methods, of all the classes, all of the time.

data class Pet(var name: String)
fun whenCreatingPetWithName_shouldTheNameSetFromTheConstructor() {
  val aName = "Rocky"
  val aPet = Pet(aName)

  if (aPet.name == aName) {
    print("Success\n")
  } else {
    throw AssertionError("Invalid pet name")
  }
}

Key points

  • A test is a procedure used to evaluate if a method, an entire class, a module or even a whole application behaves correctly.
  • This book focuses on writing tests before implementing the features.
  • You should write tests to have confidence when refactoring.
  • Tests also act as complementary documentation of the application features.
  • The tests you write should be short, simple to read and easy to follow.
  • You should only write tests related to the logic of your application.
  • You can use test coverage tools to find untested code that should be tested.

Where to go from here?

Congratulations! Now you should understand what a test is, why it matters and the coverage metric.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now