Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Laravel
Code

Laravel, BDD và bạn: Tính năng đầu tiên

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: Let’s Get Started

Vietnamese (Tiếng Việt) translation by Andrea Ho (you can also view the original English article)

Trong phần hai của loạt bài này có tên là Laravel, BDD và Bạn, chúng tôi sẽ bắt đầu mô tả và xây dựng tính năng đầu tiên của chúng tôi bằng Behat và PhpSpec. Trong bài trước, chúng tôi đã thiết lập mọi thứ và cho thấy có thể tương tác dễ dàng như thế nào với Laravel trong các kịch bản Behat của chúng tôi.

Gần đây, người tạo ra Behat là Konstantin Kudryashov (được biết đến với tên everzet), đã có một bài viết thực sự tuyệt vời với tên gọi Introducing Modelling by Example (giới thiệu về mô hình hóa bằng ví dụ). Quy trình công việc chúng tôi sẽ sử dụng, khi chúng tôi xây dựng tính năng của mình, được truyền cảm hứng rất lớn từ tính năng do everzet trình bày.

Ngắn gọn chúng ta sẽ sử dụng cùng một .feature để thiết kế cả core domain và giao diện người dùng của chúng ta. Tôi thường cảm thấy rằng có nhiều sự trùng lặp trong các tính năng của mình trong các kiểm tra acceptance/functional và integration. Khi tôi đọc đề xuất của everzet về việc sử dụng cùng một tính năng cho nhiều bối cảnh, tất cả đều cho tôi biết đây là cách thực hiện.

Trong trường hợp của chúng tôi, sẽ có bối cảnh chức năng của chúng tôi, hiện tại, cũng sẽ đóng vai trò là acceptance layer của chúng tôi và bối cảnh tích hợp của chúng tôi, sẽ bao gồm domain của chúng tôi. Chúng tôi sẽ bắt đầu bằng cách xây dựng tên miền và sau đó thêm UI và sau đó là những thứ cụ thể theo framework.

Những tái cấu trúc nhỏ

Để sử dụng phương pháp "chia sẻ tính năng, nhiều bối cảnh", chúng tôi phải thực hiện một vài thao tác tái cấu trúc trong thiết lập hiện tại.

Đầu tiên, chúng ta sẽ xóa tính năng chào mừng mà chúng ta đã thực hiện trong phần đầu tiên, vì chúng ta không thực sự cần nó và đó không thực sự theo phong cách chung mà chúng ta cần để sử dụng nhiều bối cảnh.

Thứ hai, chúng ta sẽ có các tính năng của mình trong gốc của thư mục features, vì vậy chúng ta có thể tiếp tục xóa thuộc tính path khỏi file behat.yml của chúng ta. Chúng tôi cũng sẽ đổi tên LaravelFeatureContext thành FunctionalFeatureContext (và nhớ thay đổi tên class):

Cuối cùng, để dọn dẹp mọi thứ gọn gàng một chút, tôi nghĩ chúng ta nên chuyển tất cả những thứ liên quan đến Laravel vào trait riêng của nó:

Trong FunctionalFeatureContext, sau đó chúng ta có thể sử dụng đặc điểm và xóa những thứ chúng ta vừa di chuyển:

Trait là cách tuyệt vời để thu dọn các bối cảnh của bạn.

Chia sẻ một tính năng

Như đã trình bày trong phần một, chúng tôi sẽ xây dựng một ứng dụng nhỏ để theo dõi thời gian. Tính năng đầu tiên sẽ là về theo dõi thời gian và tạo bảng thời gian từ các mục được theo dõi. Đây là tính năng:

Hãy nhớ rằng đây chỉ là một ví dụ. Tôi thấy việc xác định các tính năng dễ dàng hơn trong cuộc sống thực tế, vì bạn có vấn đề thực sự cần giải quyết và thường có cơ hội thảo luận về tính năng này với đồng nghiệp, khách hàng hoặc các bên liên quan khác.

Được rồi, hãy để chúng tôi để Behat tạo các bước trong kịch bản cho chúng tôi:

Chúng ta cần phải điều chỉnh các bước được tạo ra một chút. Chúng tôi chỉ cần 4 bước để nói đến kịch bản. Kết quả cuối cùng sẽ trông như thế này:

Bối cảnh chức năng của chúng tôi đã sẵn sàng để thực hiện, nhưng chúng tôi cũng cần một bối cảnh cho integration suite của chúng tôi. Đầu tiên, chúng tôi sẽ bổ sung suite vào file behat.yml:

Tiếp theo, chúng ta chỉ có thể sao chép FeatureContext mặc định:

Hãy nhớ thay đổi tên class thành IntegrationFeatureContext và cũng để sao chép câu lệnh sử dụng cho PendingException.

