TDD: khái niệm, nguyên tắc kiểm thử và phương pháp luận

Tác giả: IT Sectr Đã đăng: 2026-04-09 Thời gian đọc: 9 phút

Test-Driven Development (TDD) là một phương pháp phát triển trong đó các bài kiểm thử được viết trước khi triển khai mã. Lập trình viên đầu tiên xây dựng hành vi mong đợi dưới dạng một bài kiểm thử thất bại, sau đó viết mã tối thiểu để vượt qua nó và cuối cùng là tái cấu trúc kết quả. Theo Martin Fowler (2023), TDD không phải là kỹ thuật kiểm thử — đó là kỹ thuật thiết kế, kỷ luật hóa kiến trúc và giảm số lượng khuyết điểm ở giai đoạn viết mã.

Những điểm chính

  • TDD là phương pháp luận trong đó bài kiểm thử được viết trước khi triển khai, không phải sau đó
  • Chu trình Red-Green-Refactor là nền tảng của TDD: kiểm thử đỏ, kiểm thử xanh, tái cấu trúc
  • JUnitMockito là những công cụ chính cho TDD trong phát triển Android
  • Mức độ phủ mã trong các dự án TDD thường vượt quá 90% nhờ kỷ luật “kiểm thử trước”
  • Tái cấu trúc mà không sợ phá vỡ chức năng là lợi thế chính của cách tiếp cận TDD

TDD là gì?

Test-Driven Development là một thực hành phát triển phần mềm trong đó các bài kiểm thử tự động quyết định việc viết mã sản xuất. Khác với cách tiếp cận truyền thống nơi mã được viết rồi mới kiểm thử, TDD đảo ngược trình tự: đầu tiên viết bài kiểm thử, sau đó viết mã vượt qua bài kiểm thử đó.

Người sáng lập TDD được coi là Kent Beck, người đã xây dựng thực hành này vào cuối những năm 1990 trong khuôn khổ phương pháp Extreme Programming (XP). Trong cuốn sách “Test-Driven Development: By Example” (2002), Beck đã mô tả năm quy tắc của TDD đã trở thành kinh điển: viết bài kiểm thử trước mã sản xuất, viết vừa đủ mã để vượt qua bài kiểm thử và tái cấu trúc sau mỗi chu trình.

Các nguyên tắc chính của TDD

Nguyên tắc đầu tiên — bài kiểm thử xác định giao diện. Lập trình viên buộc phải suy nghĩ về cách thành phần sẽ được sử dụng trước khi nghĩ về cách nó được triển khai. Điều này hình thành một API sạch ngay từ đầu.

TDD như một kỹ thuật thiết kế

Nguyên tắc thứ hai — triển khai tối thiểu. Khi bài kiểm thử được viết, lập trình viên viết chính xác lượng mã sản xuất cần thiết để vượt qua nó — không hơn một dòng. Điều này ngăn chặn sự trừu tượng hóa sớm và độ phức tạp quá mức, mà Martin Fowler gọi là Speculative Generality.

Sự khác biệt giữa TDD và kiểm thử thông thường

Sự khác biệt chính giữa TDD và kiểm thử “sau khi viết” — kỷ luật về trình tự. Trong TDD, bài kiểm thử không chỉ xác minh mã — nó định hướng cấu trúc của mã. Theo nghiên cứu của Microsoft Research (Nagappan et al., 2008), các nhóm áp dụng TDD cho thấy mật độ khuyết điểm giảm 40–90% so với các nhóm sử dụng cách tiếp cận truyền thống.

Chu trình Red-Green-Refactor

Chu trình Red-Green-Refactor là một chuỗi ba bước được lặp lại cho mỗi bài kiểm thử mới. Red: viết một bài kiểm thử không vượt qua. Green: viết mã tối thiểu để bài kiểm thử vượt qua. Refactor: cải thiện mã mà không thay đổi hành vi của nó.

Giai đoạn Red: viết bài kiểm thử thất bại

Lập trình viên viết một bài kiểm thử xác minh chức năng chưa được triển khai. Ở giai đoạn này, bài kiểm thử phải thất bại — điều này xác nhận rằng bài kiểm thử thực sự đang xác minh điều gì đó. Trong môi trường phát triển Android, framework JUnit 5 hiển thị chỉ báo màu đỏ cho các bài kiểm thử thất bại, điều này đã đặt tên cho giai đoạn này.

kotlin
class CalculatorTest {
    fun testAddition() {
        val result = Calculator().add(2, 3)
        Assertions.assertEquals(5, result)
    }
}

Giai đoạn Green: triển khai tối thiểu

Ở giai đoạn này, mã sản xuất tối thiểu đủ để vượt qua bài kiểm thử được viết. Không dư thừa — chỉ những gì cần thiết cho chỉ báo xanh. Nếu việc triển khai có thể là một hằng số, hãy để nó là hằng số. Việc tái cấu trúc sẽ diễn ra ở bước tiếp theo khi các bài kiểm thử mới xuất hiện.

kotlin
class Calculator {
    fun add(a: Int, b: Int): Int {
        return a + b
    }
}

Giai đoạn Refactor: cải thiện không rủi ro

Bài kiểm thử xanh là bảo hiểm cho việc tái cấu trúc. Lập trình viên có thể viết lại việc triển khai, tối ưu hóa hiệu suất hoặc cải thiện khả năng đọc, tin tưởng rằng bài kiểm thử sẽ ngay lập tức phát hiện bất kỳ sai lệch nào so với hành vi mong đợi. Trong phát triển di động Android, giai đoạn này đặc biệt quan trọng để trích xuất các giao diện chung và giảm trùng lặp mã.

Lợi ích của TDD trong phát triển di động

Việc áp dụng TDD trong các dự án di động mang lại những lợi ích có thể đo lường được, được xác nhận bởi cả nghiên cứu học thuật và thực tiễn của các studio phát triển hàng đầu.

Giảm mật độ khuyết điểm

Một nghiên cứu của IBM (Bhat & Nagappan, 2006) trên bốn dự án công nghiệp cho thấy các nhóm sử dụng TDD tạo ra ít hơn 40% khuyết điểm so với các nhóm tương tự làm việc theo sơ đồ truyền thống. Đối với phát triển di động, nơi chi phí sửa lỗi sau khi phát hành trên Google Play cao hơn đáng kể so với giai đoạn viết mã, chỉ số này rất quan trọng.

Tài liệu hóa mã thông qua kiểm thử

Các bài kiểm thử được viết với TDD đóng vai trò là tài liệu sống của API. Lập trình viên mới tham gia dự án có thể đọc các bài kiểm thử và hiểu cách mỗi thành phần nên được sử dụng. Điều này đặc biệt có giá trị trong điều kiện tỷ lệ thay đổi nhân sự cao — một vấn đề điển hình của các studio di động.

Tái cấu trúc tự tin

Mức độ phủ mã vượt quá 90% cho phép lập trình viên tái cấu trúc mà không sợ làm hỏng thứ gì. Google, trong cuốn sách “Software Engineering at Google” (2020), gọi mức độ phủ kiểm thử là yếu tố chính cho phép duy trì cơ sở mã sạch trong các dự án với hàng triệu dòng mã.

Công cụ và framework cho TDD

Hệ sinh thái TDD trong phát triển di động bao gồm các công cụ cho kiểm thử đơn vị, mocking và xác minh thành phần giao diện người dùng — cho cả Android và iOS.

Công cụNền tảngMục đích
JUnit 5Android (Kotlin/Java)Framework cơ bản cho kiểm thử đơn vị
MockitoAndroidTạo đối tượng mock và xác minh lời gọi
MockKAndroid (Kotlin)Mocking với cú pháp Kotlin-first và hỗ trợ coroutine
TurbineAndroidKiểm thử Kotlin Flow và luồng phản ứng
XCTestiOS (Swift)Framework kiểm thử tiêu chuẩn

