Tiết kiệm cả triệu đô!

Trong quy trình phát triển phầm mềm, đã lập trình là phải test, không test bằng cách này thì cách kia, quan trọng nhất có thể kể đến:

  1. Unit test - test đơn vị
  2. Integration test - test tích hợp
  3. Monkey test - test theo một số kịch bản ngẫu nhiên
  4. QA test - test bởi đội kiểm thử

Một công ty phần mềm có thể phải tiêu tốn rất nhiều tiền để xây dựng một đội QA chuyên nghiệp, nhưng nếu biết tận dụng tốt Unit test thì cũng có thể không cần đến mội đội ngũ QA đông đảo, các dự án của mình từng làm rất ít QA và Unit Test đã giúp bọn mình cover được khoảng 80% rồi.

Unit test là gì

Unit test là kiểm thử ở mức hàm trong source code, công việc này thường do dev thực hiện, và kết quả là một bản báo cáo trực quan thế này:

Kết quả này bao gồm 2 thông số quan trọng nhất:

  1. Missed Instructions: là số câu lệnh chưa được test
  2. Missed Branches: là số nhánh logic (if else) chưa được test

Như dự án ezyfox-bean đã được test 100% nên không có dòng lệnh nào hay nhánh login nào chưa được chạy qua.

Rào cản Unit test

UnitTest (UT một khái niệm không còn xa lại với tất cả lập trình viên chúng ta. Nhưng vẫn là 1 thứ gì đó quá khó để áp dụng triệt để, theo kinh nghiệm cá nhân mình, có một số vấn đề sau:

  1. Các dự án outsource đòi hòi tốc độ hoàn thành dự án nên với deadline gấp gáp chúng ta chưa thể áp dụng được
  2. UT làm tăng chi phí cho doanh nghiệp, kéo dài thời gian phát triển dự án và có thể làm mất đi cơ hội cạnh tranh
  3. Do chính lập trình viên chúng ta chủ quan, ỷ lại vào đội QA
  4. Do quản lý dự án không có nhiều hiểu biết về kỹ thuật nên không chú trọng vào vấn đề này
  5. Do UT chưa được đưa vào quy trình phát triển phầm mềm

Nhưng phải áp dụng

Tất cả các lý do trên theo mình là hợp lý, tuy nhiên nó chỉ hợp lý đối với những dự án nhỏ và những dự án phát triển trong giai đoạn đầu (phiên bản đầu tiên đưa ra thị trường). Nhưng đối với những dự án lớn trên 10 nghìn dòng code và những dự án đã phát triển đến phiên bản thứ 2, UT nên là điều bắt buộc. Vì sao vậy? Lại tiếp tục theo kinh nghiệm của mình, thời gian để tạo ra 1 dòng code là 1 thì thời gian để để fix bug cho dòng code đó gấp 21 lần, và càng nhiều bug thì thời gian và chi phí của doanh nghiệp lại đội lên gấp bội, theo đúng quy trình close 1 bug thì nó phải thế này:

  1. QA test và phát hiện ra bug, tiến hành chụp ảnh, quay video, nói rõ các bước cho dev hiểu
  2. QA thông qua công cụ thông báo cho dev
  3. Dev test lại, nếu thấy không phải bug quay ra tranh luận với QA
  4. QA test lại và thấy bug, DEV không cãi được về fix
  5. Dev debug các kiểu, deploy, test đi test lại
  6. Dev chụp lại evidence đã fix rồi assign lại cho QA
  7. QA test lại nếu chưa ok thì reopen và báo lại cho DEV, ok rồi thì close

Trời ơi!!!, nó là cả một quy trình phức tạp, đau đầu, tranh cãi và chán nản, chi phí của doanh nghiệp cũng tăng lên từ đó. Chưa kể là chẳng may có lỗi nào lọt lên môi trường thật, nó có thể ảnh hưởng đến hàng nghìn, hàng triệu khách hàng

Vậy nên UT phải được đưa vào quy trình, phải được hợp thức hóa và thực thi cẩn thận

Các framework phổ biến

Với java thì hiện tại vẫn có 2 framework là phổ biến nhất đó là:

  1. JUnit: Là framework đi cùng java từ thời đầu, hiện tại đang ở phiên bản 5
  2. TestNG: đi sau JUnit và cố gắng tạo ra nhiều tính năng hơn JUnit, vì mình thích cái là có thể cấu hình được nhiều thứ thông qua file XML.

Một số thư viện mở rộng cho JUnit và TestNG có thể kể đến:

  1. Mockito: thư viện giả lập (mockup) dữ liệu và gọi hàm
  2. Mockk: thư viện giả lập (mockup) dữ liệu và gọi hàm cho kotlin
  3. Spring test: thư viện hỗ trợ quản lý bean, inject bean, giúp bạn tự động khởi tạo các đối tượng test
  4. test-util: hộ trợ test performance, assertion (kiểm tra kết quả), random kết quả và gọi các hàm thông qua java refleciton.

Một ví dụ đơn giản

Giờ thì thử một ví dụ xem sao nhé. Hãy tưởng tượng bạn đang cần thực hiện tính năng login, user sẽ chỉ được input username là chữ thường và từ 5 đến 8 kí tự, vậy chúng ta sẽ có tối thiểu 5 cases cần test

  1. username chiều dài từ 5 đến 8 kí tự: thành công
  2. username với chiều dài nhỏ hơn 5: thất bại
  3. username với chiều dài lớn hơn 8: thất bại
  4. username chứa cả chữ và số: thất bại
  5. username chứa cả chữ hoa và chữ thường: thất bại

Đây là lớp validator của chúng ta:

public class UserValidator {
    private static final String USERNAME_PATTERN = "[a-z]{5,8}";
    public boolean validateUsername(String username) {
        return Pattern.compile(USERNAME_PATTERN)
            .matcher(username)
            .matches();
    }
}

Còn đây là lớp test của chúng ta:

public class UserValidatorTest {
    private final UserValidator sut = new UserValidator();
    @Test
    public void validateUsernameSuccess() {
        String username = "hello";
        assert sut.validateUsername(username);
    }
    @Test
    public void validateUsernameFailsDueToMinLength() {
        String username = "he";
        assert !sut.validateUsername(username);
    }
    @Test
    public void validateUsernameFailsDueToMaxLength() {
        String username = "hellohellohello";
        assert !sut.validateUsername(username);
    }
    @Test
    public void validateUsernameFailsDueToContainsNumber() {
        String username = "hello123";
        assert !sut.validateUsername(username);
    }
    @Test
    public void validateUsernameFailsDueToContainsUpperCase() {
        String username = "helloHE";
        assert !sut.validateUsername(username);
    }
}

Nó cũng khá đơn giản phải không? Tuy nhỏ nhưng có võ, đó là tư tưởng của UT.

Tổng kết

Nếu dự án outsource của bạn không quá gấp gáp, hay áp dụng UT nhé, ngăn chặn được bug và tạo ấn tượng tốt với khách hàng. Nếu dự án sản phẩm của bạn đã bắt đầu có khách hàng, hãy áp dụng UT nhé, khách hàng lúc nào cũng là người khó tính và thiếu kiên nhẫn, hãy biết cách làm hài lòng họ

Mình sẽ nói về cách cài đặt cho maven và gradle, các kinh nghiệm làm việc với Unit Test sao cho hiệu quả ở các bài viết sau nhé.

Tham khảo

  1. Wiki