Testing in Android

Types of Testing :

  • Unit Testing
  • UI Testing
  • Integration Test
Unit Testing
Used to test the business login of our application
These tests are very fast and least expensive tests we can write because it runs on JVM
The most commonly used tools for Unit testing are JUnit and Mockito

Integration Testing
Used to test the android components like Activity, Fragments, service, Context, Intent etc
These run on JVM as well like the Unit Test and not on emulator or real devcie
The most common tool for integration testing is Roboelectric

UI Testing
Used to test our User Interface like Views, Buttons, EditText, TextView etc
These tests run on Emulator or Real Device
The most common tool for UI testing is Espresso and UI Automator

Suppose i create a class called Validation and create a method email() to check for the entered email pattern.
class Validation {
private val EMAIL_PATTERN = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+"

fun email(email : String) : Boolean {
return email.matches(EMAIL_PATTERN.toRegex())
}
}
Now create a class called ValidationTest in the test folder by right clicking on the Validation class
class ValidationTest {

}
Now we need to create the object of Validation class in the test class
class ValidationTest {
private lateinit var validation : Validation

@Before
fun setUp() {
validation = Validation()
}
}
@Before is used to execute the code before we start with unit testing. If we need to initialise any value or a object then we need to do it before we start running the unit test.
Now create a method testEmail() and annotate it with @Test and call the email method using the validation object that was used to initialise the Validation class
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test

class ValidationTest {
private lateinit var validation : Validation

@Before
fun setUp() {
validation = Validation()
}

@Test
fun testEmail() {
val result = validation.email("keshu.pai@gmail.com")
assertThat(result).isTrue()
}
}
Defining a Unit Test :
<UnitOfWork>_<stateUnderTest>_<expectedBehaviour>


What is JUnit?

JUnit is an open-source, Java-based unit testing framework that plays a crucial role in achieving the culture of TDD (Test Driven Development). The TDD culture lays strong emphasis on setting up the test data for testing a logic that would be implemented once the testing is successful. JUnit helps to increase the software stability as it helps in identifying the bug in the code logic at an early stage without requiring the software to go to production. This helps in reducing the time required to debug any issues later on.

JUnit is used more often by developers for implementing unit test cases for the functionalities they have developed. However, these days testers also use this framework for performing unit testing of the applications. 

JUnit is used due to the following reasons:

  • Helps in automating test cases.
  • Helps in reducing defects in the code logic whenever the logic changes.
  • Helps in reducing the cost of testing as the bugs are identified, captured and addressed at early phases in the software development.
  • Helps to identify the gaps in coding and gives a chance to refactor the code

What is Unit Testing?

Unit testing is a software testing strategy that tests single entities like methods or classes at a time. This helps to ensure that the product quality is met as per the business requirements. It also helps in reducing the technical debt of the application by helping developers discover issues in the code logic due to any changes. It also gives insights into how the code logic implemented could impact future changes

What will happen if the return type of the JUnit method is String?

The test case execution will fail because the JUnit methods are designed to return void

What are the features of JUnit?

Following are the features of JUnit:

  • JUnit is an open-source framework.
  • Supports automated testing of test suites.
  • Provides annotations for identifying the test methods.
  • Provides assertions to test expected results or exceptions of the methods under test.
  • Provides a platform for running test cases automatically and checking their results and giving feedback.

What are some of the important annotations provided by JUnit?

Some of the annotations provided by JUnit are as follows:

  • @Test: This annotation over a public method of void return type can be run as a test case. This is a replacement of the org.junit.TestCase annotation.
  • @Before: This is used when we want to execute the preconditions or any initialisation based statements before running every test case.
  • @BeforeClass: This is used when we want to execute statements before all test cases. The statements may include test connections, common setup initialisation etc.
  • @After: This is used when we want to execute statements after each test case. The statements can be resetting the variables, deleting extra memory used etc.
  • @AfterClass: This is used when certain statements are required to be executed after all the test cases of the class are run. Releasing resource connections post-execution of test cases is one such example.
  • @Ignores: This is used when some statements are required to be ignored during the execution of test cases.
  • @Test(timeout=x): This is used when some timeout during the execution of test cases is to be set. The value of x is an integer that represents the time within which the tests have to be completed.
  • @Test(expected=NullPointerException.class): This is used when some exception thrown by the target method needs to be asserted.

