This practical codelab is part of Unit 1: Get started in the Android Developer Fundamentals (Version 2) course. You will get the most value out of this course if you work through the codelabs in sequence:

Introduction

Testing your code can help you catch bugs early in development, when bugs are the least expensive to address. As your app gets larger and more complex, testing improves your code's robustness. With tests in your code, you can exercise small portions of your app in isolation, and you can test in ways that are automatable and repeatable.

Android Studio and the Android Testing Support Library support several different kinds of tests and testing frameworks. In this practical you explore Android Studio's built-in testing functionality, and you learn how to write and run local unit tests.

Local unit tests are tests that are compiled and run entirely on your local machine with the Java Virtual Machine (JVM). You use local unit tests to test the parts of your app that don't need access to the Android framework or an Android-powered device or emulator, for example the internal logic. You also use local unit tests to test parts of your app for which you can create fake ("mock" or stub) objects that pretend to behave like the framework equivalents.

Unit tests are written with JUnit, a common unit testing framework for Java.

What you should already know

You should be able to:

What you'll learn

What you'll do

This practical uses the SimpleCalc app from the previous practical codelab (Android fundamentals 3.1: The debugger). You can modify that app in place, or make a copy of your project folder before proceeding.

You write and run your tests (both unit tests and instrumented tests ) inside Android Studio, alongside the code for your app. Every new Android project includes basic sample classes for testing that you can extend or replace for your own uses.

In this task you return to the SimpleCalc app, which includes a basic unit testing class.

1.1 Explore source sets and CalculatorTest

Source sets are collections of code in your project that are for different build targets or other "flavors" of your app. When Android Studio creates your project, it creates three source sets:

In this task you'll explore how source sets are displayed in Android Studio, examine the Gradle configuration for testing, and run the unit tests for the SimpleCalc app.

  1. Open the SimpleCalc project in Android Studio, if you have not already done so.
  2. Open the Project > Android pane, and expand the app and java folders.

The java folder in the Android view lists all the source sets in the app by package name. In this case (as shown below), the app code is in the com.android.example.SimpleCalc source set. The test code is in the source set with test appearing in parentheses after the package name: com.android.example.SimpleCalc (test).

  1. Expand the com.android.example.SimpleCalc (test) folder.

This folder is where you put your app's local unit tests. Android Studio creates a sample test class for you in this folder for new projects, but for SimpleCalc the test class is called CalculatorTest.

  1. Open CalculatorTest.

Examine the code and note the following:

1.2 Run tests in Android Studio

In this task you'll run the unit tests in the test folder and view the output for both successful and failed tests.

  1. In the Project > Android pane, right-click (or Control-click) CalculatorTest and select Run 'CalculatorTest'.

The project builds, if necessary, and the CalculatorTest pane appears at the bottom of the screen. At the top of the pane, the drop-down list for available execution configurations also changes to CalculatorTest.

All the tests in the CalculatorTest class run, and if those tests are successful, the progress bar at the top of the view turns green. (In this case, there is currently only one test.) A status message in the footer also reports "Tests Passed."

  1. Open CalculatorTest if it is not already open, and change the assertion in addTwoNumbers() to:
assertThat(resultAdd, is(equalTo(3d)));
  1. In the run configurations dropdown menu at the top of the screen, select CalculatorTest (if it is not already selected) and click Run .

The test runs again as before, but this time the assertion fails (3 is not equal to 1 + 1.) The progress bar in the run view turns red, and the testing log indicates where the test (assertion) failed and why.

  1. Change the assertion in addTwoNumbers() back to the correct test and run your tests again to ensure they pass.
  2. In the run configurations dropdown, select app to run your app normally.

With unit testing, you take a small bit of code in your app such as a method or a class, and isolate it from the rest of your app, so that the tests you write makes sure that one small bit of the code works in the way you'd expect. Typically, a unit test calls a method with a variety of different inputs, and verifies that the method does what you expect and returns what you expect it to return.

In this task you learn more about how to construct unit tests. You'll write additional unit tests for the Calculator utility methods in the SimpleCalc app, and run those tests to make sure that they produce the output you expect.

Note: Unit testing, test-driven development, and the JUnit 4 API are all large and complex topics and outside the scope of this course.

2.1 Add more tests for the add() method

Although it is impossible to test every possible value that the add() method may ever see, it's a good idea to test for input that might be unusual. For example, consider what happens if the add() method gets arguments:

In this task we'll add more unit tests for the add() method to test different kinds of inputs.

  1. Add a new method to CalculatorTest called addTwoNumbersNegative(). Use this skeleton:
@Test
public void addTwoNumbersNegative() {
}

This test method has a similar structure to addTwoNumbers(): it is a public method, with no parameters, that returns void. It is annotated with @Test, which indicates it is a single unit test.

Why not just add more assertions to addTwoNumbers()? Grouping more than one assertion into a single method can make your tests harder to debug if only one assertion fails, and obscures the tests that do succeed. The general rule for unit tests is to provide a test method for every individual assertion.

  1. Run all tests in CalculatorTest, as before.

In the test window both addTwoNumbers and addTwoNumbersNegative are listed as available (and passing) tests in the left panel. The addTwoNumbersNegative test still passes even though it doesn't contain any code—a test that does nothing is still considered a successful test.

  1. Add a line to addTwoNumbersNegative() to invoke the add() method in the Calculator class with a negative operand.
double resultAdd = mCalculator.add(-1d, 2d);

The d notation after each operand indicates that these are numbers of type double. Because the add() method is defined with double parameters, a float or int will also work. Indicating the type explicitly enables you to test other types separately, if you need to.

  1. Add an assertion with assertThat().
assertThat(resultAdd, is(equalTo(1d)));

The assertThat() method is a JUnit4 assertion that claims the expression in the first argument is equal to the one in the second argument. Older versions of JUnit used more specific assertion methods (assertEquals(), assertNull(), or assertTrue()), but assertThat() is a more flexible, more debuggable and often easier to read format.