Chọn framework cho Android

Đối với các dự án Android trên Kotlin, ngăn xếp tiêu chuẩn bao gồm JUnit 5 + MockK. MockK được ưa chuộng hơn Mockito vì nó hỗ trợ các tính năng hạng nhất của Kotlin — sealed class, coroutine và suspend function — mà không cần cấu hình thêm.

Công cụ cho iOS

Trong phát triển iOS, TDD được triển khai thông qua XCTest — framework tích hợp của Apple cung cấp các xác nhận (assertions), lớp kiểm thử và tích hợp CI/CD qua Xcode Server hoặc GitHub Actions. Để mocking trên iOS, các thư viện Cuckoo và OHHTTPStubs được sử dụng.

Ví dụ mã với TDD trong Kotlin

Hãy xem xét một kịch bản TDD thực tế trong Kotlin cho Android — kiểm thử kho lưu trữ người dùng. Đầu tiên chúng ta viết bài kiểm thử, sau đó viết triển khai vượt qua bài kiểm thử đó.

Bước 1: kiểm thử cho UserRepository

kotlin
class UserRepositoryTest {
    private val api = mockk<UserApi>()
    private val dao = mockk<UserDao>()
    private val repo = UserRepository(api, dao)

    fun `when api returns user then cache and emit`() = runTest {
        val user = User(1, "Alice")
        coEvery { api.getUser(1) } returns user
        every { dao.insert(user) } returns Unit

        val result = repo.getUser(1)

        assertEquals(user, result)
        verify { dao.insert(user) }
    }
}

Bước 2: triển khai tối thiểu

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUser(id: Int): User {
        val user = api.getUser(id)
        dao.insert(user)
        return user
    }
}

Bước 3: kiểm thử bộ nhớ đệm với chế độ ngoại tuyến

Sau khi vượt qua bài kiểm thử đầu tiên, chúng ta thêm bài kiểm thử thứ hai — xác minh hành vi khi gặp lỗi mạng. Bây giờ bài kiểm thử xác định rằng khi API thất bại, kho lưu trữ phải trả về dữ liệu từ bộ nhớ đệm.

kotlin
fun `when api fails then return cached user`() = runTest {
    val cached = User(1, "Cached Alice")
    coEvery { api.getUser(1) } throws IOException()
    every { dao.getById(1) } returns cached

    val result = repo.getUser(1)

    assertEquals(cached, result)
}

Các lỗi thường gặp khi áp dụng TDD

Việc chuyển đổi sang TDD đi kèm với những lỗi điển hình có thể làm mất tất cả lợi ích của phương pháp luận. Hiểu được những cạm bẫy này giúp các nhóm áp dụng thực hành hiệu quả hơn.

Các bài kiểm thử quá lớn

Phản mẫu đầu tiên và phổ biến nhất — kiểm thử quá nhiều chức năng trong một bài kiểm thử duy nhất. Một bài kiểm thử nên xác minh chính xác một xác nhận. Nếu một bài kiểm thử thất bại, lập trình viên phải biết chính xác điều gì đã hỏng mà không cần gỡ lỗi thêm.

Bỏ qua giai đoạn đỏ

Sai lầm thứ hai — viết một bài kiểm thử vượt qua ngay từ đầu. Nếu bài kiểm thử chưa bao giờ có màu đỏ dù chỉ một lần, không có sự chắc chắn rằng nó thực sự đang xác minh điều gì đó. Quy tắc: không bao giờ tin tưởng một bài kiểm thử mà bạn chưa thấy nó thất bại.

Bỏ qua tái cấu trúc

