Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Integrating Unit Tests into Android Projects in 2024

Tech 1

What is a unit test?

In the context of Android development, a unit test targets the smallest testable parts of an application, such as individual methods within a class. The testing pyramid places unit tests at the fonudation because they are fast to run and provide immediate feedback on logic correctness. Any class in your project, from TextFormatter.kt to UserRepository.kt, and even presentation layer components like LoginViewModel.kt, can be considered a unit under test.

Why are they necessary?

Consider a scenario where a utility is required to extract the numeric version from a system string like "Android OS 14". The initial implementation might look like this:

object SystemVersionParser {
    fun extractVersionCode(rawName: String): Int {
        val pattern = Regex("\\d+")
        val match = pattern.find(rawName)
        return match?.value?.toIntOrNull() ?: -1
    }
}

A new team member might refactor this, assuming the prefix never changes:

object SystemVersionParser {
    fun extractVersionCode(rawName: String): Int {
        return try {
            rawName.removePrefix("Android OS ").toInt()
        } catch (e: Exception) {
            -1
        }
    }
}

This refactoring appears cleaner but fails when the string changes to "Android OS 14 (Special Edition)". Without tests, this regression might only be caught after deployment, leading to data parsing errors.

How unit tests prevent regressions

If the original developer had provided a test class, the issue would have been caught immediately during the build process.

import org.junit.Assert.assertEquals
import org.junit.Test

class SystemVersionParserTest {

    @Test
    fun `test standard version extraction`() {
        val result = SystemVersionParser.extractVersionCode("Android OS 14")
        assertEquals(14, result)
    }

    @Test
    fun `test version extraction with suffix`() {
        val result = SystemVersionParser.extractVersionCode("Android OS 14 (Special Edition)")
        assertEquals(14, result)
    }
}

When the new developer runs these tests after their refactor, the test version extraction with suffix case fails. This acts as a safety net, ensuring that changes do not break existing logic. In a professional CI/CD environment, these tests run automatically, preventing merges that cause regressions.

Case study: Testing in AOSP

The Android Open Source Project (AOSP) relies heavily on continuous integration. Contributors submit patches to the main branch, but these are only merged after passing rigorous checks. A key component of this process is atest, a test harness that runs unit tests defined in TEST_MAPPING files.

For example, the Settings application module defines its test suites to run before submission (presubmit):

{
  "presubmit": [
    {
      "name": "SettingsUnitTests",
      "options": [
        { "include-filter": "com.android.settings.display" },
        { "include-filter": "com.android.settings.network" }
      ]
    }
  ]
}

This configuration ensures that any modification to the display or network settings logic triggers the associated unit tests. If a test fails, the submission is blocked, maintaining the stability of the platform despite having thousands of contributors.

Do unit tests replace manual QA?

Unit tests cannot replace manual testing. While unit tests verify that a specific function calculates 1 + 1 = 2 under various conditions, they do not assess the user experience. Manual testing is required to evaluate UI fluidity, accessibility, and complex user flows that are difficult to simulate in code. However, by offloading logic verification to automated tests, QA teams can focus more on exploratory testing and usability.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.