Một số lưu ý khi viết test cho Rails
Bài đăng này đã không được cập nhật trong 2 năm
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ả dog1 và user_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
All rights reserved