BEHAVIOR DRIVEN DEVELOPMENT VỚI BEHAT & MINK

Trước khi bắt đầu bài viết, chúng ta sẽ đi qua 2 khái niệm cơ bản cần nắm rõ:

1. TDD là gì

Test Driven Development (viết tắt: TDD) là một mô hình sofware development mà chủ yếu hướng tới quá trình kiểm thử, hay nói cách khác là sự phát triển dựa trên kiểm thử ! Trong đó, khi một yêu cầu phần mềm (requirement) được đặt ra: Người developer soạn thảo kịch bản kiểm thử (test case) cho yêu cầu đó trước tiên và chạy thử kịch bản đó lần đầu tiên. Hiển nhiên, việc chạy thử sẽ đưa ra 1 kết quả thất bại vì hiện tại chức năng đó chưa được xây dựng (và thông qua kết quả đó, ta cũng kiểm tra được là kịch bản kiểm thử đó được viết đúng). Theo đó, dựa vào mong muốn (expectation) của kịch bản kia, người developer sẽ xây dựng một lượng mã nguồn (source code) vừa đủ để lần chạy thứ 2 của kịch bản đó thành công. Nếu trong lần chạy thứ 2 vẫn đưa ra 1 kết quả thất bại, điều đó có nghĩa là thiết kế chưa ổn và người developer lại chỉnh sửa mã nguồn và chạy lại kịch bản đến khi thành công. Khi kịch bản kiểm thử được chạy thành công, người developer tiến hành chuẩn hóa đoạn mã nguồn (base-line code) và tiếp tục hồi quy với kịch bản kiểm thử tiếp theo. Việc chuẩn hóa bao gồm thêm các comment, loại bỏ các dư thừa, tối ưu các biến…

2. BDD là gì

Behaviour Driven Development (viết tắt: BDD) là một quá trình phát triển phần mềm dựa trên thử nghiệm hướng phát triển. BDD quy định rằng các developer và product owner cần hợp tác và xác định hành vi của người sử dụng. Như đã đề cập ở mục 1, TDD với việc cộng gộp vai trò cả của acceptance test vào developer dẫn tới phát sinh vấn đề quá tải cho người developer. Do đó BDD sinh ra, hướng tới các feature test mà người thực hiện là các Acceptance Tester.

  • Thay vì chờ đợi sản phẩm hoàn thành và kiểm thử, người tester/analyst tham gia vào quá trình xây dựng mã nguồn với vai trò phân tích và xây dựng hệ thống kịch bản kiểm thử dưới góc độ ngôn ngữ tự nhiên dễ hiểu từ các yêu cầu (requirement).

  • Đồng thời, họ giúp đỡ developer trong việc giải thích và đưa ra các phương án xây dựng mã nguồn mang tính thực tiễn với người dùng ngay trước khi bắt tay xây dựng.

  • Người developer liên hệ mật thiết với người tester và xây dựng mã nguồn với những phương án mà tester cung cấp theo mô hình TDD.

gg490346.Satrom_Figure2_hiresen-usMSDN.10-300x131.jpg

  • Kịch bản kiểm thử được phân chia làm 2 lớp: Lớp chấp nhận (feature/acceptance test) và Lớp đơn vị (unit test) .

  • Theo đó, kịch bản kiểm thử lớp đơn vị mang thuần tính thiết kế và phục vụ cho việc kiểm thử lớp đơn vị (Unit test) còn kịch bản kiểm thử lớp chấp nhận có thể được tái sử dụng cho quá trình kiểm thử hồi quy về sau (Regression Test)

Một ứng dụng phổ biến của BDD là cố gắng kế thừa TDD bằng cách tập trung chủ yếu vào việc tạo ra nhiều tests của Acceptance Tests hoặc chạy thử các đặc tả kỹ thuật. Mỗi đặc tả kỹ thuật tương ứng với đầu vào chu kỳ phát triển và mô tả, từ quan điểm của người dùng từng bước từng bước một, làm thế nào hệ thống hoạt động chính xác. Với một lần viết kịch bản nhưng các developer có thể sử dụng đặc điểm kỹ thuật và quá trình TDD sẵn có của họ để triển khai phát triển code đáp ứng đúng kịch bản test.

3. Bethat và Mink

  • Behat là một open source behavior-driven development framework dành cho PHP 5.3 và 5.4. Behat giúp developer hoặc tester viết ra các dòng lệnh theo ngôn ngữ tự nhiên miêu tả các tính năng của ứng dụng nhưng lại phục vụ cho việc kiểm thử dựa trên hành vi.
  • Hãy tưởng tượng bạn đang code 1 đoạn mã tương tự lệnh ls trong Unix, vậy với Behat hành vi thực hiện các lệnh và kết quả tương ứng đc miêu tả như sau:

Feature: ls

In order to see the directory structure As a UNIX user

I need to be able to list the current directory’s contents

Scenario:

Given I am in a directory “test”
And I have a file named “foo”
And I have a file named “bar”
When I run “ls”
Then I should get:
“””
bar
foo
“””

