+3

Một số lưu ý khi viết test cho Rails

1. Giới thiệu

Khi mình đọc lại test case của dự án, có khá nhiều đoạn code dài và rối mắt khi đọc dẫn đến việc bạn phải tốn nhiều thời gian cho việc đọc hiểu code. Hơn thế nữa, việc viết test không theo một quy chuẩn sẽ kiến bạn trở nên lúng túng, không biết sẽ triển khai thêm code test mới ở đâu. Vì thế, mình viết bài này nhắm mục đích giúp các bạn có thêm những lựa chọn hữu ích khi viết code, giúp code trở nên trong sáng, sạch đẹp hơn.

2. Code style

A. Controller

Với controller, việc sắp xếp theo thứ tự method như dưới đây sẽ giúp bạn dễ dàng tìm kiếm, đọc hiểu code hơn.

index
create
show
update
destroy
other actions
private methods

B. Models

Với model

  • Sắp xếp theo bảng chữ cái.
  • Sắp xếp theo thứ tự, ví dụ
class MyModel < ApplicationRecord
  include DateTime

  CONSTANT = ...

  attr_accessor :name

  belongs_to :animal

  has_one :dog

  has_many :feature

  accepts_nested_attributes_for :foods

  delegate

  validates :name

  enum category: { pug: 0, hasky: 1 }

  after_destroy :destroy_feature

  scope :first_scope

  class << self
    def method1; end

    def method2; end
  end

  def instance_method1; end

  private

  def do_something; end
end

3. Spec, test case

Sau đây là một số lưu ý khi viết test :

1. Viết test case method theo thứ tự như trong models, controller sẽ giúp bạn dễ dàng tìm kiếm và viết thêm mã

RSpec.describe MyModel, type: :model do
  describe 'associations' do; end

  describe 'validations' do; end

  describe 'enums' do; end

  describe 'callbacks' do
    describe 'method1' do; end

    describe 'method2' do; end
  end
  
  ...
end

2. Đưa subject lên đầu method sẽ giúp bạn clear về nội dung viết test

describe 'delete_extended_object' do
  subject { parent.destroy }

  ..
end

3. Chỉ khai báo dữ liệu dùng đến trong blocks

BAD
  let!(:animal) { create :animal, type: 'dog'}
  let!(:dog1) { create :dog, name: 'abc', age: 2, type: :husky, parent_object: animal }
  let!(:user_login) { create :user, name: 'user1', is_login: false }
  
  let(:return_object1) do
    {
      animal: { id: 1, type: 'dog' }
    }
  end
  let(:return_object2) do
    {
      user: { id: 1, name: 'user1' },
      pet: { id: 1, name: 'abc' }
    }
  end

  context 'action 1' do
    it { expect(response.body).to eq return_object1 }
  end

  context 'action 2' do
    it { expect(response.body).to eq return_object2 }
  end

Ví dụ, khối context1 chỉ sử dụng dữ liệu animal, nhưng lại tạo dữ liệu cho cả dog1user_login. Tất nhiên, việc viết mã như trên không có gì sai. Tuy nhiên khối mã trên sẽ tốn thêm thời gian để tạo dữ liệu dư thừa mà không sử dụng đến.

GOOD
let!(:animal) { create :animal, type: 'dog'}

context 'action 1' do
  let(:return_object1) do
    {
      animal: { id: 1, type: 'dog' }
    }
  end

  it { expect(response.body).to eq return_object1 }
end

context 'action 2' do
  let!(:dog1) { create :dog, name: 'abc', age: 2, type: :husky, parent_object: animal }
  let!(:user_login) { create :user, name: 'user1', is_login: false }
  
  let(:return_object2) do
    {
      user: { id: 1, name: 'user1' },
      pet: { id: 1, name: 'abc' }
    }
  end

  it { expect(response.body).to eq return_object2 }
end

4. Tránh việc gọi method, truy vấn trong response trả về

Không nên

BAD
  describe 'PATCH /api/v1/objects' do
    let!(:animal) { create :animal, type: 'dog'}
  
    context 'action 1' do
      let(:return_object1) do
        {
          animal: {
            id: 1,
            type: animal.type,
            feature: animal.method_feature 
          }
        }
      end
  
      it { expect(response.body).to eq return_object1 }
    end
  end

Khi gọi đến thuộc tính, method sẽ làm tăng thời gian chạy test case. Không những thế, việc xem lại test case sẽ tốn nhiều thời gian hơn, như phải tìm thuộc tính method sẽ trả ra giá trị nào.

GOOD
describe 'PATCH /api/v1/objects' do
  let!(:animal) { create :animal, type: 'dog'}

  context 'action 1' do
    let(:return_object1) do
      {
        animal: {
          id: 1,
          type: 'dog',
          feature: 'rescue'
        }
      }
    end

    it { expect(response.body).to eq return_object1 }
  end
end

5. Định hình cấu trúc trả về để viết test hiệu quả, tránh việc bỏ sót

Khi viết test chúng ta nên định hình các cấu trúc sẽ được trả về, phân chia chúng thành các case . Ví dụ các test case thường sẽ bắt đầu bằng expect nil, sau đó sẽ đến cách case khác.

  context 'if return value is blank' do
    it 'return nil' do; end

    it 'return empty array' do; end
  end

  context 'if return value is present ' do
    it 'return integer' do; end

    it 'return array' do; end
  end

Tham khảo thêm


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.