Topic: 20 Understanding Testing in Android
This is our twentieth topic from learn android from basic to advance series
Hello devs, Can you believe we've reached the last topic in our Learn Android from Basic to Advance series? It's been quite a journey! Ok, devs So whenever you write a code for your Android App always remember to follow a test-driven development approach. Write a test case for your code. Let's Explore TDD.
TDD- Test Driven Development
Test-driven development (TDD) is like building a puzzle: you start with a picture in mind (your test), then you put in one piece at a time (your code) until the whole puzzle fits together perfectly.
Here's how it helps:
Writing Less but Enough Code: Instead of writing a ton of code all at once, TDD makes you focus on just what you need for each step. It's like making a sandwich - you only add the ingredients you need, not everything in the kitchen.
Preventing Common Mistakes: TDD stops you from making big mistakes like making your code too complicated or missing important things your app needs. It's like having a friend check your homework to make sure you didn't forget anything.
Concise and Clean Code: TDD encourages you to write neat and tidy code. It's like tidying up your room - everything has its place and it's easy to find what you need.
Saves Time and Money: By catching problems early and keeping your code organized, TDD actually saves time and money in the long run. It's like fixing a leaky faucet before it floods your house - it's easier and cheaper to deal with it early on.
So, TDD is like having a roadmap for building your app: it keeps you on track, helps you avoid wrong turns, and gets you to your destination faster and with fewer headaches.
It follows a simple cycle: Red, Green, and Refactor.
Red: Write a Failing Test
You start by writing a test that defines the behavior you want to implement in your app. This test initially fails because you haven't written any code to satisfy it yet.
In Android development, you might use testing frameworks like JUnit, Mockito, or Espresso for unit tests, mocking dependencies, and UI tests respectively.
For instance, if you're building a calculator app, you might write a test to ensure that adding two numbers works correctly.
Green: Write the Minimum Code to Pass the Test
Now, you write the minimal code required to make the failing test pass. You're not concerned about code quality or performance at this stage; you're just aiming to make the test pass.
In our calculator example, you'd implement the logic for adding two numbers together.
Refactor: Improve Code Quality
With the test passing, you can now refactor your code. Refactoring involves restructuring your code to improve readability, maintainability, or performance without changing its external behavior.
In Android development, you might extract methods, rename variables for clarity, or optimize algorithms.
Ensure that your tests still pass after refactoring. This ensures that you haven't unintentionally introduced bugs while improving your code.
Repeat the Cycle
After refactoring, you repeat the cycle by writing another failing test for the next feature or improvement you want to make.
This iterative process continues throughout the development lifecycle, with tests acting as a safety net, ensuring that changes don't break existing functionality.
Types of Tests in Android Development
Unit Tests
Integration Tests
UI Tests
Unit Tests
These test individual units or components of your app in isolation, such as classes or functions. You can use JUnit and Mockito for writing unit tests in Android. Unit testing follows the principles of the βArrange, Act, Assertβ (AAA) pattern.
Certainly! As an experienced Android developer, let's dive into how you can use JUnit and Mockito to write unit tests in Android Kotlin.
JUnit: JUnit is a popular testing framework for Java and Kotlin. It provides annotations and assertions to write and organize tests effectively.
Mockito: Mockito is a mocking framework for Java and Kotlin that allows you to create mock objects in your tests. Mock objects simulate the behavior of real objects, enabling you to isolate the unit of code you want to test.
Here's a step-by-step guide to writing a unit test using JUnit and Mockito in Android Kotlin:
Suppose you have a class called LoginManager
responsible for handling user authentication. It has a method login()
that communicates with a backend service to verify user credentials.
class LoginManager(private val authService: AuthService) {
fun login(username: String, password: String): Boolean {
return authService.authenticate(username, password)
}
}
Writing the Unit Test:
Set Up Your Test Environment:
- Ensure that JUnit and Mockito dependencies are added to your
build.gradle
file.
- Ensure that JUnit and Mockito dependencies are added to your
dependencies {
testImplementation 'junit:junit:4.13'
testImplementation 'org.mockito:mockito-core:3.11.2'
}
Write the Unit Test:
Create a Kotlin test file (e.g.,
LoginManagerTest.kt
) in thetest
directory.Use JUnit annotations to define your test class and methods.
import org.junit.Test
import org.junit.Assert.*
import org.mockito.Mockito.*
class LoginManagerTest {
@Test
fun `login should return true when authentication succeeds`() {
// Arrange
val authService = mock(AuthService::class.java)
`when`(authService.authenticate(anyString(), anyString())).thenReturn(true)
val loginManager = LoginManager(authService)
// Act
val result = loginManager.login("username", "password")
// Assert
assertTrue(result)
}
@Test
fun `login should return false when authentication fails`() {
// Arrange
val authService = mock(AuthService::class.java)
`when`(authService.authenticate(anyString(), anyString())).thenReturn(false)
val loginManager = LoginManager(authService)
// Act
val result = loginManager.login("username", "password")
// Assert
assertFalse(result)
}
}
In these tests:
Arrange: We create a mock
AuthService
object using Mockito and define its behavior usingwhen()
andthenReturn()
.Act: We call the
login()
method ofLoginManager
with test credentials.Assert: We verify if the method returns the expected result.
Run the Tests:
- Run the tests in Android Studio or using Gradle to ensure they pass.
By using JUnit and Mockito, you can effectively write unit tests for your Android Kotlin code. Mockito helps you isolate the unit under test by mocking dependencies, making your tests more focused and reliable. Unit tests ensure that individual components of your app behave as expected, leading to a more robust and stable application.
Integration Tests
Integration tests in Android Kotlin involve testing how different parts of your app work together. Unlike unit tests that focus on testing individual components in isolation, integration tests verify interactions between components, ensuring they integrate correctly and produce the desired behavior as a whole. For Android, you might use frameworks like Robolectric.
Robolectric
Robolectric is a testing framework for Android that allows you to run tests on the JVM (Java Virtual Machine) without needing to deploy them to a device or emulator. This makes testing faster and more convenient, especially for integration tests that require interactions with Android framework components.
Suppose we have a simple Android app with a CalculatorActivity
that performs basic arithmetic operations. We want to write an integration test to verify that the UI elements and logic in this activity work correctly.
Writing the Integration Test:
Set Up Your Test Environment:
- Ensure that Robolectric dependency is added to your
build.gradle
file:
- Ensure that Robolectric dependency is added to your
gradleCopy codetestImplementation 'org.robolectric:robolectric:4.6.1'
Write the Integration Test:
Create a Kotlin test file (e.g.,
CalculatorActivityTest.kt
) in thetest
directory.Use Robolectric's
@RunWith
annotation to specify the test runner.
import android.widget.Button
import android.widget.EditText
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Config.OLDEST_SDK])
class CalculatorActivityTest {
private lateinit var activity: CalculatorActivity
private lateinit var editTextNumber1: EditText
private lateinit var editTextNumber2: EditText
private lateinit var addButton: Button
@Before
fun setUp() {
activity = Robolectric.buildActivity(CalculatorActivity::class.java).create().start().resume().get()
editTextNumber1 = activity.findViewById(R.id.editTextNumber1)
editTextNumber2 = activity.findViewById(R.id.editTextNumber2)
addButton = activity.findViewById(R.id.addButton)
}
@Test
fun testAddition() {
// Enter numbers
editTextNumber1.setText("5")
editTextNumber2.setText("3")
// Click the add button
addButton.performClick()
// Verify result
assertEquals("8", activity.findViewById<TextView>(R.id.resultTextView).text)
}
}
In this test:
We use Robolectric's
Robolectric.buildActivity()
method to create an instance ofCalculatorActivity
.In the
setUp()
method annotated with@Before
, we initialize the activity and obtain references to its UI elements.In the
testAddition()
method annotated with@Test
, we simulate user interaction by setting text in theEditText
fields and clicking the add button.We then verify that the result displayed matches the expected sum.
Run the Tests:
- Run the tests in Android Studio or using Gradle to ensure they pass.
Interpret the Results:
If the test passes, it indicates that the
CalculatorActivity
is functioning correctly, and the integration between UI elements and logic is successful.If the test fails, investigate the failure reason, fix any issues, and rerun the test.
Robolectric allows you to test Android components in a simulated environment, providing fast and reliable integration tests without the need for a physical device or emulator. By leveraging Robolectric, you can ensure the correctness and robustness of your Android Kotlin applications.
UI Tests
These test the app's user interface, ensuring that UI elements and interactions behave as expected. UI tests, also known as user interface tests or end-to-end tests, verify that the user interface of your app behaves correctly across different screens and interactions. Espresso is a popular choice for UI testing in Android.
Suppose we have a simple Android app with a login feature. We want to write a UI test to verify that the login screen (LoginActivity
) functions correctly.
Writing the UI Test:
Set Up Your Test Environment:
- Ensure that the appropriate testing dependencies are added to your
build.gradle
file, including Espresso and JUnit.
- Ensure that the appropriate testing dependencies are added to your
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Write the UI Test:
Create a Kotlin test file (e.g.,
LoginActivityUITest.kt
) in theandroidTest
directory.Use Espresso to interact with UI elements and verify their behavior.
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions.*
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import org.junit.Rule
import org.junit.Test
class LoginActivityUITest {
@get:Rule
var activityScenarioRule = ActivityScenarioRule(LoginActivity::class.java)
@Test
fun testLoginSuccess() {
// Type username and password
onView(withId(R.id.editTextUsername)).perform(typeText("user"))
onView(withId(R.id.editTextPassword)).perform(typeText("password"), closeSoftKeyboard())
// Click login button
onView(withId(R.id.buttonLogin)).perform(click())
// Verify navigation to home screen
onView(withId(R.id.home_activity_layout)).check(matches(isDisplayed()))
}
@Test
fun testLoginFailure() {
// Type invalid username and password
onView(withId(R.id.editTextUsername)).perform(typeText("invalidUser"))
onView(withId(R.id.editTextPassword)).perform(typeText("invalidPassword"), closeSoftKeyboard())
// Click login button
onView(withId(R.id.buttonLogin)).perform(click())
// Verify error message is displayed
onView(withText("Invalid credentials")).check(matches(isDisplayed()))
}
}
In this test:
We use Espresso's
onView()
method to locate UI elements by their IDs.We use
perform()
to simulate user interactions, such as typing text intoEditText
fields and clicking buttons.We use
check()
to verify the expected outcome, such as checking if the home screen is displayed after successful login or if an error message is displayed for failed login attempts.
Run the Tests:
Run the tests in Android Studio by right-clicking on the test file and selecting "Run 'LoginActivityUITest'".
Alternatively, execute the tests using Gradle from the command line.
Interpret the Results:
If the tests pass, it indicates that the login screen functions correctly, and the UI interactions behave as expected.
If any test fails, investigate the failure reason, fix any issues, and rerun the test.
By writing UI tests, you can ensure that the user interface of your app behaves correctly across different scenarios and interactions, providing a smooth and reliable user experience.
Common mistakes to avoid
Testing Too Much or Too Little at Once:
- Don't try to test everything in one go, but also don't ignore important cases.
Not Testing Extreme Conditions:
- Make sure you test the limits, like the smallest and largest values, to catch any unexpected problems.
Tests Depending on Each Other:
- Each test should be independent, so one test's success or failure doesn't affect others.
Messy Test Data:
- Keep your test data organized and don't hardcode it directly into tests.
Forgetting to Test Error Handling:
- Check if your app handles errors gracefully, not just when everything goes smoothly.
Weak Assertions:
- Double-check your test results to make sure they cover everything you want to test.
Ignoring Performance Issues:
- Test how your app performs under pressure, like when lots of people are using it at once.
Forgetting to Update Tests:
- Keep your tests up-to-date as you change your app, or they might not catch new bugs.
Not Checking Enough Code:
- Make sure your tests cover all the important parts of your app's code.
Skipping Test Documentation:
- Write down what each test is supposed to do, so anyone reading it knows what to expect.
It's time to wrap up the blog devs and also it's time to end our series. Thank you for following this series. I hope this series has been a helpful resource for simplifying the process of learning Android development.
Connect with Me:
Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? π You can also follow my Instagram page for more mobile development-related content. π²π¨βπ» Letβs stay connected, share knowledge and have some fun in the exciting world of app development! π