Lệnh ls như mô tả trong các kịch bản. Nếu đoạn code là đúng thì chúng ta sẽ có kết quả test pass, còn nếu sai thì sẽ có failure. Đó là chính xác những gì Behat có thể làm được !

Tất nhiên trên đây chỉ là 1 ví dụ đơn giản và để feature trên có thể chạy đc bạn cẩn phải tự define các behavior như: Given I am in a directory "test", … Bạn có thể sử dụng Behat để mô tả bất cứ điều gì mà bạn có thể mô tả trong business logic. Công cụ, GUI ứng dụng, ứng dụng web, vv. Phần thú vị nhất đó là ứng dụng web. Nhưng để test 1 ứng dụng web thì phần quan trọng nhất vẫn là Browser. Đơn giản bởi trình duyệt là cửa sổ thông qua đó người sử dụng ứng dụng web tương tác với các ứng dụng và những người dùng khác, để đáp ứng được nhu cầu đó chúng ta sẽ sử dụng Mink:

Mink là một lớp trừu tượng mô phỏng trình duyệt, cũng là một thư viện PHP 5.3 bạn sẽ sử dụng bên trong test và feature suites. Sử dụng Mink mang lại những lợi ích sau: Duy nhất, nhất quán API. Gần như không phải Cấu hình. Hỗ trợ cho cross browser.

4. Cài đặt

Để cài đặt Behat và Mink hãy làm các bước sau trong Project của bạn:

1. Create file composer.json với nội dung sau

Screen-Shot-2015-01-10-at-8.24.27-PM.png

2. Tiếp đến, cd vào thư mục tương ứng và chạy lệnh

composer update

3. Để Mink chạy

các lệnh thực hiện testing trực tiếp trên browser ta cần download selenium/webDriver selenium-server-standalone-2.44.0.jar

Sau khi download thành công, chạy lệnh để webDriver khởi động:


java -jar selenium-server-standalone-2.44.0.jar

4. Tạo feature profile:

Screen-Shot-2015-01-10-at-8.30.07-PM.png

5. Tạo file FeatureContext.php, khai báo class FeatureContext như bên dưới:

Screen-Shot-2015-01-10-at-8.32.22-PM.png

Tất cả các step được định nghĩa trong FeatureContext Class sẽ được sử dụng bởi Behat khi biểu diễn các step trong Scenario. Mỗi Scenario là tập hợp các hoạt động trong ứng dụng của bạn, khi thực hiện test, Behat sẽ tiến hành kiểm tra đầu vào và đầu ra của các hoạt động đó có thực sự như mong đợi không! Tất nhiên, Mink cũng hỗ trợ chúng ta rất nhiều các hành động thực hiện như:


Given /^(?:|I )am on (?:|the )homepage$/
- Opens homepage.

# FeatureContext::iAmOnHomepage()

When /^(?:|I )go to (?:|the )homepage$/
- Opens homepage.

# FeatureContext::iAmOnHomepage()

