1. Testing là gì và tại sao Backend cần Testing?
Software Testing là quá trình kiểm tra phần mềm để xác minh rằng hệ thống hoạt động đúng theo yêu cầu, phát hiện lỗi và hạn chế những thay đổi làm hỏng chức năng đã tồn tại.
Trong Backend, một chức năng thường không chỉ bao gồm một đoạn code đơn lẻ. Ví dụ, API tạo đơn hàng có thể liên quan đến:
- Controller nhận và kiểm tra HTTP request.
- Service thực hiện logic nghiệp vụ.
- Repository truy vấn database.
- Transaction đảm bảo các thao tác dữ liệu nhất quán.
- Message broker nhận sự kiện để xử lý tiếp.
- Một dịch vụ bên ngoài xử lý thanh toán.
Nếu chỉ kiểm tra bằng cách gọi API thủ công và thấy kết quả đúng một lần, chúng ta chưa thể chắc chắn chức năng sẽ tiếp tục hoạt động đúng khi code thay đổi, dữ liệu khác đi hoặc dependency gặp sự cố.
Testing giải quyết những vấn đề nào?
Phát hiện lỗi sớm: Lỗi trong một hàm tính toán thường dễ xác định và sửa hơn lỗi chỉ xuất hiện khi nhiều thành phần phối hợp.
Bảo vệ chức năng cũ: Khi refactor hoặc thêm tính năng, bộ test có thể phát hiện hành vi trước đây đã bị thay đổi ngoài ý muốn.
Làm rõ yêu cầu nghiệp vụ: Một test tốt mô tả rõ điều gì phải xảy ra khi đầu vào hợp lệ, dữ liệu thiếu hoặc một điều kiện đặc biệt xuất hiện.
Hỗ trợ refactor: Khi có các test đáng tin cậy, developer có thể thay đổi cấu trúc bên trong mà vẫn kiểm tra được hành vi bên ngoài.
Giảm rủi ro triển khai: Test có thể được chạy tự động trong CI/CD trước khi đưa code lên môi trường staging hoặc production.
2. Unit Testing là gì?
Unit Testing là kiểm thử một đơn vị logic nhỏ của chương trình trong phạm vi được kiểm soát.
Đơn vị đó thường là một hàm, method hoặc một nhóm logic nhỏ có trách nhiệm rõ ràng. Phạm vi cụ thể phụ thuộc vào ngôn ngữ, kiến trúc và cách tổ chức code.
Ví dụ, một hàm tính phí vận chuyển:
func CalculateShippingFee(total float64) float64 {
if total >= 500_000 {
return 0
}
return 30_000
}
Ta có thể kiểm thử:
- Đơn hàng dưới 500.000 đồng phải chịu phí 30.000 đồng.
- Đơn hàng đúng 500.000 đồng được miễn phí.
- Đơn hàng trên 500.000 đồng được miễn phí.
Đặc điểm của Unit Test
Một unit test tốt thường có các đặc điểm:
- Nhanh: Có thể chạy nhiều lần trong thời gian ngắn.
- Tập trung: Kiểm tra một hành vi hoặc quy tắc cụ thể.
- Dễ xác định nguyên nhân lỗi: Khi test thất bại, phạm vi cần kiểm tra tương đối nhỏ.
- Có tính lặp lại: Cùng điều kiện đầu vào và trạng thái ban đầu phải cho kết quả ổn định.
- Ít phụ thuộc môi trường: Thông thường không cần database thật, mạng hoặc dịch vụ bên ngoài.
Unit test không bắt buộc chỉ được kiểm tra đúng một dòng code. Điều quan trọng là phạm vi kiểm thử phải rõ ràng và tránh kéo theo quá nhiều thành phần không liên quan.
3. Integration Testing là gì?
Integration Testing là kiểm thử sự phối hợp giữa nhiều thành phần để xác minh rằng chúng hoạt động đúng khi kết nối với nhau.
Ví dụ, một integration test cho chức năng tạo đơn hàng có thể kiểm tra:
- Service nhận yêu cầu tạo đơn hàng.
- Repository ghi đơn hàng vào database.
- Database áp dụng các constraint.
- Transaction commit thành công.
- Khi đọc lại, dữ liệu được lưu đúng như mong đợi.
Khác với unit test chỉ kiểm tra logic trong phạm vi nhỏ, integration test tập trung vào các ranh giới kết nối giữa thành phần.
4. AAA Pattern: Arrange, Act, Assert
AAA là cách tổ chức test phổ biến, gồm ba giai đoạn.
Arrange: Chuẩn bị dữ liệu đầu vào, dependency và trạng thái ban đầu.
Act: Thực hiện hành động cần kiểm thử.
Assert: Kiểm tra kết quả thực tế có đúng với kết quả mong đợi hay không.
Ví dụ:
func TestDiscountForPremiumCustomer(t *testing.T) {
// Arrange
customer := Customer{
Tier: "premium",
}
total := int64(1_000_000)
// Act
got := CalculateDiscount(customer, total)
// Assert
want := int64(100_000)
if got != want {
t.Fatalf("got %d, want %d", got, want)
}
}
AAA giúp test dễ đọc vì người xem có thể nhanh chóng xác định:
- Điều kiện ban đầu là gì?
- Hành động nào đang được kiểm tra?
- Điều kiện thành công được định nghĩa ra sao?
Không cần ép buộc mọi test phải có ba khối tách biệt rõ ràng. Với test đơn giản, điều quan trọng vẫn là tính dễ hiểu.
5. Test Case và Test Scenario
Hai thuật ngữ này thường được sử dụng gần nhau nhưng có thể phân biệt như sau:
- Test scenario: Mô tả tình huống hoặc luồng cần kiểm thử.
- Test case: Mô tả điều kiện, đầu vào, hành động và kết quả mong đợi cụ thể.
Ví dụ với chức năng đăng nhập:
Scenario: Đăng nhập bằng thông tin hợp lệ.
Các test case có thể gồm:
- Email và mật khẩu hợp lệ.
- Email không tồn tại.
- Mật khẩu sai.
- Tài khoản bị khóa.
- Thiếu email.
- Mật khẩu rỗng.
- Người dùng gửi request với định dạng không hợp lệ.
Một test case tốt cần có kết quả mong đợi rõ ràng. Nếu chỉ ghi “test đăng nhập”, người viết test vẫn chưa xác định được chính xác cần kiểm tra điều gì.
6. Contract Testing là gì?
Trong kiến trúc microservices, một service thường gọi service khác qua HTTP hoặc message broker.
Contract Testing kiểm tra rằng bên cung cấp và bên sử dụng giao tiếp với nhau theo một hợp đồng đã xác định.
Ví dụ, service đơn hàng kỳ vọng API khách hàng trả về:
{
"id": 123,
"status": "active"
}
Nếu service khách hàng đổi tên status thành account_status mà không có kế hoạch tương thích, consumer có thể gặp lỗi.
Contract test giúp phát hiện sự khác biệt giữa hợp đồng mà consumer mong đợi và giao diện mà provider cung cấp.
Contract testing không thay thế hoàn toàn integration test hoặc E2E test. Nó tập trung vào sự tương thích giữa các bên, đặc biệt hữu ích khi các service được phát triển và triển khai độc lập.
7. Kết luận
Unit Testing và Integration Testing giải quyết hai vấn đề khác nhau nhưng bổ trợ chặt chẽ cho nhau.
Unit Testing giúp xác minh các quy tắc và logic trong phạm vi nhỏ, chạy nhanh và dễ xác định nguyên nhân lỗi. Integration Testing giúp xác minh các thành phần phối hợp đúng, đặc biệt ở những ranh giới như database, HTTP, message broker và dịch vụ bên ngoài.
Một chiến lược testing tốt không dựa vào số lượng test hay coverage đơn thuần. Nó cần xác định đúng hành vi cần bảo vệ, chọn phạm vi test phù hợp, kiểm soát dependency và đảm bảo kết quả có thể lặp lại.








Khoá học lập trình game con rắn cho trẻ em




