Laravel và BDD

BDD là gì

BDD (Behavior Driven Development) là một quá trình phát triển phần mềm dựa trên phương pháp Agile(phát triển phần mềm linh hoạt).

BDD là sự mở rộng của TDD (Test driven development). Thay vì tập trung vào phát triển phần mềm theo hướng kiểm thử, BDD tập trung vào phát triển phần mềm theo hướng hành vi.

Dựa vào requirement các kịch bản test (Scenarios) sẽ được viết trước dưới dạng ngôn ngữ tự nhiên và dễ hiểu nhất sau đó mới thực hiện cài đặt source code đễ pass qua tất cả các stories đó.

Những kịch bản test này được viết dưới dạng các feature file và đòi hỏi sự cộng tác từ tất cả các thành viên tham gia dự án hay stakeholder.

Những ưu điểm của BDD

  • 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.
  • 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) [1].
  • 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)

Cài đặt Behat và Laravel

Trước tiên ta cần lấy thư viện về qua Composer sau composer require behat/behat behat/mink behat/mink-extension laracasts/behat-laravel-extension --dev P/s: Đối với windows ta nên thiết lập Path của System properties là đường dẫn đến thư viện behat. Để thiết lập ta làm như sau.

  1. Vào Propertices của My Computer.
  2. Vào phần Advance System setting rồi tìm đến Enviroment Variables.
  3. Phần tab System Variables tìm đến Path và edit thêm đường dẫn đến thư viện behat.

Sau khi cài đặt xong thư viện ta chạy lệnh behat --init. Lúc này sẽ tự động sinh ra 1 folder là features dành cho việc viết các kịch bản. Trong folder này sẽ có file là FeatureContext.php trong folder bootstrap. Nhưng vì file tạo mặc định của Behat này chưa liên kết đến Behat-mink nên khi chạy behat -dl sẽ không có gì khác cả. Vì thế ta cần thêm extends với minkContext. Vậy nội dung của file FeatureContext.php sẽ như sau

<?php

use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Behat\Gherkin\Node\TableNode;
\\ Thêm thư viện MinkContext
use Behat\MinkExtension\Context\MinkContext;

/**
 * Defines application features from the specific context.
 * Ta cần extends đến MinkContext
 */
class FeatureContext extends MinkContext implements Context
{
    /**
     * Initializes context.
     *
     * Every scenario gets its own context instance.
     * You can also pass arbitrary arguments to the
     * context constructor through behat.yml.
     */
    public function __construct()
    {
    }
}

Và lúc này khi chạy thử behat -dl thì ta sẽ có danh sách gợi ý các kịch bản như dưới đấy:

default | Given /^(?:|I )am on (?:|the )homepage$/
default | When /^(?:|I )go to (?:|the )homepage$/
default | Given /^(?:|I )am on "(?P<page>[^"]+)"$/
default | When /^(?:|I )go to "(?P<page>[^"]+)"$/
default | When /^(?:|I )reload the page$/
default | When /^(?:|I )move backward one page$/
default | When /^(?:|I )move forward one page$/
default | When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
default | When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/
default | When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
default | When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
default | When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
default | When /^(?:|I )fill in the following:$/
default | When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default | When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default | When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
default | When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/
default | When /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
default | Then /^(?:|I )should be on (?:|the )homepage$/
default | Then /^the (?i)url(?-i) should match (?P<pattern>"(?:[^"]|\\")*")$/
default | Then /^the response status code should be (?P<code>\d+)$/
default | Then /^the response status code should not be (?P<code>\d+)$/
default | Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default | Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default | Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/
default | Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default | Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default | Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
default | Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/
default | Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/
default | Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/
default | Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/
default | Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
default | Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/
default | Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
default | Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/
default | Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/
default | Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/
default | Then /^print current URL$/
default | Then /^print last response$/
default | Then /^show last response$/

Tất cả các kịch bản này đều được ví dụ ở file vendor\behat\mink-extension\src\Behat\MinkExtension\Context\MinkContext.php

Tiếp đến ta cần khai báo cho Bdd từ file behat.yml như sau:

default:
  extensions:
    Laracasts\Behat\ServiceContainer\BehatExtension: ~
    Behat\MinkExtension\ServiceContainer\MinkExtension:
      default_session: laravel
      laravel: ~

Tiếp theo viết kịch bản

Như ta đã biết thì khi cài đặt xong Laravel, vào link sẽ luôn có chữ Laravel 5. Vậy giờ ta sẽ lên kịch bản là khi nguoi dùng vào link thì sẽ có chữ Laravel 5. Để thiết lập kịch bản ta sẽ tạo 1 file là example.feature ở folder Feature mà behat tạo ra với nội dung sau:

Feature: Testing
    In order to learn Behat
    As a trial
    I want try use behat

    Screnario: Home page
        Given I am on homepage
        Then I should see "Laravel 5"

Và để kiểm tra ta sẽ chạy lệnh behat và kết quả là:

Feature: Testing
    In order to learn Behat
    As a trial
    I want try use behat

    Screnario: Home page
        Given I am on homepage
        Then I should see "Laravel 5"

1 scenario (passed)
2 steps (passed)
0m0.03s (14.15Mb)

Vậy câu hỏi đặt ra ở đây là vì sao khi ta viết những từ tựa như ngôn ngữ giao tiếp bình thường mà Behat có thể hiểu và chạy như vậy. Để hiểu rõ ta cần xem lại list danh sách kịch bản đã nói ở trên thì ta sẽ thấy có Given /^(?:|I )am on (?:|the )homepage$/ vậy tương ứng với việc ta viết Given I am on homepage thì sẽ hiểu là chạy hàm public function iAmOnHomepage() và hàm này trong file MinkContext như sau:

    /**
     * Opens homepage
     * Example: Given I am on "/"
     * Example: When I go to "/"
     * Example: And I go to "/"
     *
     * @Given /^(?:|I )am on (?:|the )homepage$/
     * @When /^(?:|I )go to (?:|the )homepage$/
     */
    public function iAmOnHomepage()
    {
        $this->visitPath("/");
    }

Tương tự thế với kịch bản Then I should see sẽ là hàm assertPageContainsText với nội dung:

     /**
     * Checks, that page contains specified text
     * Example: Then I should see "Who is the Batman?"
     * Example: And I should see "Who is the Batman?"
     *
     * @Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
     */
    public function assertPageContainsText($text)
    {
        $this->assertSession()->pageTextContains($this->fixStepArgument($text));
    }

Như vậy việc áp dụng các kịch bản có sẵn ta dễ dang viết trước được các kịch bản để develop dựa vào đó làm và có thể tự kiểm tra chất lượng công việc của mình. Như vậy rõ ràng cho ta thấy sự khác biệt của BDD ở đây là ta có thể viết trước các kịch bản rồi mới tiền hành phát triển.

Trong bài viết có sử dụng tài liệu từ Laracast cung như vài trang khác


All Rights Reserved