Given /^(?:|I )am on “(?P<page>[^"]+)”$/
- Opens specified page.

# FeatureContext::visit()

When /^(?:|I )go to “(?P<page>[^"]+)”$/
- Opens specified page.

# FeatureContext::visit()

When /^(?:|I )reload the page$/
- Reloads current page.

# FeatureContext::reload()

When /^(?:|I )move backward one page$/
- Moves backward one page in history.

# FeatureContext::back()

When /^(?:|I )move forward one page$/
- Moves forward one page in history

# FeatureContext::forward()

When /^(?:|I )press “(?P<button>(?:[^"]|\\”)*)”$/
- Presses button with specified id|name|title|alt|value.

# FeatureContext::pressButton()

When /^(?:|I )follow “(?P<link>(?:[^"]|\\”)*)”$/
- Clicks link with specified id|title|alt|text.

# FeatureContext::clickLink()

When /^(?:|I )fill in “(?P<field>(?:[^"]|\\”)*)” with “(?P<value>(?:[^"]|\\”)*)”$/
- Fills in form field with specified id|name|label|value.

# FeatureContext::fillField()

When /^(?:|I )fill in “(?P<value>(?:[^"]|\\”)*)” for “(?P<field>(?:[^"]|\\”)*)”$/
- Fills in form field with specified id|name|label|value.

# FeatureContext::fillField()

When /^(?:|I )fill in the following:$/
- Fills in form fields with provided table.

# FeatureContext::fillFields()

When /^(?:|I )select “(?P<option>(?:[^"]|\\”)*)” from “(?P<select>(?:[^"]|\\”)*)”$/
- Selects option in select field with specified id|name|label|value.

# FeatureContext::selectOption()

When /^(?:|I )additionally select “(?P<option>(?:[^"]|\\”)*)” from “(?P<select>(?:[^"]|\\”)*)”$/
- Selects additional option in select field with specified id|name|label|value.

# FeatureContext::additionallySelectOption()

When /^(?:|I )check “(?P<option>(?:[^"]|\\”)*)”$/
- Checks checkbox with specified id|name|label|value.

# FeatureContext::checkOption()

When /^(?:|I )uncheck “(?P<option>(?:[^"]|\\”)*)”$/
- Unchecks checkbox with specified id|name|label|value.

# FeatureContext::uncheckOption()

When /^(?:|I )attach the file “(?P[^"]*)” to “(?P<field>(?:[^"]|\\”)*)”$/
- Attaches file to field with specified id|name|label|value.

# FeatureContext::attachFileToField()

Then /^(?:|I )should be on “(?P<page>[^"]+)”$/
- Checks, that current page PATH is equal to specified.

# FeatureContext::assertPageAddress()

Then /^the (?i)url(?-i) should match (?P<pattern>”([^"]|\\”)*”)$/
- Checks, that current page PATH matches regular expression.

# FeatureContext::assertUrlRegExp()

Then /^the response status code should be (?P<code>\d+)$/
- Checks, that current page response status is equal to specified.

# FeatureContext::assertResponseStatus()

Then /^the response status code should not be (?P<code>\d+)$/
- Checks, that current page response status is not equal to specified.

# FeatureContext::assertResponseStatusIsNot()

Then /^(?:|I )should see “(?P<text>(?:[^"]|\\”)*)”$/
- Checks, that page contains specified text.

# FeatureContext::assertPageContainsText()

Then /^(?:|I )should not see “(?P<text>(?:[^"]|\\”)*)”$/
- Checks, that page doesn’t contain specified text.

# FeatureContext::assertPageNotContainsText()

Then /^(?:|I )should see text matching (?P<pattern>”(?:[^"]|\\”)*”)$/
- Checks, that page contains text matching specified pattern.

# FeatureContext::assertPageMatchesText()

Then /^(?:|I )should not see text matching (?P<pattern>”(?:[^"]|\\”)*”)$/
- Checks, that page doesn’t contain text matching specified pattern.

# FeatureContext::assertPageNotMatchesText()

Then /^the response should contain “(?P<text>(?:[^"]|\\”)*)”$/
- Checks, that HTML response contains specified string.

# FeatureContext::assertResponseContains()

Then /^the response should not contain “(?P<text>(?:[^"]|\\”)*)”$/
- Checks, that HTML response doesn’t contain specified string.

# FeatureContext::assertResponseNotContains()

Then /^(?:|I )should see “(?P<text>(?:[^"]|\\”)*)” in the “(?P<element>[^"]*)” element$/
- Checks, that element with specified CSS contains specified text.

# FeatureContext::assertElementContainsText()

Then /^(?:|I )should not see “(?P<text>(?:[^"]|\\”)*)” in the “(?P<element>[^"]*)” element$/
- Checks, that element with specified CSS doesn’t contain specified text.

# FeatureContext::assertElementNotContainsText()

Then /^the “(?P<element>[^"]*)” element should contain “(?P<value>(?:[^"]|\\”)*)”$/
- Checks, that element with specified CSS contains specified HTML.

# FeatureContext::assertElementContains()

Then /^the “(?P<element>[^"]*)” element should not contain “(?P<value>(?:[^"]|\\”)*)”$/
- Checks, that element with specified CSS doesn’t contain specified HTML.

# FeatureContext::assertElementNotContains()

Then /^(?:|I )should see an? “(?P<element>[^"]*)” element$/
- Checks, that element with specified CSS exists on page.

# FeatureContext::assertElementOnPage()

Then /^(?:|I )should not see an? “(?P<element>[^"]*)” element$/
- Checks, that element with specified CSS doesn’t exist on page.

# FeatureContext::assertElementNotOnPage()

Then /^the “(?P<field>(?:[^"]|\\”)*)” field should contain “(?P<value>(?:[^"]|\\”)*)”$/
- Checks, that form field with specified id|name|label|value has specified value.

# FeatureContext::assertFieldContains()

Then /^the “(?P<field>(?:[^"]|\\”)*)” field should not contain “(?P<value>(?:[^"]|\\”)*)”$/
- Checks, that form field with specified id|name|label|value doesn’t have specified value.

# FeatureContext::assertFieldNotContains()

Then /^the “(?P<checkbox>(?:[^"]|\\”)*)” checkbox should be checked$/
- Checks, that checkbox with specified in|name|label|value is checked.

# FeatureContext::assertCheckboxChecked()

Then /^the “(?P<checkbox>(?:[^"]|\\”)*)” checkbox should not be checked$/
- Checks, that checkbox with specified in|name|label|value is unchecked.

# FeatureContext::assertCheckboxNotChecked()

Then /^(?:|I )should see (?P<num>\d+) “(?P<element>[^"]*)” elements?$/
- Checks, that (?P<num>\d+) CSS elements exist on the page

# FeatureContext::assertNumElements()

Then /^print last response$/
- Prints last response to console.

# FeatureContext::printLastResponse()

Then /^show last response$/
- Opens last response content in browser.

# FeatureContext::showLastResponse()

5. Testing & result:

  • Tiến hành tạo file feature như sau:

Selection_104.png

  • Run command:

bin/behat –profile home

  • Kết quả:

Selection_106.png

6. Nguồn tham khảo: