Rspec: Chỉ test những gì thực sự quan trọng

1. Giới thiệu Việc viết test cho ứng dụng là rất quan trọng vì nó đảm bảo rằng những thứ bạn đang làm hoạt động giống như mong đợi. Nó cung cấp cho bạn phản hồi nhanh chóng để từ đó chỉnh sửa code cho hợp lý hơn. Đây là một phần của việc phát triển ứng dụng. Ở kỹ thuật TDD, bạn thậm chí phải viết test trước khi viết các đoạn code cho ứng dụng. Những đoạn test cũng là một phần của ứng dụng nên bạn cũng cần quan tâm đến chúng, nó nên được viết dễ hiểu để chúng sẽ dễ dàng sửa nếu cần thay đổi trong tương lai. Một việc cũng rất quan trọng là bạn cần cô lập những đoạn test (chỉ test những gì thực sự quan trọng). Hãy cùng xem một vài ví dụ dưới đây để hiểu hơn về việc viết test.

2. Ví dụ Hãy theo dõi một vài ví dụ dưới đây, những đoạn code cần viết test sẽ không quá xa lạ mà có thể là những việc bạn đã từng làm. Khi thực hiện một tính năng mới, bạn nhận ra rằng các bộ phận của nó có thể được tái sử dụng và thay đổi nhiều lần theo thời gian. Giả sử chúng ta có 1 service sẽ chạy sau khi một User đăng ký thành công ứng dụng. Nó sẽ tạo ra một token bí mật và việc tạo ra token này không thực sự quan trọng. Đoạn code service như sau:

class User::Register
  def call
    User.create! email: email
    generate_super_secret_token email
  end

  private
  def generate_super_secret_token email
    # ...
  end
end

Đoạn test cho đoạn code ở trên có thể như sau:

it { expect { subject.call }.to change(User, :count).by(1) }
it { expect(subject.call).to eq("TOP SECRET") }

3. Vấn đề có thể xảy ra Code phía trên hoạt động khá tốt, tuy nhiên phần tạo ra token có thể tái sử dụng ở nhiều chỗ khác, vì vậy chúng ta nên chuyển logic của nó sang một class khác và thay đổi service ở trên một chút.

class User::Register
  def call
    User.create! email: email
    User::GenerateToken.call email
  end
end

Đối tượng User::Register có thể được tái sử dụng ở những chỗ khác khi cần thiết. Chúng ta có thể di chuyển đoạn code cũ sang class mới, với những thông số như cũ, test sẽ pass, và coi như là hoàn thành task. Tuy nhiên ở đây sẽ có một vấn đề nhỏ, mà ảnh hưởng trong tương lai. Các bạn sẽ thắc mắc xảy ra vấn đề gì khi mà các test đều pass, các kiểm tra đều đúng. Vậy có vấn đề gì ở đây?

4. Thay đổi việc tạo ra token Bây giờ chúng ta phải thay đổi thuật toán tạo token, chúng ta sẽ sửa đổi nó trong class User::Register. Và vấn đề xảy ra là các spec sử dụng class trên bị fail. Bạn phải sửa lại code ở mỗi spec sử dụng nó. Điều này đã vi phạm nguyên tắc các đoạn test của bạn không được cô lập, sửa chỗ này dính đến chết test ở chỗ khác. Vậy cách sửa sẽ như thế nào? Bạn hãy xem đoạn code đã sửa sau:

before { allow(User::GenerateToken).to receive(:call) { "TOP SECRET" } }

it { expect { subject.call }.to change(User, :count).by(1) }
it { expect(subject.call).to eq("TOP SECRET") }

Đây là một ví dụ đơn giản nhưng nó cho chúng ta thấy được 3 điều quan trọng:

  1. Nó đảm bảo chúng ta không cần sửa đoạn test này khi logic trong User::GenerateToken thay đổi.
  2. Nó cô lập các đoạn test. Tức là đoạn test này chỉ quan tâm đến class User::Register, việc sửa class khác không ảnh hưởng đến nó.
  3. Khi method User::GenerateToken.call được gọi, nó thậm chí không chạy vào code mà lập tức trả về giá trị là "TOP SECRET", điều này sẽ cải thiện được tốc độ chạy test.

Đây chỉ là một ví dụ nhỏ nhưng nó thật sự quan trọng để nhìn vào và hiểu được cách cô lập các đối tượng khi viết test.

5. Kết luận Mong rằng bài viết sẽ hữu ích với mọi người. Thankyou!