Cuối cùng, khi chia sẻ tính năng, chúng tôi chỉ có thể sao chép các định nghĩa 4 bước từ bối cảnh chức năng. Nếu bạn chạy Behat thì bạn sẽ thấy tính năng này được chạy 2 lần: một lần cho mỗi bối cảnh.

Thiết kế domain

Tại thời điểm này, chúng tôi đã sẵn sàng để bắt đầu điền vào các bước đang chờ xử lý trong bối cảnh tích hợp của chúng tôi để thiết kế domain cốt lõi của ứng dụng của chúng tôi. Bước đầu tiên là Given I have the following time entries, tiếp sau đó là bảng có các bản ghi nhập thời gian. Để đơn giản, chúng ta chỉ cần lặp qua các hàng của bảng, cố gắng khởi tạo một giá trị nhập thời gian cho từng mục và bổ sung chúng vào một mảng nhập giá trị theo bối cảnh:

Việc chạy Behat sẽ gây ra lỗi nghiêm trọng, vì class TimeTracker\TimeEntry chưa tồn tại. Đây là nơi PhpSpec tham gia vào giai đoạn. Cuối cùng, TimeEntry sẽ trở thành một class Eloquent, dù chúng tôi chưa lo lắng về nó. PhpSpec và ORM như Eloquent không hòa hợp tốt với nhau, nhưng chúng ta vẫn có thể sử dụng PhpSpec để tạo class và thậm chí chỉ ra một số hành vi cơ bản. Chúng ta hãy sử dụng các trình tạo PhpSpec để tạo class TimeEntry:

Sau khi class được tạo ra, chúng tôi cần cập nhật phần autoload của file composer.json:

Và tất nhiên chạy composer dump-autoload.

Chạy PhpSpec cho chúng ta thấy màu xanh lá cây. Việc chạy Behat cũng cho chúng ta màu xanh lá cây. Thật là một khởi đầu tuyệt vời!

Để Behat hướng dẫn cho chúng tôi, làm thế nào về việc chúng tôi chỉ cần chuyển sang bước tiếp theo, when I generate the time sheet, ngay lập tức?

Từ khóa ở đây là "generate", trông giống như một thuật ngữ từ domain của chúng tôi. Trong thế giới của lập trình viên, việc biên dịch "tạo bảng thời gian" sang code có thể có nghĩa là bắt đầu một class TimeSheet với một loạt các mục nhập thời gian. Điều quan trọng là phải thử và bám vào ngôn ngữ từ domain khi chúng tôi thiết kế code của chúng tôi. Bằng cách đó, code của chúng tôi sẽ giúp mô tả hành vi đã dự định của ứng dụng của chúng tôi.

Tôi xác định thuật ngữ generate là quan trọng đối với domain, đó là lý do tại sao tôi nghĩ rằng chúng ta nên có một phương thức static tên gọi generate trên class TimeSheet đóng vai trò là alias cho hàm constructor. Phương thức này sẽ lấy một tập hợp các mục nhậo thời gian và lưu trữ chúng trên bảng thời gian.

Thay vì chỉ sử dụng một mảng, tôi nghĩ sẽ hợp lý khi sử dụng class Illuminate\Support\Collection đi cùng với Laravel. Vì TimeEntry sẽ là một mô hình Eloquent, khi chúng tôi truy vấn database cho các mục nhập thời gian, chúng tôi sẽ nhận được một trong những collection của Laravel. Còn những điều như thế này thì sao:

Nhân tiện, TimeSheet sẽ không phải là một class Eloquent. Ít nhất hiện tại, chúng ta chỉ cần duy trì các mục nhập thời gian, và sau đó các bảng thời gian sẽ chỉ được tạo từ các mục đó.

Lần nữa việc chạy Behat sẽ gây ra lỗi nghiêm trọng, vì TimeSheet không tồn tại. PhpSpec có thể giúp chúng tôi giải quyết điều đó:

Chúng tôi vẫn gặp một lỗi nghiêm trọng (fatal error) sau khi tạo class, bởi vì phương thức static generate() vẫn không tồn tại. Vì đây là một phương thức static thực sự đơn giản, tôi không nghĩ cần có thông số kỹ thuật. Nó không có gì khác hơn là một wrapper cho constructor:

Điều này sẽ khiến Behat trở lại màu xanh lá cây, nhưng PhpSpec hiện đang kêu rít lên với chúng tôi, nói rằng: Argument 1 passed to TimeTracker\TimeSheet::__construct() must be an instance of Illuminate\Support\Collection, none given. Chúng ta có thể giải quyết điều này bằng cách viết một hàm let() đơn giản sẽ được gọi trước mỗi spec:

Điều này sẽ đưa chúng ta trở lại màu xanh trên tất cả các dòng. Hàm đảm bảo rằng bảng thời gian luôn được xây dựng với một mô hình của class Collection.

Bây giờ chúng ta có thể chuyển sang bước Then my total time spent on .... Chúng ta cần một phương thức lấy tên tác vụ và trả về thời gian tích lũy của tất cả các mục có tên tác vụ này. Được biên dịch trực tiếp từ gherkin sang code, đây có thể là điều gì giống như TotalTimeSpentOn($task):

Phương thức này không tồn tại, do đó, việc chạy Behat sẽ đem lỗi cho chúng ta Call to undefined method TimeTracker\TimeSheet::totalTimeSpentOn().

Để chỉ ra phương thức, chúng tôi sẽ viết một thông số trông giống với những gì chúng tôi đã có trong kịch bản của mình:

Lưu ý rằng chúng tôi không sử dụng giả cho các đối tượng TimeEntry và Collection. Đây là suite tích hợp và không cần phải chế giễu điều này. Các đối tượng khá đơn giản và chúng tôi muốn đảm bảo rằng các đối tượng trong domain của chúng tôi tương tác như chúng tôi mong đợi. Có lẽ có nhiều ý kiến về điều này, nhưng điều này có ý nghĩa với tôi.

Tiếp tục nào:

Để lọc các mục, chúng ta có thể sử dụng phương thức filter() trên class Collection. Một giải pháp đơn giản giúp chúng ta có màu xanh:

Thông số kỹ thuật của chúng tôi là màu xanh lá cây, nhưng tôi cảm thấy rằng chúng tôi có thể hưởng lợi từ một số tái cấu trúc ở đây. Phương thức này dường như thực hiện hai việc khác nhau: lọc các mục và tích lũy thời lượng. Hãy để chúng tôi extract việc thứ hai cho phương thức riêng của mình:

PhpSpec vẫn còn màu xanh và hiện tại chúng tôi có ba bước màu xanh trong Behat. Bước cuối cùng nên dễ dàng triển khai, vì nó hơi giống với bước chúng ta vừa làm.

Chạy Behat sẽ báo lỗi cho chúng tôi Call to undefined method TimeTracker\TimeSheet::totalTimeSpent(). Thay vì làm một ví dụ riêng trong thông số kỹ thuật của chúng tôi cho phương thức này, chúng ta chỉ cần thêm nó vào ví dụ mà chúng ta đã có? Có thể ví dụ này không hoàn toàn phù hợp với những gì "đúng" cần làm, nhưng chúng ta hãy thực dụng một chút:

Hãy để PhpSpec tạo phương thức:

Giờ đây, việc chuyển sang xanh trở nên dễ dàng khi chúng ta có phương thức sumDuration():

Và bây giờ chúng ta có một tính năng màu xanh. Domain của chúng tôi đang dần phát triển!

Thiết kế giao diện người dùng

Bây giờ, chúng tôi đang chuyển sang bộ chức năng của chúng tôi. Chúng tôi sẽ thiết kế giao diện người dùng và xử lý tất cả những việc cụ thể của Laravel, chúng không phải là mối quan tâm của domain của chúng tôi.

Khi làm việc trong functional suite, chúng ta có thể bổ sung cờ -s để hướng dẫn Behat chỉ chạy các tính năng của chúng tôi thông qua FunctionalFeatureContext:

Bước đầu tiên sẽ trông giống như bước đầu tiên trong bối cảnh tích hợp. Thay vì chỉ làm cho các mục tồn tại trên bối cảnh trong một mảng, chúng ta cần thực sự làm cho chúng tồn tại trong database để chúng có thể được truy xuất sau đó:

Chạy Behat sẽ gây ra cho chúng tôi lỗi nghiêm trọng Call undefined method  TimeTracker\TimeEntry::save(), vì TimeEntry vẫn không phải là model của Eloquent. Điều đó dễ sửa:

Nếu chúng tôi chạy lại Behat, Laravel sẽ than phiền nó không thể kết nối với database. Chúng tôi có thể khắc phục điều này bằng cách bổ sung file database.php vào thư mục app/config/testing để thêm thông tin kết nối cho database của chúng tôi. Đối với các dự án lớn hơn, bạn có thể muốn sử dụng cùng một máy chủ database cho các test và code base thực tế của mình, nhưng trong trường hợp của chúng tôi, chúng tôi sẽ chỉ sử dụng một database SQLite trong dạng in memory. Điều này cực kỳ đơn giản để thiết lập với Laravel:

Bây giờ nếu chúng ta chạy Behat, nó sẽ cho chúng ta biết rằng không có bảng time_entries. Để khắc phục điều này, chúng tôi cần triển khai migration:

