Sử dụng Cucumber để viết unit test trong Rails 3.2
Giới thiệu
Ngày nay xu hướng phổ biến của các chuẩn mới về sản xuất phần mềm là yêu cầu các developers viết test unit khi cài đặt source code. Đối với những hệ thống phát triển dựa trên framework Ruby on rails thì có rất nhiều gem có thể hỗ trợ việc viết test unit: RSPEC, CUCUMBER, CAPYBARA… Hôm nay tôi xin giới thiệu một trong những công cụ phổ biến và hữu dụng được sử dụng để viết Unit test có một cái tên rất đặc biệt “cucumber – dưa chuột”. Dưới đây là những nội dung sẽ được trình bày trong bài viết này:
1. Cucumber là gì?
2. Ngôn ngữ Gherkin.
3. Hướng dẫn cài đặt Cucumber trên rails 3.2
4. Demo
Nội dung
I – Cucumber là gì?
Cucumber là một công cụ kiểm thử tự động dựa trên việc thực thi các functions được mô tả dướng dạng plain-text, mục đích là để support cho việc viết Behavior Driven Development (BDD) của các developers. Điều này có nghĩa rằng kịch bản test unit (scenarios) sẽ được viết trước và thể hiện nghiệp vụ, sau đó source code mới được cài đặt để pass qua tất cả các stories đó.
Ngôn ngữ được cucumber sử dụng là “Gherkin” (sẽ giới thiệu vào mục II). Bản chất Cucumber được viết bằng Ruby nhưng chúng ta có thể sử dụng để test code được viết bằng Ruby, Java, C# và Python. Yêu cầu khi viết Cucumber là chúng ta có một chút ít hiểu biết về ngôn ngữ Ruby cơ bản.
II – Ngôn ngữ Gherkin.
Gherkin dịch sang tiếng Việt là “giống dưa chuột”. Hiểu đơn giản là cucumber sử dụng ngôn ngữ Gherkin cũng giống như Dưa chuột thì sử dụng ngôn ngữ của giống dưa chuột để trao đổi thông tin :D. Hay nói tổng quát hơn Gherkin là ngôn ngữ mà Cucumber có thể hiểu được.
Gherkin là một ngôn ngữ thể hiện nghiệp vụ và có miền ngữ nghĩa xác định giúp cho người đọc có thể hiểu được kịch bản và hành động mà không cần biết chi tiết chúng được cài đặt như thế nào.
Có 2 quy tắc khi viết Gherkin:
(*) Một file Gherkin chỉ mô tả cho một feature.
(*) Source file Gherkin là .feature
(**) Ngữ pháp của Gherkin
– Giống như Python và YAML, Gherkin là một ngôn ngữ kịch bản được sử dụng để định nghĩa logic theo cấu trúc. Cũng giống như Ruby, nên thay thế kí tự tab bằng các kí tự space, dòng comment sẽ có kí tự # ở đầu dòng.
– Bắt đầu một file sẽ là Feature, sau đó đến scenarios và steps. Khi chúng ta chạy file source “.feature” mỗi step sẽ match với một Ruby code block được định nghĩa sẵn trước đó gọi là “Step Definitions”.
– Một file feature được viết bằng Gherkin như sau:
1: Feature: Some terse yet descriptive text of what is desired 2: In order to realize a named business value 3: As an explicit system actor 4: I want to gain some beneficial outcome which furthers the goal 5: 6: Scenario: Some determinable business situation 7: Given some precondition 8: And some other precondition 9: When some action by the actor 10: And some other action 11: And yet another action 12: Then some testable outcome is achieved 13: And something else we can check happens too 14: 15: Scenario: A different situation 16: ...
III – Hướng dẫn cài đặt và cấu hình Cucumber trên rails 3.2
1. Install cucumber gem trên ứng dụng bằng cách chạy câu lệnh:
gem install cucumber
2. Thêm các gem sau vào Gemfile:
group :development, :test do gem "rspec-rails" gem 'cucumber-rails' gem 'database_cleaner' end
– Sau đó chạy bundle install và rails g cucumber:install
– Khi cài đặt thành công sẽ sinh ra thư mục features chứa 2 thư mục con support (chứa file cấu hình env.rb và paths.rb) và step_definitions(sẽ chứa các Ruby code block thực thi các step).
– Lưu ý: Trong file features/support/env.rb có dòng code: “DatabaseCleaner.strategy = :transaction” có nghĩa là trước khi chạy cucumber thì sẽ thực hiện truncate toàn bộ dữ liệu trong DB. Vì vậy bạn nên tạo 1 DB test riêng để chạy Cucumber, không chung DB với development hay production.
Ok. như vậy là việc cài đặt và cấu hình Cucumber đã thành công. Sau đây sẽ là demo viết test cho chức năng login.
IV – Demo
1. Tạo file features/step_definitions/navigations_steps.rb với nội dung như sau:
require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) Given /^I am on (.+)$/ do |page_name| visit path_to(page_name) end When /^I go to (.+)$/ do |page_name| visit path_to(page_name) end When /^I press "([^\"]*)"$/ do |button| click_button(button) end When /^I click "([^\"]*)"$/ do |link| click_link(link) end When /^I fill in "([^\"]*)" with "([^\"]*)"$/ do |field, value| fill_in(field.gsub(' ', '_'), :with => value) end When /^I fill in "([^\"]*)" for "([^\"]*)"$/ do |value, field| fill_in(field.gsub(' ', '_'), :with => value) end When /^I fill in the following:$/ do |fields| fields.rows_hash.each do |name, value| When %{I fill in "#{name}" with "#{value}"} end end When /^I select "([^\"]*)" from "([^\"]*)"$/ do |value, field| select(value, :from => field) end When /^I check "([^\"]*)"$/ do |field| check(field) end When /^I uncheck "([^\"]*)"$/ do |field| uncheck(field) end When /^I choose "([^\"]*)"$/ do |field| choose(field) end Then /^I should see "([^\"]*)"$/ do |text| page.should have_content(text) end Then /^I should see \/([^\/]*)\/$/ do |regexp| regexp = Regexp.new(regexp) page.should have_content(regexp) end Then /^I should not see "([^\"]*)"$/ do |text| page.should_not have_content(text) end Then /^I should not see \/([^\/]*)\/$/ do |regexp| regexp = Regexp.new(regexp) page.should_not have_content(regexp) end Then /^the "([^\"]*)" field should contain "([^\"]*)"$/ do |field, value| find_field(field).value.should =~ /#{value}/ end Then /^the "([^\"]*)" field should not contain "([^\"]*)"$/ do |field, value| find_field(field).value.should_not =~ /#{value}/ end Then /^the "([^\"]*)" checkbox should be checked$/ do |label| find_field(label).should be_checked end Then /^the "([^\"]*)" checkbox should not be checked$/ do |label| find_field(label).should_not be_checked end Then /^I should be on (.+)$/ do |page_name| current_path.should == path_to(page_name) end Then /^page should have (.+) message "([^\"]*)"$/ do |type, text| page.has_css?("p.#{type}", :text => text, :visible => true) end Given /^I am a (.+)$/ do |user| FactoryGirl.create(user.to_sym) end
2. Tạo file features/contact.feature với nội dung:
Feature: Login form Input data to form click submit button Scenario: Sends a contact message Given I am a user_super Given I am on the signin page When I fill in "session[email]" with "super@septeni-technology.jp" When I fill in "session[password]" with "xxxxxxxxxx" When I press "Login" Then I should be on the clients page
– Trong đó:
+ Given I am a user_super: đăng nhập với tài khoản super (tham khảo Given /^I am a (.+)$/ trong file navigation_steps.rb).
+ Given I am on the signin page: access tới trang login (tham khảo Given /^I am on (.+)$/ trong file navigation_steps.rb).
+ session[email]: tên của email tag ( tham khảo When /^I fill in “([^\”]*)” with “([^\”]*)”$/).
+ session[password]: tên của password tag ( tham khảo When /^I fill in “([^\”]*)” with “([^\”]*)”$/).
+ When I press “Login”: mô tả sự kiện click vào nút có value=”Login” (tham khảo When /^I press “([^\”]*)”$/ ).
+ Then I should be on the clients page: đăng nhập thành công và chuyên đến trang clients ( tham khảo Then /^I should be on (.+)$/).
3. chạy “bundle exec cucumber” và kết quả:
1 scenario (1 passed)
6 steps (6 passed)
0m0.903s