The assertThat() method is used with matchers. Matchers are the chained method calls in the second operand of this assertion, is(equalto(). The Hamcrest framework defines the available matchers you can use to build an assertion. ("Hamcrest" is an anagram for "matchers.") Hamcrest provides many basic matchers for most basic assertions. You can also define your own custom matchers for more complex assertions.

In this case the assertion is that the result of the add() operation (-1 + 2) equals 1.

  1. Add a new unit test to CalculatorTest for floating-point numbers:
@Test
public void addTwoNumbersFloats() {
   double resultAdd = mCalculator.add(1.111f, 1.111d);
   assertThat(resultAdd, is(equalTo(2.222d)));
}

Again, a very similar test to the previous test method, but with one argument to add() that is explicitly type float rather than double. The add() method is defined with parameters of type double, so you can call it with a float type, and that number is promoted to a double.

  1. Click Run to run all the tests again.

This time the test failed, and the progress bar is red. This is the important part of the error message:

java.lang.AssertionError: 
Expected: is <2.222>
     but: was <2.2219999418258665>

Arithmetic with floating-point numbers is inexact, and the promotion resulted in a side effect of additional precision. The assertion in the test is technically false: the expected value is not equal to the actual value.

The question this raises is: When you have a precision problem with promoting float arguments, is that a problem with your code, or a problem with your test? In this particular case both input arguments to the add() method from the SimpleCalc app will always be type double, so this is an arbitrary and unrealistic test. However, if your app was written such that the input to the add() method could be either double or float, and you only care about some precision, you need to provide some wiggle room to the test so that "close enough" counts as a success.

  1. Change the assertThat() method to use the closeTo() matcher:
assertThat(resultAdd, is(closeTo(2.222, 0.01)));

You need to make a choice for the matcher. Click on closeTo twice (until the entire expression is underlined), and press Alt+Enter (Option+Return on a Mac). Choose isCloseTo.closeTo (org.hamcrest.number).

  1. Click Run to run all the tests again.

This time the test passes.

With the closeTo() matcher, rather that testing for exact equality you can test for equality within a specific delta. In this case the closeTo() matcher method takes two arguments: the expected value and the amount of delta. In the example above, that delta is just two decimal points of precision.

2.2 Add unit tests for the other calculation methods

Use what you learned in the previous task to fill out the unit tests for the Calculator class.

  1. Add a unit test called subTwoNumbers() that tests the sub() method.
  2. Add a unit test called subWorksWithNegativeResults() that tests the sub() method where the given calculation results in a negative number.
  3. Add a unit test called mulTwoNumbers() that tests the mul() method.
  4. Add a unit test called mulTwoNumbersZero() that tests the mul() method with at least one argument as zero.
  5. Add a unit test called divTwoNumbers() that tests the div() method with two non-zero arguments.
  6. Add a unit test called divTwoNumbersZero() that tests the div() method with a double dividend and zero as the divider.

All of these tests should pass, except divTwoNumbersZero() which causes an illegal argument exception for dividing by zero. If you run the app, enter zero as Operand 2, and click Div to divide, the result is an error.

Task 2 solution code

Android Studio project: SimpleCalcTest

The following code snippet shows the tests for this task:

@Test
public void addTwoNumbers() {
   double resultAdd = mCalculator.add(1d, 1d);
   assertThat(resultAdd, is(equalTo(2d)));
}

@Test
public void addTwoNumbersNegative() {
   double resultAdd = mCalculator.add(-1d, 2d);
   assertThat(resultAdd, is(equalTo(1d)));
}
@Test
public void addTwoNumbersFloats() {
   double resultAdd = mCalculator.add(1.111f, 1.111d);
assertThat(resultAdd, is(closeTo(2.222, 0.01)));
}
@Test
public void subTwoNumbers() {
   double resultSub = mCalculator.sub(1d, 1d);
   assertThat(resultSub, is(equalTo(0d)));
}
@Test
public void subWorksWithNegativeResult() {
   double resultSub = mCalculator.sub(1d, 17d);
   assertThat(resultSub, is(equalTo(-16d)));
}
@Test
public void mulTwoNumbers() {
   double resultMul = mCalculator.mul(32d, 2d);
   assertThat(resultMul, is(equalTo(64d)));
}
@Test
public void divTwoNumbers() {
   double resultDiv = mCalculator.div(32d,2d);
   assertThat(resultDiv, is(equalTo(16d)));
}
@Test
public void divTwoNumbersZero() {
   double resultDiv = mCalculator.div(32d,0);
   assertThat(resultDiv, is(equalTo(Double.POSITIVE_INFINITY)));
}

Challenge 1: Dividing by zero is always worth testing for, because it is a special case in arithmetic. How might you change the app to more gracefully handle divide by zero? To accomplish this challenge, start with a test that shows what the right behavior should be.

Remove the divTwoNumbersZero() method from CalculatorTest, and add a new unit test called divByZeroThrows() that tests the div() method with a second argument of zero, with the expected result as IllegalArgumentException.class. This test will pass, and as a result it will demonstrate that any division by zero will result in this exception.

After you learn how to write code for an Exception handler, your app can handle this exception gracefully by, for example, displaying a Toast message to the user to change Operand 2 from zero to another number.

Challenge 2: Sometimes it's difficult to isolate a unit of code from all of its external dependencies. Rather than organize your code in complicated ways just so you can test it more easily, you can use a mock framework to create fake ("mock") objects that pretend to be dependencies. Research the Mockito framework, and learn how to set it up in Android Studio. Write a test class for the calcButton() method in SimpleCalc, and use Mockito to simulate the Android context in which your tests will run.

Android Studio has built-in features for running local unit tests:

The related concept documentation is in 3.2: App testing.

Android Studio documentation:

Android developer documentation:

Other:

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Open the SimpleCalc app from the practical on using the debugger. You're going to add a POW button to the layout. The button calculates the first operand raised to the power of the second operand. For example, given operands of 5 and 4, the app calculates 5 raised to the power of 4, or 625.

Before you write the implementation of your power button, consider the kind of tests you might want to perform using this calculation. What unusual values may occur in this calculation?

  1. Update the Calculator class in the app to include a pow() method. Hint: Consult the documentation for the java.lang.Math class.
  2. Update the MainActivity class to connect the POW Button to the calculation.

Now write each of the following tests for your pow() method. Run your test suite each time you write a test, and fix the original calculation in your app if necessary:

Answer these questions

Question 1

Which statement best describes a local unit test? Choose one:

Question 2

Source sets are collections of related code. In which source set are you likely to find unit tests? Choose one:

Question 3

Which annotation is used to mark a method as an actual test? Choose one:

Submit your app for grading

Guidance for graders

Check that the app has the following features:

To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).

For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).