TL;DR
TDD (Test-Driven Design) là hệ tư tưởng “viết test trước, code sau” hoạt động như một màng lọc kiến trúc, giúp định hình interface, decouple logic và triệt tiêu vĩnh viễn tình trạng “fix một chỗ, oẳng ba chỗ”.
Core Concept
- Tại sao nó tồn tại? Phá vỡ “Confirmation Bias” (code xong mới viết test để bao biện cho logic vừa đẻ ra). Ngăn chặn tình trạng viết test hời hợt, né edge cases và dẹp loạn các module bị dính chặt vào nhau (tight-coupling).
- Giải quyết bài toán gì? Thiết lập vòng lặp feedback tức thì (
Red -> Green -> Refactor). Ép kỹ sư phải định nghĩa contract rành mạch (input, output, hành vi) trước khi nhảy vào implement chi tiết thuật toán. - Nó có thay thế cái gì hay không? Tiễn quy trình “Code & Fix” truyền thống vào dĩ vãng. Thay thế tư duy thiết kế thác nước “Big Design Up Front” (vẽ UML rườm rà) bằng kiến trúc tiến hóa (Evolutionary Architecture) — hệ thống tự scale và hoàn thiện qua từng bài test.
- Áp dụng dụng vào những dự án nào? Phụ thuộc vào vòng đời và độ phức tạp của domain:
- Tuyệt đối KHÔNG: Dự án MVP, Outsource mì ăn liền (thay đổi requirement theo ngày) hoặc Frontend UI/Components.
- Bắt buộc CÓ: Core Product (lifespan > 2 năm), logic phức tạp. Tỏa sáng rực rỡ ở Backend (xử lý logic, API contract) và phần State Management của Frontend.
- Cơ chế hoạt động (How it works under the hood).
- TDD là công cụ thực chiến của
Interface-Driven Design (IDD). Gọi một method chưa tồn tại ép bạn phải đẻ raInterfacecủa nó trước. - Pragmatic TDD: Phân lô codebase. Áp dụng 100% TDD cho “Lòng đỏ” (Core Domain/Use Cases - thuần logic, không I/O). Bỏ qua TDD hoàn toàn cho “Lòng trắng” (Infrastructure/Database/UI - nơi logic mỏng nhưng thay đổi công nghệ liên tục) để né bẫy over-mocking.
- TDD là công cụ thực chiến của
Practical Implementation
- Trade-offs: Đánh đổi tốc độ ship code ở giai đoạn đầu để lấy sự ổn định tuyệt đối về sau. Rủi ro ôm “nợ bảo trì test” khổng lồ nếu áp dụng sai lãnh thổ (chạy TDD cho các module giao tiếp I/O hoặc UI dễ biến động).
import { describe, it, expect } from "bun:test";
// Contract Definition
export interface IPostRepository {
getTrending(limit: number): Promise<PostEntity[]>;
}
// Unit Test Setup (Red -> Green)
describe("TimelineFeed Logic", () => {
it("should fetch trending posts correctly", async () => {
// Space/Time Complexity of mock setup: O(1) Time / O(N) Space
const mockRepo: IPostRepository = {
getTrending: async () => [
{ id: "1", content: "Pragmatic TDD strict mode" },
],
};
const feedService = new FeedService(mockRepo);
const posts = await feedService.getFeed();
expect(posts.length).toBeGreaterThan(0);
});
});
// Implementation (Refactor)
export class FeedService {
constructor(private readonly repo: IPostRepository) {}
// PERF: Implement Redis caching layer here if RPS throughput exceeds infrastructure limits
// Time Complexity: O(1) from service abstraction
public async getFeed(): Promise<PostEntity[]> {
return await this.repo.getTrending(50);
}
}
Related Notes: