Trong bài viết này, chúng ta sẽ tìm hiểu system test trong Rails 6. System test dùng để tự động kiểm tra cách user tương tác với ứng dụng, bao gồm cả JavaScript trong giao diện. Minitest, framework test mặc định của Rails, rất phù hợp cho system testing. Với mọi cấu hình Rails lo liệu, chỉ cần vài bước để có test đầu tiên chạy được.
System test được thêm vào Rails stack từ phiên bản 5.1. Khi tôi bắt đầu dùng, tôi thấy khó tìm thông tin cập nhật, không liên quan đến RSpec. Đây là tất cả kiến thức mới nhất về system test với Minitest.
Giới thiệu System Test
Trong thuật ngữ Rails, system testing là "kiểm tra ứng dụng như một hệ thống toàn thể". Đó là dùng browser trong test. Thay vì test từng phần riêng lẻ, system test cho phép test toàn bộ "workflow", giống như user tương tác với app, bao gồm phần JavaScript. Thực tế, chúng ta không muốn system test kiểm tra có record được tạo trong database khi user click nút; chỉ test record đó có xuất hiện trên màn hình không. Loại test tương tác user này còn gọi feature test hoặc acceptance test. Chúng khác integration test: integration test kiểm tra hành vi, đặc biệt của tất cả phần app cùng nhau, nhưng không qua giao diện user.
Cấu hình
Cấu hình đơn giản đến mức gần như gây nhầm lẫn.
Gem Capybara dùng để tương tác với browser. Thông qua Capybara, test có thể visit page, fill form, click link và button. Nếu đã làm việc với Capybara, bạn biết phải phối hợp nhiều thứ để làm nó chạy với database cleaning strategy và browser config. May mắn là Rails system test setup lo tất cả cấu hình cần thiết để làm việc với Capybara out-of-the-box. Chúng ta tập trung viết test. Mặc định dùng Selenium driver.
Từ docs: "Mặc định, ActionDispatch::SystemTestCase driven bởi Selenium driver, với Chrome browser, và browser size 1400x1400." Selenium driver khác với default của Capybara và được chọn vì hỗ trợ JavaScript.
Tất cả setting có sẵn trong application_system_test_case.rb. Chỉ cần require file này trong mỗi file test.
# application_system_test_case.rb (default)
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
end
Nếu generate app mới với Rails 6, đó là tất cả bạn cần. Tức là: có thể bỏ qua mọi lời khuyên setup Selenium driver cho UI test hoặc feature test của RSpec.
Trong app hiện có, generate scaffold sẽ tự động generate application_system_test_case.rb và mọi thứ cần cho system test.
Khi Eileen Uchitelle giới thiệu system test, Chrome được chọn hơn Firefox vì lúc đó FF không chơi tốt với Selenium. Vấn đề đó đã fix ở các version Firefox sau. Tôi thay Chrome bằng FF:
# application_system_test_case.rb (đổi driver sang Firefox)
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :firefox, screen_size: [1400, 1400]
end
(Ban đầu tôi chạy $ rails db:test:prepare sau khi đổi driver để đảm bảo khởi động sạch, NHƯNG test runner mới lo việc đó: invoke test sẽ autorun :prepare.)
Mới trong Rails 6
Kể từ khi system test ra mắt ở 5.1, gem chromedriver-helper đã deprecated. Trong app Rails 6 mới, nó được thay bằng gem webdrivers. Trong app Rails 5, nên replace chromedriver-helper bằng webdrivers nếu chưa làm. Với webdrivers, bạn không cần gem selenium-webdriver; webdrivers lo cả nó. Thú vị: Rails 6 cung cấp cả selenium-webdriver và webdrivers gem.
Kiểm tra sự suy ra: Những gì bạn KHÔNG cần nữa
Setup Capybara trước đây rất đau đầu. Không rõ cần gì để setup từ đầu, kể cả tìm database cleaning strategy đúng, dependency của capybara-webkit gem và các yêu cầu bất ngờ khác. Bây giờ Rails cung cấp mọi thứ, phần gây nhầm lẫn duy nhất là thông tin lỗi thời trong guides và docs. Dưới đây là những gì có thể bỏ qua.
Thứ nhất, Rails setup sẵn có nghĩa là có thể bỏ qua nhiều gem thường nhắc đến về test, như minitest-rails-capybara, hoặc extras cho Minitest: colored output, fast failing tests, chạy một line/test. Các tính năng Minitest này có trong test runner giới thiệu từ Rails 5.0.
Không cần gem database_cleaner nữa; Rails tự cleanup (transactional tests).
Vì Rails dùng Selenium thay vì Capybara default, không cần capybara-webkit và không cần extra để include test JavaScript element.
Bạn thậm chí không cần dùng save_and_open_screenshot của Capybara, vì Rails cung cấp method take_screenshot. Nó lưu screenshot ở /tmp và cung cấp link trong test output để truy cập dễ dàng.
Chọn giữa Browser Thật và Headless Browser
Tôi không bao giờ chán xem system test chạy trong browser, thấy link được click, form được fill. Nhưng nó chậm. Để tăng tốc test run, có thể dùng 'headless' browser: browser có cùng quyền truy cập app như browser thường, nhưng không có GUI. Nghĩa là: nó hoạt động giống hệt nhưng bạn không thấy test chạy vì headless driver không mở cửa sổ browser thực.
Nếu muốn headless, có headless_chrome và headless_firefox. Để dùng, chỉ cần thay đổi nhỏ:
# Đổi driver sang headless_*
# application_system_test_case.rb
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_firefox
end
Lưu ý: Trong Rails docs và nhiều bài viết, Poltergeist được nhắc như option driver. Nó từng phổ biến là headless PhantomJS driver cho Capybara. PhantomJS đã bị abandon. Vì vậy không cần đi sâu - lý do tôi nhắc ở đây là Poltergeist và PhantomJS vẫn được nhắc trong Rails docs.
Có thêm customization nhưng để bắt đầu, trên là đủ.
Chạy Test
$ rails test chạy tất cả test trừ system test. Cần chạy explicit $ rails test:system. (Thú vị: command $ rails luôn chạy qua bin/rails. Không cần gõ $ bin/rails nữa.)
- Chạy tất cả system test:
$ rails test:system - Chạy test trong file cụ thể (ví dụ: users_test.rb)
$ rails test/system/users_test - ... hoặc một test cụ thể:
$ rails test test/system/users_test.rb:21 - Chạy tất cả test: chạy system test trước:
$ rails test:system test
Lưu ý: option flags không hoạt động với test:system; nếu muốn dùng flags như -f (fail fast) hoặc -v (verbose), dùng $ rails test test/system -v -f
Test Cái Gì (và Không Test Cái Gì)
System test bổ sung cho unit test, không thay thế. Chỉ cần test happy path, thêm một path với error message hoặc redirect. System test không dành để test mọi edge case trong browser; chỉ cover main feature.
Khi chọn test subject, tôi tìm entity phản ánh cách user dùng app. Về đặt tên test, tôi mượn convention của GitLab: ROLE_ACTION_test.rb, phù hợp với cách tiếp cận này. Ví dụ: user_shares_card_test.rb.
Mẹo và Thủ thuật Chung
- Nếu dùng Devise cho authentication, có thể dùng Devise integration helpers để login/logout test user. Thêm
include Devise::Test::IntegrationHelpersvào test class, hoặc vàotest_helper.rbđể dùng cho tất cả test. Bây giờ có thể tạo user vàsign_in(@user). Nếu để trong methodsetup, không cần logout user trong teardown. Rails lo cleanup những gì trong setup. - Làm việc với form, dễ bị cám dỗ reference id Rails tự tạo từ model name + field name và button type cho
click_on(fill_in "user_email",click_on :commit). Nhưng vì system test về những gì user thấy trên màn hình, nên dùng element hiển thị, ví dụ text. Option hợp lý: dùng reference key trong i18n locale file và dùng key đó thay vì literal text thay đổi. (fill_in :user_email). Capybara tìm text chỉ với full I18n syntax:assert_selector "h1", text: I18n.t("activerecord.models.things"). - Path helpers included mặc định. Với một số thư viện cần include helpers vào test class, ví dụ
include ActionMailer::TestHelper, để dùng methodassert_emails. - Tạo custom class để chạy test trên nhiều screen size. Xem guides.
- Tính năng screenshot cũng dùng để chụp ảnh cho documentation và promotional material. Nếu làm screencast và chậm lại playback speed, bạn có product video trong vài phút!
- Rails cung cấp generator cho system test.
- Minitest trong Rails hơi khác Minitest thuần, và thêm method/assertion đặc thù Rails. Xem Rails docs trước, rồi mới Minitest docs.
Tôi vui vẻ với system test, và phần lớn nhờ dễ dàng dùng out-of-the-box.
Kết luận
Trong bài này, chúng ta xem cấu hình Rails cung cấp out-of-the-box và customization tối thiểu có thể thêm. Minitest tuyệt vời cho System test. Vài mẹo giúp system test đầu tiên chạy được. Hãy xem chúng làm phép màu trong browser!