Chúng tôi vẫn không có màu xanh, khi chúng tôi cần một cách để hướng dẫn Behat chạy migration của chúng tôi trước mọi kịch bản, vì vậy chúng tôi luôn có một bảng xếp hạng gọn gàng. Bằng cách sử dụng các chú thích của Behat, chúng ta có thể bổ sung hai phương thức này vào trajtc ủa LaravelTrait:

Điều này là khá gọn gàng và là bước đầu tiên đưa chúng tôi đến màu xanh.

Tiếp theo là bước When I generate the time sheet. Theo cách tôi thấy, tạo bảng thời gian tương đương với việc truy cập hành động index của tài nguyên nhập thời gian, vì bảng thời gian là tập hợp của tất cả các mục nhập thời gian. Vì vậy, đối tượng bảng thời gian giống như một container cho tất cả các mục nhập thời gian và đề xuất cho chúng ta một cách hay để xử lý các mục. Thay vì đi đến /time-entries, để xem bảng thời gian, tôi nghĩ rằng nhân viên sẽ tìm đến /time-sheet. Chúng ta nên đặt nó trong bước định nghĩa:

Điều này sẽ tạo ra NotFoundHttpException, vì mỗi route chưa được định nghĩa. Như vừa giải thích, tôi nghĩ rằng URL này nên ánh xạ tới hành động index trên tài nguyên của mục nhập thời gian:

Để chuyển sang màu xanh, chúng ta cần tạo controller:

Và chúng tôi đã đạt được.

Cuối cùng, chúng ta cần thu thập dữ liệu trang để tìm tổng thời lượng của các mục nhập thời gian. Tôi tính toán chúng ta sẽ có một số kiểu bảng nhằm tóm tắt thời lượng. Hai bước cuối cùng giống nhau đến mức chúng ta sẽ cùng lúc thực hiện chúng:

Crawler tìm kiếm node <td> với id là [task_name]TotalDuration hoặc TotalDuration trong ví dụ trước.

Vì chúng tôi vẫn chưa có view, crawler sẽ cho chúng tôi The current node list is empty.

Để khắc phục điều này, chúng ta hãy xây dựng hành động index. Đầu tiên, chúng tôi lấy collection các mục nhập thời gian. Thứ hai, chúng tôi tạo một bảng thời gian từ các mục và gửi nó đến view (vẫn chưa tồn tại).

Hiện tại view sẽ chỉ bao gồm một bảng đơn giản với các giá trị thời lượng được tóm tắt:

Nếu bạn chạy lại Behat, bạn sẽ thấy rằng chúng ta đã triển khai thành công tính năng này. Có lẽ chúng ta nên dành một chút thời gian để nhận ra điều đó, thậm chí chưa một lần chúng ta mở trình duyệt! Đây là một cải tiến lớn cho quy trình làm việc của chúng tôi và như một phần thưởng tuyệt vời, giờ đây chúng tôi đã có các test tự động cho ứng dụng của mình.

Tổng kết

Nếu bạn chạy vendor/bin/behat để chạy cả hai suite của Behat, bạn sẽ thấy cả hai đều có màu xanh. Nếu bạn chạy PhpSpec, thật không may, bạn sẽ thấy thông số kỹ thuật của chúng tôi bị hỏng. Chúng tôi nhận được một lỗi nghiêm Class 'Eloquent not found in.... Điều này là do Eloquent là một alias. Nếu bạn xem qua app/config/app.php dưới các alias, bạn sẽ thấy Eloquent thực sự là một alias cho Illuminate\Database\Eloquent\Model. Để đưa PhpSpec trở về màu xanh, chúng ta cần import class này:

Nếu bạn chạy hai lệnh này:

Bạn sẽ thấy chúng tôi đã nhận lại màu xanh lá cây, cả Behat và PhpSpec.

Bây giờ chúng tôi đã mô tả và thiết kế tính năng đầu tiên, hoàn toàn sử dụng phương pháp BDD. Chúng tôi đã thấy làm thế nào có thể hưởng lợi từ việc thiết kế core domain của ứng dụng của chúng tôi, trước khi chúng tôi lo lắng về UI và các công cụ cụ thể của framework. Chúng tôi cũng đã thấy việc tương tác với Laravel dễ dàng như thế nào, và đặc biệt là database, trong các bối cảnh Behat của chúng tôi.

Trong bài viết tiếp theo, chúng tôi sẽ thực hiện nhiều thao tác tái cấu trúc để tránh quá nhiều logic trên các model Eloquent của chúng tôi, vì các model này khó kiểm tra riêng rẽ hơn và được kết hợp chặt chẽ với Laravel. Hãy tiếp tục theo dõi!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.