Sử dụng Stubs khi viết Rspec trong Rails
Bài đăng này đã không được cập nhật trong 7 năm
1. Giới thiệu
RSpec là một công cụ test vô cùng mạnh mẽ đồng thời cung cấp nhiều tính năng phong phú. Một trong số đó là khả năng stub
một phương thức của một đối tượng hoặc một class. Thay vì phải thực thi một hàm một cách bình thường, stub
sẽ trả về một giá trị cứng và không bao giờ thực sự chạy phương thức đó.
2. Ví dụ
student = load_student_from_database(id: 77)
allow(student).to receive(:grades).and_return(['A', 'A+', 'C'])
puts(student.grades)
Ở dòng thứ 2 có ý nghĩa rằng khi gọi student.grades
thì sẽ trả về một array với giá trị là ['A', 'A+', 'C']. Nhưng trên thực thế phương thức grades
không bao giờ được gọi.
Tiếp tục xây dựng ví dụ trên:
Thử tưởng tượng rằng tồn tại một class Student và class GpaCalculator và chúng ta phải viết test cho những class này.
Class GpaCalculator được dùng để tham chiếu điểm. Ví dụ A tương đương với 4.0 và B tương đương với 3.0. Ta có thể viết test như sau:
describe GpaCalculator do
it “calculates one A to be 4.0” do
student = Student.new
allow(student).to receive(:grades).and_return([“A”])
expect(GpaCalculator.new(student).calculate).to eq(4.0)
end
it “calculates one B to be 3.0” do
student = Student.new
allow(student).to receive(:grades).and_return([“B”])
expect(GpaCalculator.new(student).calculate).to eq(3.0)
end
it “calculates one A and one B to be 3.5” do
student = Student.new
allow(student).to receive(:grades).and_return([“A”, “B”])
expect(GpaCalculator.new(student).calculate).to eq(3.5)
end
end
Bạn có thể làm gọn code ở đây một chút bằng cách:
describe GpaCalculator do
let(:student) { Student.new }
subject { GpaCalculator.new(student) }
it "calculates one A to be 4.0" do
allow(student).to receive(:grades).and_return(["A"])
expect(subject.calculate).to eq(4.0)
end
it "calculates one B to be 3.0" do
allow(student).to receive(:grades).and_return(["B"])
expect(subject.calculate).to eq(3.0)
end
it "calculates one A and one B to be 3.5" do
allow(student).to receive(:grades).and_return(["A", "B"])
expect(subject.calculate).to eq(3.5)
end
end
Nhiều người sẽ dừng lại ở đây và tiếp tục viết test cho những class khác. Với một class nhỏ không phức tạp thì cách trên có vẻ không có vấn đề gì. Nhưng nếu trên một class lớn với những rspec phức tạp thì việc refactor code triệt để là một vấn đề lớn giúp cho việc bảo trì trở nên dễ dàng hơn.
Trong trường hợp này, bạn sử stub
cùng một phương thức nhiều lần và trả về một array khác nhau trong mỗi test. Có một kỹ thuật để giúp viết gọn và dễ đọc hơn.
describe GpaCalculator do
let(:grades) { [] }
let(:student) { Student.new }
subject { GpaCalculator.new(student) }
before :each do
allow(student).to receive(:grades).and_return(grades)
end
it "calculates one A to be 4.0" do
grades << "A"
expect(subject.calculate).to eq(4.0)
end
it "calculates one B to be 3.0" do
grades << "B"
expect(subject.calculate).to eq(3.0)
end
it "calculates one A and one B to be 3.5" do
grades << "A"
grades << "B"
expect(subject.calculate).to eq(3.5)
end
end
Chúng ta chuyển đoạn stub
tách ra khỏi mỗi bài test và đặt vào before
. Nhiều người có thể không thích cách viết block before nên còn một cách nữa là đưa stub và let
:
let(:student) {
student = Student.new
allow(student).to receive(:grades).and_return(grades)
student
}
Một cách hoàn toàn khách nữa đó là bạn sử dụng double
thay cho allow
. Ví dụ:
let(:student) { instance_double('Student', grades: grades) }
Và bạn có phiên bản hoàn chỉnh như bên dưới :
describe GpaCalculator do
let(:grades) { [] }
let(:student) { instance_double("Student", grades: grades) }
subject { GpaCalculator.new(student) }
it "calculates one A to be 4.0" do
grades << "A"
expect(subject.calculate).to eq(4.0)
end
it "calculates one B to be 3.0" do
grades << "B"
expect(subject.calculate).to eq(3.0)
end
it "calculates one A and one B to be 3.5" do
grades << "A"
grades << "B"
expect(subject.calculate).to eq(3.5)
end
end
3. Tổng kết
Mặc dù các đoạn mã RSpec không phải là các đoạn mã để chạy chương trình nhưng vẫn hết sức quan trọng đối với project của bạn. Nếu RSpec khó đọc, khó sửa đổi thì khi mở rộng hệ thống sẽ bị ảnh hưởng. Vì vậy hãy cố gắng refactor và làm cho chúng dễ đọc, dễ hiểu nhất có thể.
All rights reserved