What is the importance of @Test annotation?
@Test annotation is used for marking the method as a test method

What is a test suite?

A test suite is a bundle of multiple unit test cases which can be run together. We can use @RunWith and @Suite annotations over the test class for running the test suite

What is mocking?
Mocking is a phenomenon where an object mimics a real object

What is Mockito? What are some of its advantages?

Mockito is an open-source, Java-based, mocking framework that allows the creation of test objects that simulate the behaviour (mock) of real-world objects. This helps in achieving test-driven or behaviour-driven development. The framework allows developers to verify system behaviours without establishing expectations. Mockito framework attempts to eliminate expect-run-verify development patterns by removing external specifications and dependencies.
Some of the advantages of Mockito are:

  • Mocks are created at runtime, hence reordering method input parameters or renaming interface methods will not break test code.
  • Mockito supports returning of values.
  • It supports exception simulation
  • It provides a check on the order of method calls.
  • It helps in creating mock objects using annotation

What are the JUnit Assert Methods?
Assert methods are utility methods that support assert conditions in test cases. They belong to the Assert class in JUnit 4 and the Assertions class in JUnit 5. It is recommended to import the assert methods statically to the test class for avoiding using the class as a prefix to the method

Let us consider an example of the Multiplier class:

public class Multiplier{
   public int multiply(int num1, int num2){
       return num1 * num2;
   }
}

Following are some of the assert methods:

  • assertEquals(): This method compares 2 objects for equality by making use of the equals() method of the object. This is shown in the test case that multiplies 2 methods and checks for the expected and actual value below.
@Test @DisplayName("Multiplying 2 numbers") public void multiplyTest() { assertEquals(50, multiplier.multiply(10, 5)); }
When two objects are found to be equal based on the equals() method implementation of the object, then assertEquals() returns normally. Else, an exception will be thrown and the test will stop its execution.

  • assertTrue(): This method tests whether the value of a variable is true.
@Test                                               
public void checkNumTest() {
    int num1 = 20;
    assertTrue("Number is not equal to 0", num1!=0);
}

   If the assertion fails, then an exception will be thrown and the test execution will be stopped.   

  • assertFalse(): This method tests whether the value of a variable is false.
@Test                                               
public void checkNumTest() {
    int num1 = -20;
    assertFalse("Number is not greater than 0",num1>0);
}

   If the assertion fails, then an exception will be thrown and the test execution will be stopped.

  • assertNull(): This method tests if a variable is null. If null, it returns normally else an exception will be thrown and the test stops execution.
@Test                                               
public void checkNullTest() {
    int num1 = null;
    assertNull("Number is null",num1);
}
  • assertNotNull(): This method tests whether a variable is not null. If it is not null, then the test returns normally else an exception will be thrown and the test stops its execution.
@Test                                               
public void checkNotNullTest() {
    int num1 = null;
    assertNotNull("Number is null",num1);
}
  • assertSame(): This method checks if two references of the object are pointing to the same object.
   @Test                                               
   public void checkAssertSameTest() {
        Object num1 = new Object();
        Object num2 = new Object();
        assertSame(num1, num2);
   }

   If the object references are pointing to the same object, the test runs normally else, it will throw an exception and the test execution is aborted.

  • assertNotSame(): This method checks if two references of an object are not pointing to the same object.
@Test                                               
public void checkAssertSameTest() {
     Object num1 = new Object();
     Object num2 = new Object();
     assertSame(num1, num2);
}

   If the object references are not pointing to the same object, the test runs normally else, it will throw an exception and the test execution is aborted.   

  • assertThat(): This method compares the object to org.hamcrest.Matcher for checking if it matches whatever the Matcher class requires for the object to match. Consider the following code.
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItems;
import org.junit.jupiter.api.Test;

