Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)
Chào mừng bạn đến với loạt bài về việc phát triển các ứng dụng Laravel bằng cách sử dụng phương pháp phát triển dựa trên hành vi - behavior-driven (BDD). Full stack BDD nghe có vẻ phức tạp và đáng sợ. Cũng như việc có nhiều nhà phát triển, có nhiều cách để triển khai phương pháp này.
Trong loạt bài này, tôi sẽ hướng dẫn bạn cách tiếp cận của tôi về việc sử dụng Behat và PhpSpec để thiết kế một ứng dụng Laravel từ đầu.
Có rất nhiều tài nguyên về BDD nói chung, nhưng tài liệu cụ thể của Laravel rất khó tìm. Do đó, trong loạt bài này, chúng tôi sẽ tập trung nhiều hơn vào các khía cạnh liên quan đến Laravel và ít đề cập đến những thứ chung mà bạn có thể tìm đọc từ nhiều nơi khác.
Mô tả về hành vi
Khi mô tả hành vi, còn được gọi là viết nên các câu chuyện và thông số kỹ thuật, chúng ta sẽ sử dụng cách tiếp cận outside-in (từ ngoài vào trong). Điều này có nghĩa là mỗi khi xây dựng một tính năng mới, chúng tôi sẽ bắt đầu bằng cách viết câu chuyện tổng thể của người dùng. Điều này thường xuất phát từ góc nhìn của khách hàng hoặc các bên liên quan.
Chúng ta đang mong đợi điều gì xảy ra khi thực hiện điều này?
Chúng tôi không được phép viết bất kỳ code nào cho đến khi chúng tôi có một rắc rối hoặc thất bại, ngoại trừ khi chúng tôi đang tái cấu trúc code hiện có.
Đôi khi, sẽ cần phải lặp đi liên tục lặp lại giải quyết một phần nhỏ của một câu chuyện hoặc tính năng (hai từ mà tôi sử dụng thay thế cho nhau) mà chúng tôi đang làm việc. Điều này thường nghĩa là viết thông số kỹ thuật với PhpSpec. Đôi khi, sẽ lần lặp lại nhiều lại ở cấp độ interation (tích hợp) hoặc unit (đơn vị) trước khi toàn bộ câu chuyện (ở acceptance level) được thông qua. Tất cả điều này nghe có vẻ rất phức tạp nhưng thực sự thì không. Tôi là một người có niềm tin to lớn vào việc học qua thực hành, vì vậy tôi nghĩ rằng mọi thứ sẽ có dễ hiểu hơn khi chúng ta bắt đầu viết code thực tế.
Chúng tôi sẽ viết các câu chuyện và thông số kỹ thuật ở 4 cấp độ khác nhau:
1. Acceptance
Hầu hết thời gian, nhóm chức năng của chúng tôi sẽ hoạt động như acceptance layer của chúng tôi. Cách chúng tôi sẽ mô tả các tính năng của chúng tôi trong nhóm chức năng sẽ rất giống với cách viết các câu chuyện acceptance (sử dụng framework trình duyệt tự động) và như vậy sẽ sinh ra nhiều sự trùng lặp.
Miễn là những câu chuyện mô tả hành vi theo góc độ của khách hàng, chúng đóng vai trò là những câu chuyện acceptance (chấp nhận). Chúng tôi sẽ sử dụng Symfony DomCrawler để kiểm tra kết quả của ứng dụng của chúng tôi. Sau đó, chúng ta có thể thấy rằng chúng ta cần kiểm tra thông qua một trình duyệt thực tế có thể chạy JavaScript. Kiểm tra thông qua trình duyệt tạo ra một số mối quan tâm mới, vì chúng tôi cần đảm bảo rằng chúng tôi tải môi trường kiểm tra khi nhóm chức năng chạy.
2. Chức năng
Trong nhóm chức năng của chúng tôi, chúng tôi sẽ có quyền truy xuất vào ứng dụng Laravel, rất thuận tiện. Trước hết, nó giúp bạn dễ dàng phân biệt giữa các môi trường. Thứ hai, không thông qua trình duyệt làm cho test của chúng tôi nhanh hơn rất nhiều. Bất cứ khi nào chúng tôi muốn thực hiện một tính năng mới, chúng tôi sẽ viết một câu chuyện trong nhóm chức năng của chúng tôi bằng Behat.
3. Tích hợp
Nhóm tích hợp của chúng tôi sẽ kiểm tra hành vi của phần cốt lõi trong ứng dụng của chúng tôi mà không nhất thiết phải có quyền truy xuất vào Laravel. Nhóm tích hợp thường sẽ là tổng hợp của các câu chuyện Behat và thông số kỹ thuật PhpSpec.
4. Unit
Các unit test của chúng tôi sẽ được viết bằng PhpSpec và sẽ kiểm tra các đơn vị nhỏ tách biệt của lõi ứng dụng. Các thực thể của chúng ta, các đối tượng giá trị, vv tất cả sẽ có thông số kỹ thuật.
Tinh huống
Trong suốt loạt bài này, bắt đầu từ bài viết tiếp theo, chúng tôi sẽ xây dựng một hệ thống để theo dõi thời gian. Chúng tôi sẽ bắt đầu bằng cách mô tả hành vi từ bên ngoài bằng cách viết các tính năng Behat. Hành vi nội bộ của ứng dụng của chúng tôi sẽ được mô tả bằng PhpSpec.
Hai công cụ này sẽ giúp chúng tôi cảm thấy thoải mái với chất lượng của ứng dụng chúng tôi đang xây dựng. Chúng tôi chủ yếu sẽ viết các tính năng và thông số kỹ thuật trên ba cấp độ:
- Functional - chức năng
- Integration - tích hợp
- Unit
Trong nhóm chức năng của chúng tôi, chúng tôi sẽ thu thập các phản hồi HTTP của ứng dụng ở chế độ không header, nghĩa là chúng tôi sẽ dùng trình duyệt. Điều này sẽ giúp việc tương tác với Laravel dễ dàng hơn và làm cho nhóm chức năng của chúng tôi cũng đóng vai trò là acceptance layer của chúng tôi.
Sau này, nếu chúng ta cho ra kết quả là một giao diện người dùng phức tạp hơn và có thể cần phải kiểm tra một số JavaScript thì chúng ta có thể thêm một nhóm acceptance đặc trưng. Loạt bài này vẫn đang được viết, vì vậy hãy để lại các đề xuất của bạn trong phần bình luận.
Thiết lập của chúng tôi
Lưu ý rằng với hướng dẫn này, tôi giả sử bạn có một bản cài đặt mới của Laravel (4.2). Tốt nhất là bạn cũng đang sử dụng Laravel Homestead, đó là những gì tôi đã dùng khi tôi viết code này.
Trước khi chúng tôi bắt đầu công việc thực tế, hãy đảm bảo rằng chúng tôi có Behat và PhpSpec đang vận hành. Mặc dù vậy, trước tiên, tôi muốn dọn dẹp một chút mỗi khi tôi bắt đầu một dự án laravel mới và xóa những thứ tôi không cần:
git rm -r app/tests/ phpunit.xml CONTRIBUTING.md
Nếu bạn xóa các file này, đảm bảo cập nhật file composer.json cho phù hợp:
"autoload": { "classmap": [ "app/commands", "app/controllers", "app/models", "app/database/migrations", "app/database/seeds" ] },
Và dĩ nhiên:
$ composer dump-autoload
Bây giờ chúng tôi đã sẵn sàng để tải về các công cụ BDD mà chúng tôi cần. Chỉ cần thêm phần yêu cầu-dev vào composer.json của bạn:
"require": { "laravel/framework": "4.2.*" }, "require-dev": { "behat/behat": "~3.0", "phpspec/phpspec": "~2.0", "phpunit/phpunit": "~4.1" },
"Tại sao chúng ta lại tải về PHPUnit?" bạn có thể đang suy nghĩ? Chúng tôi sẽ không viết các trường hợp thử nghiệm PHPUnit tốt trong loạt bài này, nhưng khẳng định chúng là một công cụ hữu ích cùng với Behat. Chúng ta sẽ thấy điều đó sau trong bài viết này khi chúng ta viết tính năng đầu tiên của chúng tôi.
Hãy nhớ cập nhật các phụ thuộc sau khi sửa đổi composer.json
:
$ composer update --dev
Chúng tôi gần như đã hoàn tất việc cài đặt và thiết lập công cụ. PhpSpec hoạt động tốt:
$ vendor/bin/phpspec run 0 specs 0 examples 0ms
Nhưng Behat cần triển khai nhanh chóng với tùy chọn --init
để thiết lập mọi thứ:
$ vendor/bin/behat --init +d features - place your *.feature files here +d features/bootstrap - place your context classes here +f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here $ vendor/bin/behat No scenarios No steps 0m0.14s (12.18Mb)
Lệnh đầu tiên tạo ra một class FeatureContext
mới, nơi chúng ta có thể viết các định nghĩa bước cần thiết cho các tính năng của mình:
<?php use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; /** * Behat context class. */ class FeatureContext implements SnippetAcceptingContext { /** * Initializes context. * * Every scenario gets its own context object. * You can also pass arbitrary arguments to the context constructor through behat.yml. */ public function __construct() { } }
Viết tính năng đầu tiên của chúng tôi
Tính năng đầu tiên của sẽ rất đơn giản: chỉ đơn giản là đảm bảo rằng bản cài đặt Laravel mới của chúng tôi chào đón chúng tôi bằng câu "You have arrived". trên trang chủ. Tôi cũng đưa ra bước Given
khá ngớ ngẫn, Given I am logged in
, chỉ cho thấy mức độ tương tác dễ dàng với Laravel trong các tính năng của chúng tôi.
Về mặt kỹ thuật, tôi sẽ phân loại loại tính năng này là một functional test, vì nó tương tác với framework, nhưng nó cũng đóng vai trò là một acceptance test, vì chúng tôi sẽ không thấy bất kỳ kết quả nào khác khi chạy test tương tự thông qua một công cụ trình duyệt kiểm tra. Từ bây giờ chúng tôi sẽ bám sát nhóm functional test của chúng tôi.
Hãy tiếp tục tạo một file welcome.feature
và đưa vào features/functional
:
# features/functional/welcome.feature Feature: Welcoming developer As a Laravel developer In order to proberly begin a new project I need to be greeted upon arrival Scenario: Greeting developer on homepage Given I am logged in When I visit "/" Then I should see "You have arrived."
Bằng cách đưa vào các tính năng chức năng trong một thư mục functional
, chúng ta sẽ dễ dàng quản lý các nhóm của mình sau này. Chúng tôi không muốn các tính năng kiểu tích hợp không yêu cầu Laravel phải chờ đợi nhóm chức năng chậm chạp.
Tôi thích mọi thứ tốt và gọn gàng, vì vậy tôi tin rằng chúng ta nên có một bối cảnh tính năng dành riêng cho nhóm chức năng có thể cho phép chúng ta truy cập vào Laravel. Bạn có thể tiếp tục sao chép file FeatureContext
hiện có và thay đổi tên class thành LaravelFeatureContext
. Để làm việc này, chúng ta cũng cần một file cấu hình Behat.yml
.
Tạo một trong thư mục gốc của dự án của bạn và thêm nội dung như sau:
default: suites: functional: paths: [ %paths.base%/features/functional ] contexts: [ LaravelFeatureContext ]
Tôi nghĩ rằng YAML ở đây là khá dễ hiểu. Nhóm chức năng của chúng tôi sẽ tìm kiếm các tính năng trong thư mục functional
và chạy chúng thông qua LaravelFeatureContext
.
Nếu chúng ta cố gắng chạy Behat tại thời điểm này, nó sẽ chỉ chúng ta thực hiện các định nghĩa bước cần thiết. Chúng ta có thể yêu cầu Behat thêm các phương thức scaffold mới vào LaravelFeatureContext
bằng lệnh sau:
$ vendor/bin/behat --dry-run --append-snippets $ vendor/bin/behat Feature: Welcoming developer As a Laravel developer In order to proberly begin a new project I need to be greeted upon arival Scenario: Greeting developer on homepage # features/functional/welcome.feature:6 Given I am logged in # LaravelFeatureContext::iAmLoggedIn() TODO: write pending definition When I visit "/" # LaravelFeatureContext::iVisit() Then I should see "You have arrived." # LaravelFeatureContext::iShouldSee() 1 scenario (1 pending) 3 steps (1 pending, 2 skipped) 0m0.28s (12.53Mb)
Và bây giờ, như bạn có thể thấy từ kết quả, chúng tôi đã sẵn sàng để bắt đầu thực hiện bước đầu tiêni: Given am logged in
.
Trường hợp test của PHPUnit gửi cùng với Laravel cho phép chúng tôi thực hiện các công cụ như $this->be($user)
, đăng nhập vào một người dùng nhất định. Cuối cùng, chúng tôi muốn có thể tương tác với Laravel như thể chúng tôi đang sử dụng PHPUnit, vì vậy hãy tiếp tục viết code định nghĩa bước "we wish we had":
/** * @Given I am logged in */ public function iAmLoggedIn() { $user = new User; $this->be($user); }
Tất nhiên, code này sẽ không hiệu quả, vì Behat không biết cụ thể gì về Laravel, nhưng tôi sẽ chỉ cho bạn thấy ngay sẽ dễ dàng như thế nào để Behat và Laravel kết thân với nhau.
Nếu bạn xem mã nguồn của Laravel và tìm thấy class Illuminate\Foundation\Testing\TestCase
, trường hợp kiểm thử mặc định mở rộng từ class này, bạn sẽ thấy rằng bắt đầu từ Laravel 4.2, mọi thứ đã được chuyển thành trait. ApplicationTrait
hiện chịu trách nhiệm khởi động một phiên bản Application
, thiết lập ứng dụng khách HTTP và cung cấp cho chúng tôi một vài phương thức helper, chẳng hạn như be()
.
Điều này khá thú vị, chủ yếu vì nghĩa là chúng ta có thể tải nó về vào bối cảnh Behat của chúng ta mà hầu như không cần thiết lập. Chúng tôi cũng có quyền truy xuất vào AssertionsTrait
, nhưng vẫn được gắn với PHPUnit.
Khi chúng ta tải về trait, chúng ta cần làm 2 việc. Chúng ta cần có một phương thức setUp()
, giống như phương thức trong class Illuminate\Foundation\Testing\TestCase
và chúng ta cần một phương thức createApplication()
, giống như phương thức trong trường hợp kiểm tra mặc định của Laravel. Trên thực tế chúng ta chỉ có thể sao chép hai phương thức đó và trực tiếp sử dụng chúng.
Chỉ có một điều cần chú ý: Trong PHPUnit, phương thức setUp()
sẽ tự động được gọi trước mỗi bài test. Để đạt được điều tương tự trong Behat, chúng ta có thể sử dụng chú thích @BeforeScenario
.
Thêm phần sau đây vào LaravelFeatureContext
của bạn:
use Illuminate\Foundation\Testing\ApplicationTrait; /** * Behat context class. */ class LaravelFeatureContext implements SnippetAcceptingContext { /** * Responsible for providing a Laravel app instance. */ use ApplicationTrait; /** * Initializes context. * * Every scenario gets its own context object. * You can also pass arbitrary arguments to the context constructor through behat.yml. */ public function __construct() { } /** * @BeforeScenario */ public function setUp() { if ( ! $this->app) { $this->refreshApplication(); } } /** * Creates the application. * * @return \Symfony\Component\HttpKernel\HttpKernelInterface */ public function createApplication() { $unitTesting = true; $testEnvironment = 'testing'; return require __DIR__.'/../../bootstrap/start.php'; }
Khá dễ dàng và xem kết quả chúng tôi nhận được khi chạy Behat:
$ vendor/bin/behat Feature: Welcoming developer As a Laravel developer In order to proberly begin a new project I need to be greeted upon arival Scenario: Greeting developer on homepage # features/functional/welcome.feature:6 Given I am logged in # LaravelFeatureContext::iAmLoggedIn() When I visit "/" # LaravelFeatureContext::iVisit() TODO: write pending definition Then I should see "You have arrived." # LaravelFeatureContext::iShouldSee() 1 scenario (1 pending) 3 steps (1 passed, 1 pending, 1 skipped) 0m0.73s (17.92Mb)
Bước đầu tiên xanh, có nghĩa là thiết lập của chúng tôi đã xong!
Tiếp theo, chúng ta có thể thực hiện bước When I visit
. Cách này cực dễ và chúng ta chỉ cần sử dụng phương thức call()
mà ApplicationTrait
cung cấp. Một dòng code sẽ tạo ra kết quả đó:
/** * @When I visit :uri */ public function iVisit($uri) { $this->call('GET', $uri); }
Bước cuối cùng, Then I should see
, mất thêm một chút thời gian và chúng ta cần phải tải về hai phụ thuộc. Chúng tôi sẽ cần PHPUnit để xác nhận và chúng tôi sẽ cần Symfony DomCrawler để tìm kiếm văn bản "You have arrived".
Chúng ta có thể triển khai như thế này:
use PHPUnit_Framework_Assert as PHPUnit; use Symfony\Component\DomCrawler\Crawler; ... /** * @Then I should see :text */ public function iShouldSee($text) { $crawler = new Crawler($this->client->getResponse()->getContent()); PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']")); }
Nó khá giống với code bạn sẽ viết nếu bạn đang sử dụng PHPUnit. Phần filterXpath()
hơi khó hiểu và chúng tôi sẽ nghĩ nhiều với nó lúc này, vì nó nằm ngoài phạm vi của bài viết này. Chỉ cần tin tôi rằng nó hoạt động.
Việc lần nữa chạy Behat là một tin tốt:
$ vendor/bin/behat Feature: Welcoming developer As a Laravel developer In order to proberly begin a new project I need to be greeted upon arival Scenario: Greeting developer on homepage # features/functional/welcome.feature:6 Given I am logged in # LaravelFeatureContext::iAmLoggedIn() When I visit "/" # LaravelFeatureContext::iVisit() Then I should see "You have arrived." # LaravelFeatureContext::iShouldSee() 1 scenario (1 passed) 3 steps (3 passed) 0m0.82s (19.46Mb)
Tính năng này hoạt động như mong đợi và nhà phát triển được chào đón khi đến nơi.
Tổng kết
Hiện tại, LaravelFeatureContext
hoàn chỉnh sẽ trông giống như thế này:
<?php use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use PHPUnit_Framework_Assert as PHPUnit; use Symfony\Component\DomCrawler\Crawler; use Illuminate\Foundation\Testing\ApplicationTrait; /** * Behat context class. */ class LaravelFeatureContext implements SnippetAcceptingContext { /** * Responsible for providing a Laravel app instance. */ use ApplicationTrait; /** * Initializes context. * * Every scenario gets its own context object. * You can also pass arbitrary arguments to the context constructor through behat.yml. */ public function __construct() { } /** * @BeforeScenario */ public function setUp() { if ( ! $this->app) { $this->refreshApplication(); } } /** * Creates the application. * * @return \Symfony\Component\HttpKernel\HttpKernelInterface */ public function createApplication() { $unitTesting = true; $testEnvironment = 'testing'; return require __DIR__.'/../../bootstrap/start.php'; } /** * @Given I am logged in */ public function iAmLoggedIn() { $user = new User; $this->be($user); } /** * @When I visit :uri */ public function iVisit($uri) { $this->call('GET', $uri); } /** * @Then I should see :text */ public function iShouldSee($text) { $crawler = new Crawler($this->client->getResponse()->getContent()); PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']")); } }
Bây giờ chúng tôi có một nền tảng thực sự tốt để xây dựng khi chúng tôi tiếp tục phát triển ứng dụng Laravel mới của mình bằng cách sử dụng BDD. Tôi hy vọng tôi đã chứng minh cho bạn thấy thật dễ dàng để khiến Laravel và Behat chơi thân với nhau.
Chúng tôi đã đề cập vào rất nhiều chủ đề khác nhau trong bài viết đầu tiên này. Không cần phải lo lắng, chúng tôi sẽ xem xét kỹ hơn về mọi thứ khi loạt bài tiếp tục. Nếu bạn có bất kỳ câu hỏi hoặc đề xuất nào, xin vui lòng để lại bình luận.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post