Skip to content
All posts
February 27, 20264 min read

How to Start Test Automation From Zero: A Practical Roadmap

Starting automation from scratch is intimidating. Most guides assume you already have a setup. This one starts at zero — no existing tests, no infrastructure — and walks you through the first 30 days.

TestingAutomationAndroid
Share:

You've been testing manually. Everyone agrees automation is needed. Nobody knows where to start. The codebase has zero tests.

Here's the actual playbook.


Why Most Automation Initiatives Fail at the Start

Teams start with UI automation because "that's what you can see." They write brittle end-to-end tests. Those tests break constantly. The team loses confidence, the suite gets abandoned, and automation gets a bad reputation.

The right starting point isn't the most visible layer. It's the most stable one.


Week 1: Set Up the Foundation

Before writing a single test, establish the infrastructure:

1. Add the testing dependencies

For Android (Kotlin):

kotlin
// build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.13.2")
    testImplementation("io.mockk:mockk:1.13.10")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
    testImplementation("app.cash.turbine:turbine:1.1.0")
    
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
}

2. Run the empty test suite

bash
./gradlew test

It should complete with "0 tests ran." This verifies the infrastructure works before you write anything.

3. Write one trivial test

kotlin
class SanityTest {
    @Test
    fun `addition works`() {
        assertEquals(4, 2 + 2)
    }
}

This sounds pointless but it confirms:

  • The test runner runs
  • Dependencies are correct
  • CI can pick up results

Week 2: Cover the Most Critical Business Logic

Don't start with the whole codebase. Identify the one or two functions where a bug would be most catastrophic.

In a task manager app, that's task creation and completion logic. In a payment app, it's billing calculation. In a content platform, it's access control.

Write 5-10 tests covering that logic with meaningful assertions. Not line coverage — behavior coverage.

kotlin
class TaskServiceTest {
    private val repository = FakeTaskRepository()
    private val service = TaskService(repository)
    
    @Test
    fun `new task is created with incomplete status`() = runTest {
        service.createTask("Write tests")
        val task = repository.getAllTasks().first()
        assertFalse(task.completed)
    }
    
    @Test
    fun `cannot create task with blank title`() = runTest {
        val result = service.createTask("")
        assertTrue(result.isFailure)
        assertThat(result.exceptionOrNull()).isInstanceOf(ValidationException::class.java)
    }
    
    @Test
    fun `completing a task marks it done`() = runTest {
        val task = service.createTask("Buy groceries").getOrThrow()
        service.completeTask(task.id)
        val updated = repository.getTask(task.id)
        assertTrue(updated!!.completed)
    }
}

Goal: 10-15 unit tests by end of week 2. All passing. All in CI.


Week 3: Expand to All Business Logic Units

Map out every class that contains business logic:

  • ViewModels / Presenters
  • Use cases / interactors
  • Repositories (with fakes)
  • Utility classes
  • Data mappers

Write tests for each one. Don't chase 100% coverage — cover the decisions and transformations.

[!TIP] Create a

code
testFixtures
or
code
fakes
directory for fake implementations (FakeRepository, FakeNetworkClient). You'll reuse these across many tests. Writing fakes forces you to design better interfaces.

A fake repository example:

kotlin
class FakeTaskRepository : TaskRepository {
    private val tasks = mutableListOf<Task>()
    
    override suspend fun createTask(task: Task) {
        tasks.add(task)
    }
    
    override suspend fun getAllTasks(): List<Task> = tasks.toList()
    
    override suspend fun getTask(id: String): Task? = tasks.find { it.id == id }
    
    override suspend fun deleteTask(id: String) {
        tasks.removeAll { it.id == id }
    }
    
    // Test helper — not part of the interface
    fun addTask(task: Task) = tasks.add(task)
    fun clear() = tasks.clear()
}

Goal: 30-50 unit tests covering core business logic.


Week 4: Add Your First Integration Test

Now add one integration test that tests a real component working together with its dependency:

kotlin
@RunWith(AndroidJUnit4::class)
class TaskDaoTest {
    private lateinit var db: AppDatabase
    private lateinit var dao: TaskDao
    
    @Before
    fun setup() {
        db = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            AppDatabase::class.java
        ).build()
        dao = db.taskDao()
    }
    
    @After
    fun teardown() = db.close()
    
    @Test
    fun `inserting a task makes it retrievable`() = runTest {
        val task = TaskEntity(id = "1", title = "Test", completed = false)
        dao.insert(task)
        
        val result = dao.getById("1")
        assertNotNull(result)
        assertEquals("Test", result!!.title)
    }
}

Goal: 5-10 integration tests covering database and API interactions.


The 30-Day Automation Checkpoint

After 30 days, you should have:

  • Test infrastructure set up and running in CI
  • 40-60 unit tests covering core business logic
  • 5-10 integration tests for database / API
  • All tests running in under 60 seconds
  • Tests running automatically on every PR

Zero UI tests. That comes later, once the lower layers are stable.


What to Do in Month 2

  • Cover all ViewModel/presenter logic with unit tests
  • Add tests for edge cases you find during manual testing
  • Add the first 3-5 UI tests for critical user paths
  • Set up a code coverage report (but don't chase the number yet)

The Mistake That Kills New Automation Programs

Trying to automate everything at once. You can't automate a legacy codebase in a sprint. You can add 10 valuable tests per week, indefinitely.

Consistency beats ambition. Ten tests per week is 500 tests in a year — more than enough to give your team confidence.


Takeaways

  • Start with infrastructure, then the most critical business logic — not UI
  • Create fake implementations of dependencies — they pay dividends across the whole suite
  • Aim for 10-15 tests in week 1-2, building from there
  • Don't add UI tests until your lower layers are stable and well-covered
  • A slow, consistent cadence (10 tests/week) beats a sprint that burns out the team
Share:
S

Sudarshan Chaudhari

AI Systems Builder / Product Engineer

Bangkok, Thailand

Solo Android developer with 13+ years in QA, building Android apps, AI automation systems, and developer tools at SudarshanTechLabs.

Stay updated

Get new posts on Android, Kotlin, and solo dev straight to your inbox.

Newsletter preferences

Related Apps

MyFamilyTracker

Real-time family location sharing — Firebase Realtime DB for sub-second propagation, WorkManager + ForegroundService for OS-compliant background collection, geofencing via Google Maps API.

Building something? Available for Android dev and QA consulting.

Work with me

Comments — powered by Giscus