Sai lầm thứ ba phổ biến — dừng lại ở giai đoạn xanh. Tái cấu trúc không phải là tùy chọn mà là một bước bắt buộc của chu trình. Nếu không có nó, cơ sở mã sẽ xuống cấp, các bài kiểm thử trở nên mong manh và lợi ích của TDD bị mất.

  • Kiểm thử việc triển khai thay vì hành vi — các bài kiểm thử gắn với chi tiết và hỏng sau mỗi lần tái cấu trúc
  • Thiếu kiểm thử cho các trường hợp biên — danh sách rỗng, giá trị null, điều kiện biên không được bao phủ
  • Bỏ qua tốc độ kiểm thử — các bài kiểm thử chậm làm chậm vòng lặp phản hồi và phá vỡ kỷ luật TDD

Câu hỏi thường gặp

TDD là kỹ thuật kiểm thử hay thiết kế?

TDD trước hết là một kỹ thuật thiết kế, không phải kỹ thuật kiểm thử. Các bài kiểm thử trong TDD đóng vai trò của một đặc tả kỹ thuật: chúng xác định API của thành phần trước khi triển khai. Chính Kent Beck gọi TDD là “kỷ luật của thiết kế, không phải của kiểm thử”.

Mất bao lâu để thành thạo TDD?

Theo các nghiên cứu của Microsoft Research, các nhóm cần từ 3 đến 6 tháng thực hành liên tục để TDD trở thành thói quen. 2–3 tuần đầu tiên, năng suất giảm 15–30%, nhưng sau khi thích nghi, nó trở lại mức ban đầu hoặc vượt qua nhờ giảm thời gian gỡ lỗi.

TDD có phù hợp cho các thành phần giao diện người dùng không?

Có, nhưng có giới hạn. Đối với logic giao diện (ViewModel, State), TDD có thể áp dụng trực tiếp. Đối với các thành phần trực quan (Compose UI, SwiftUI Views), kiểm thử ảnh chụp màn hình (snapshot testing) bổ sung cho TDD nhưng không thay thế nó. Khuyến nghị tách biệt logic nghiệp vụ khỏi hiển thị.

Có thể áp dụng TDD trong các dự án cũ không?

Đối với mã cũ, chiến lược được khuyến nghị là “kiểm thử đặc tính” (characterization tests) — nơi các bài kiểm thử được viết trên hành vi hiện tại, sau đó mã được tái cấu trúc. Cách tiếp cận này được mô tả trong cuốn sách của Michael Feathers “Working Effectively with Legacy Code” (2004) và cho phép áp dụng TDD dần dần.

TDD kết hợp với Clean Architecture như thế nào?

TDD và Clean Architecture củng cố lẫn nhau. Kiến trúc sạch yêu cầu ranh giới rõ ràng giữa các lớp, và TDD buộc lập trình viên thiết kế các ranh giới đó thông qua các bài kiểm thử. Lớp miền (domain) được kiểm thử cách ly với các phụ thuộc mock, lớp dữ liệu (data) — thông qua các bài kiểm thử tích hợp.

Tổng kết

  • TDD — phương pháp luận nơi bài kiểm thử được viết trước khi triển khai, hình thành API sạch và định hướng kiến trúc
  • Chu trình Red-Green-Refactor — đơn vị cơ bản của TDD: kiểm thử thất bại → triển khai tối thiểu → tái cấu trúc
  • Áp dụng TDD giảm mật độ khuyết điểm 40–90% theo các nghiên cứu của IBM và Microsoft Research
  • Công cụ chính cho phát triển Android: JUnit 5, MockK, Turbine cho Flow
  • MockK được ưa chuộng hơn Mockito trong các dự án Kotlin nhờ hỗ trợ coroutine và sealed class
  • Các lỗi thường gặp: kiểm thử quá lớn, bỏ qua giai đoạn đỏ, bỏ qua tái cấu trúc
  • Chiến lược áp dụng được khuyến nghị — dần dần, bắt đầu từ lớp miền và các tính năng mới, không cố gắng bao phủ toàn bộ mã cũ cùng một lúc

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm