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

Add a Comment

Scroll Up