Trong bài Unit Testing chúng ta đã tìm hiểu cách kiểm tra code đối với model, trong phần này chúng ta sẽ tìm hiểu cách kiểm tra code trên controller – hay còn gọi là Functional Testing.
Khi chúng ta tạo một controller thì Rails sẽ tự động tạo cho chúng ta một file dùng để test controller này, trong bài Routing chúng ta đã tạo một controller là store,
và Rails sẽ tạo một file test tương ứng có tên là store_controller_test.rb
trong thư mục test/controllers.
Ngoài ra nếu bạn còn nhớ thì khi tạo một model, Rails cũng sẽ định nghĩa controller cho model này, và dĩ nhiên Rails cũng sẽ tạo một file test cho controller này, chúng ta đã tạo model Product
và file test controller là file products_controller_test.rb
cũng nằm trong thư mục test/controllers.
Nội dung của file products_controller_test.rb
như sau:
require 'test_helper' class ProductsControllerTest < ActionController::TestCase setup do @product = products(:one) end test "should get index" do get :index assert_response :success assert_not_nil assigns(:products) end test "should get new" do get :new assert_response :success end test "should create product" do assert_difference('Product.count') do post :create, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } end assert_redirected_to product_path(assigns(:product)) end test "should show product" do get :show, id: @product assert_response :success end test "should get edit" do get :edit, id: @product assert_response :success end test "should update product" do patch :update, id: @product, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } assert_redirected_to product_path(assigns(:product)) end test "should destroy product" do assert_difference('Product.count', -1) do delete :destroy, id: @product end assert_redirected_to products_path end end
Trong đó đoạn setup do...end
sẽ làm công việc khởi tạo, ở đây Rails khởi tạo biến @product
là đối tượng one
trong fixture.
Sau đó là các đoạn code test, ở đây các phương thức get, post, patch
sẽ gửi các gói tin HTTP lên server, theo sau là tên các hàm trong model mà Rails đã định nghĩa trước, như index
là phương thức index
trong controller, các phương thức gửi sẽ tương ứng với một phương thức của giao thức HTTP như GET, POST, PATCH, PUT… sau đó chúng ta dùng các phương thức assert
để kiểm tra dữ liệu trả về từ server, trong đó:
assert_response:
nhận vào tham số mã và kiểm tra xem gói tin trả về có mã giống với tham số hay không, ở đoạn code trên các đoạnassert_response
đều kiểm tra với mã là:success
, tương ứng với mã từ 200-299, ngoài ra còn có:redirect
tương ứng với mã từ 300-399,:missing
là mã 404,:error
là từ 500-599.assert_not_nil:
nhận vào một đối tượng và kiểm tra xem đối tượng đó có giá trị lànil
hay khôngassert_difference
: nhận vào tham số là một biểu thức (phép tính, hàm, toán tử…v.v) và tính với hiệu của biểu thức đó sau khi thực hiện phần code phía sau, rồi kiểm tra xem hiệu đó có bằng 1 hay không.assert_redirected_to
: nhận vào tham số là một URL, kiểm tra xem URL đó có giống với hành động chuyển hướng trang web cuối cùng được thực hiện hay không.
ProductsControllerTest
Bạn có thể chạy lệnh rake test:functionals
để Rails chạy những test trên. Và Bạn sẽ nhận được 2 lỗi tại test "should create product"
và "should update product".
Đối với
"should create product"
thì lý do là vì bị trùng thuộc tính title,
chúng ta đã quy định thuộc tính này phải là duy nhất, nhưng Rails lại lấy dữ liệu từ fixture để chèn vào CSDL nên mới bị trùng, và Rails lại không kiểm tra trường hợp bị trùng, chúng ta có thể sửa lại như sau:
require 'test_helper' class ProductsControllerTest < ActionController::TestCase . . . test "should create product" do assert_difference('Product.count', 0) do post :create, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } end assert_difference('Product.count') do post :create, product: { :description => 'Some new book', :image_url => 'some_img.jpg', :price => 59.99, :title => 'Some new title' } end assert_redirected_to product_path(assigns(:product)) end . . . end
Đối với trường hợp bị trùng, chúng ta truyền thêm tham số thứ 2 vào phương thức assert_difference,
đây là tham số hiệu, như đã nói ở trên mặc định phương thức này kiểm tra hiệu bằng 1, ở đây chúng ta kiểm tra hiệu bằng 0, tức là nếu không chèn thêm bản ghi mới vào thì số lượng các bản ghi không đổi, còn với trường hợp chèn thành công thì chúng ta kiểm tra một yêu cầu redirect có tồn tại không.
Tương tự, "should update product"
cũng bị 2 lỗi vì Rails chưa kiểm tra trường hợp cập nhật thất bại, nếu bạn còn nhớ thì trong Fixtures có 2 đối tượng dữ liệu mẫu có title
giống nhau, và cả 2 đều có thuộc tính image_url
không có đuôi hợp lệ, chúng ta sửa lại như sau:
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: MyString description: MyText price: 9.99 image_url: MyString two: title: MyString2 description: MyText price: 9.99 image_url: MyString.jpg ruby: title: Ruby on Rails 4 description: Zzzzzzz.... price: 9.99 image_url: img.jpg
Chúng ta sửa dữ liệu của two
sao cho hợp lệ, còn one
thì giữ nguyên. Tiếp theo chúng ta sửa đoạn code test như sau:
class ProductsControllerTest < ActionController::TestCase . . . test "should update product" do patch :update, id: @product, product: { description: @product.description, image_url: @product.image_url, price: @product.price, title: @product.title } assert_response 200 @product = products(:two) patch :update, id: @product, product: { :description => @product.description, :image_url => @product.image_url, :price => @product.price, :title => @product.title } assert_redirected_to product_path(assigns(:product)) end . . . end
Đầu tiên chúng ta kiểm tra đối với fixture mặc định được tạo trong phần setup
là one,
đây là dữ liệu không hợp lệ, Rails sẽ trả về response có thông báo lỗi, chú ý là response ở đây mang theo một thuộc tính thông báo lỗi, còn bản thân response lại là một gói tin HTTP được gửi thành công, nên chúng ta mới so sánh với mã trạng thái là 200. Sau đó chúng ta đổi lại biến @product
lấy giá trị của fixture two,
đây là fixture hợp lệ, khi cập nhật xong thì trang web của chúng ta sẽ được chuyển đến đường dẫn /products/<id>,
do đó chúng ta kiểm tra với phương thức assert_redirected_to
.
StoreControllerTest
Đối với controller store
chỉ có một phương thức index
thì có file test như sau:
require 'test_helper' class StoreControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success end end
Ở đây Rails chỉ kiểm tra xem khi trỏ đến hàm index
này thì có nhận được một gói tin trả về với mã từ 200-299 (thành công – success) hay không.
Chúng ta kiểm tra thêm một số thứ bằng cách sửa lại như sau:
require 'test_helper' class StoreControllerTest < ActionController::TestCase test "should get index" do get :index assert_response :success assert_select '#columns #side a', :minimum => 4 assert_select '#main .entry', 3 assert_select 'h3', 'Ruby on Rails 4' assert_select '.price', /\$[,\d]+\.\d\d/ end end
Phương thức assert_select
sẽ kiểm tra xem trong nội dung HTML trả về có chứa những thành phần mà chúng ta quy định hay không.
Chẳng hạn như assert_select '#columns #side a'
, tức là kiểm tra xem trong nội dung có thẻ nào có id là columns
hay không, và trong đó phải có một thẻ có id là side,
rồi phải có thẻ <a>
, ngoài ra thuộc tính minimum
còn yêu cầu là phải có ít nhất 4 thẻ <a>
.
Dòng assert_select '#main .entry', 3
yêu cầu nội dung trả về phải có một thẻ có id là main,
trong đó có 3 thẻ có class là entry
.
Dòng assert_select 'h3', 'Ruby on Rails 4'
kiểm tra xem có thẻ <h3>
nào có nội dung là Ruby on Rails
4
hay không.
Cuối cùng dòng assert_select '.price', /\$[,\d]+\.\d\d/
kiểm tra xem thẻ nào mà có class là price
có nội dung khớp với đoạn biểu thức chính quy hay không, ở đây đoạn biểu thức chính quy này mô tả số tiền, có ký tự $, có 2 chữ số sau phần thập phân.
Bạn có thể chạy lệnh rake test:functionals
để Rails kiểm tra tất cả các đoạn test trên. Có tổng cộng 8 đoạn test trong 2 lớp StoreControllerTest
và ProductsControllerTest.
C:\Project\Rails\depot>rake test:functionals Run options: --seed 46620 # Running: ........ Finished in 0.874736s, 9.1456 runs/s, 21.7208 assertions/s 8 runs, 19 assertions, 0 failures, 0 errors, 0 skips