public class MultiplierTest {
    @Test
    public void assertThatTest() {
        assertThat(
          Arrays.asList(1,2,3,4), 
          hasItems(2,3));
    }
}

In this code, we will be asserting that the list has some items specified in the hamcrest’s hasItems() method. If the assertThat is successful, i.e if the list indeed has items specified, then the test runs normally. Else, the test throws an exception and the test stops executing

What is the importance of @RunWith annotation?

@RunWith annotation lets us run the JUnit tests with other custom JUnit Runners like SpringJUnit4ClassRunner, MockitoJUnitRunner etc. We can also do parameterized testing in JUnit by making use of @RunWith(Parameterized.class)

How does JUnit help in achieving tests isolation?

For calling test methods, JUnit creates separate individual instances of the test class. For example, if the test class contains 10 tests, then JUnit creates 10 instances of the class to execute the test methods. In this way, every test is isolated from the other

What are the best practices for writing Unit Test Cases?

A standard unit test case comprises a known input and an expected output. These two things need to be known before we run the test case. A known input is tested for a precondition and an expected output is tested by postcondition. Following are the best practices for writing unit test cases:

  • For every method, we need to have at least two unit test cases - a positive test case and a negative test case.
  • If there are sub-requirements for a requirement, then those sub-requirements should have their own positive and negative test cases.
  • Each test case should be independent of other test cases. If we make a chain of unit test cases, then it would not be possible for finding the root cause of the test case failures.
  • Mock all the external services that are used by the modules under test. This is necessary because we do not want to unnecessarily debug our modules under test due to the failures of the external systems.
  • Configuration settings need not be tested as they won’t be part of any unit code. Even if we want to inspect the configuration, then test whether the loading code is working or not.
  • The unit test cases should be named consistently and clearly. The names of the test cases should be dependent on the operations that the test case would test.

What are the differences between JUnit 4 and JUnit 5?


How to ignore tests in JUnit?

We need to ignore test cases when we have certain functionalities that are not certain or they are under development. To wait for the completion of the code, we can avoid running these test cases. In JUnit 4, we can achieve this by using @Ignore annotation over the test methods. In JUnit 5, we can do it using @Disabled annotation over the test methods.

What is the purpose of @Before and @After annotations in JUnit 4?

These are the annotations present in JUnit 4. Methods annotated with @Before will be called and run before the execution of each test case. Methods annotated with @After will be called and executed after the execution of each test case. If we have 5 test cases in a JUnit class, then the methods annotated with these two would be run 5 times. In JUnit 5, the annotations @Before and @After are renamed to @BeforeEach and @AfterEach for making it more readable.

How can we test protected methods?

For testing protected methods, the test class should be declared in the same package as that of the target class.

How do you assert exceptions thrown in JUnit 4 tests?

Consider an example to see how to write test cases for asserting exceptions.

import org.apache.commons.lang3.StringUtils;
public final class ExceptionDemo {
   public String convertToUpperCase(String input) {
       if (StringUtils.isEmpty(input)) {
           throw new IllegalArgumentException("Input empty, cannot convert to upper case!!");
       }
       return input.toUpperCase();
   }
}

The method convertToUpperCase() defined in the ExceptionDemo class would throw an IllegalArgumentException if an empty string is passed as an input to the method.

What are the differences between @Before, @After, @BeforeClass and @AfterClass?


Define code coverage. What are the different types of code coverages?

The extent to which the source code is unit tested is called coverage. There are three types of coverage techniques, they are:

  • Statement coverage: This ensures that each statement/line in the source code is executed and tested.
  • Decision coverage: This ensures that every decision point that results in true or false is executed and run.
  • Path coverage: This ensures that every possible route from a given point is run and tested




Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API