Hướng Dẫn Cài Đặt và Sử Dụng PHPUnit: Bước Đầu Tiếp Cận Unit Test

Biết rằng Unit Test quan trọng và mang lại nhiều lợi ích, tuy nhiên, việc áp dụng và thực hiện nó một cách đúng đắn và hiệu quả lại là một thách thức. Bài viết này sẽ giới thiệu các khái niệm cơ bản về testing trong PHP bằng PHPUnit.

Bài viết này là bài đầu tiên trong loạt bài hướng dẫn thực hiện Unit Test trong PHP, được dịch từ blog của tác giả Juan Treminio.

Loạt bài này sẽ giới thiệu cho bạn các khái niệm cơ bản về testing. Nó sẽ cho bạn biết vì sao việc sử dụng static là không tốt, tại sao Dependency Injection tuyệt vời, sự khác biệt giữa mock và stub và làm thế nào để yêu cái thanh màu đỏ, xanh ấy.

Tôi cũng sẽ đề cập một chút đến Test-Driven Development (TDD) nhưng sẽ không tập trung nhiều vào nó vì tôi nghĩ rằng để code của bạn trở nên testable và làm sao để test nó một cách chính xác đã là một thử thách đủ lớn cần phải học trước tiên, mà không cần phải quá quan tâm đến một quy trình phát triển hoàn toàn khác.

Bài viết sẽ không nói nhiều về việc tại sao bạn nên viết test, tại sao testing là cần thiết và những lợi ích của testing. Nếu bạn muốn được thông não về những điều đó, tôi khuyên bạn nên đọc qua cuốn Real-World Solutions for Developing High-Quality PHP Frameworks and Applications của Sebastian Bergmann và ghé thăm blog của bạn thân tôi Chris Hartjes, The Grumpy Programmer’s blog, người mà đã la hét vào cộng đồng PHP trong nhiều năm rằng họ nên viết test. Ngoài ra, trên Viblo cũng đã có rất nhiều bài viết bàn về Unit Test, các bạn rất nên đọc qua:

  • Tản mạn về Testing
  • Vai trò của Unit Test
  • Writing Great Unit Tests

Chuẩn Bị Trước Khi Bắt Đầu

Bài viết giả sử bạn đã cài đặt PHP. Chúng tôi khuyến khích bạn nên sử dụng một máy ảo dành riêng cho môi trường phát triển thay vì thiết lập nó ngay trên hệ điều hành của bạn. Bạn có thể tham khảo các bài viết trên Viblo về sử dụng Docker để tạo môi trường phát triển.

Chúng ta cũng sẽ sử dụng dòng lệnh để chạy PHPUnit… Nếu bạn đang quen với việc làm mọi thứ trên giao diện GUI, thì đây cũng là thời gian cho bạn làm quen với Terminal.

Hướng Dẫn Cài Đặt PHPUnit

PHPUnit 6.2 yêu cầu PHP 7 và các thư viện dom, jsonmbstring, thường đã được cài đặt mặc định khi cài đặt PHP. Bạn có thể kiểm tra bằng lệnh sau:

php -r 'print_r(get_loaded_extensions());'

Nếu chưa có, bạn có thể cài đặt thêm. Ví dụ, trên Ubuntu 16.04 trở lên:

sudo apt install php-xml php-json php-mbstring

Tính năng tạo báo cáo Code Coverage yêu cầu Xdebug (phiên bản 2.5.0 hoặc mới hơn) và thư viện tokenizer (đã có sẵn khi cài PHP).

Cách đơn giản nhất để cài đặt PHPUnit là thông qua Composer. Nếu bạn chưa quen với Composer, hãy tìm hiểu về nó trước khi tiếp tục. Composer giúp quản lý các thư viện và dependencies trong dự án PHP của bạn.

Cài đặt PHPUnit bằng Composer:

composer require --dev phpunit/phpunit ^6.2

Chạy PHPUnit

Sau khi cài đặt xong, bạn sẽ thấy file ./vendor/bin/phpunit. Đây là file thực thi của PHPUnit. Chạy lệnh sau để xem tất cả các tùy chọn help:

./vendor/bin/phpunit

Giao diện dòng lệnh sau khi chạy lệnh ./vendor/bin/phpunit, hiển thị các tùy chọn help của PHPUnit.Giao diện dòng lệnh sau khi chạy lệnh ./vendor/bin/phpunit, hiển thị các tùy chọn help của PHPUnit.

Cấu Trúc Dự Án (Project)

Vì chúng ta đang sử dụng Composer, cần phải cấu trúc project một chút để nó hoạt động với autoloader. Các file mã nguồn sẽ được viết vào thư mục src với namespace là App và unit tests sẽ được viết vào thư mục tests với namespace là Tests.

Cập nhật file composer.json của bạn như sau:

{
    "require-dev": {
        "phpunit/phpunit": "^6.2"
    },
    "autoload": {
        "psr-4": {
            "App\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\": "tests/"
        }
    }
}

Sau đó chạy lệnh để cập nhật autoloader:

composer dump-autoload

Cấu trúc project của chúng ta sẽ như sau:

phpunit-tut/
├── composer.json
├── composer.lock
├── src
├── tests
└── vendor

Cấu Hình File phpunit.xml

Bạn có thể sử dụng các tùy chọn trong dòng lệnh để cấu hình cho PHPUnit, tuy nhiên, sử dụng file cấu hình phpunit.xml sẽ đơn giản hơn.

Trong thư mục gốc của project (phpunit-tut), tạo file phpunit.xml với nội dung sau:

 version="1.0" encoding="UTF-8"?>


Đây là file cấu hình đơn giản nhất, nhưng có hai điểm quan trọng:

  • colors="true": làm cho kết quả test được bôi màu, giúp dễ dàng nhận biết trạng thái của test.
  • “: cho PHPUnit biết nơi lưu các file tests.

Các Quy Ước (Conventions) Khi Viết Unit Test

Trong loạt bài viết này, chúng ta sẽ tuân theo một số quy ước giúp bạn sử dụng PHPUnit dễ dàng hơn. Mặc dù một số quy ước là không bắt buộc, việc tuân thủ chúng sẽ giúp code của bạn trở nên nhất quán và dễ đọc hơn.

Cấu Trúc và Tên File

Các file tests nên ánh xạ với các file mã nguồn tương ứng trong từng thư mục và tên file nên được đặt giống với file mã nguồn cộng thêm từ Test ở sau. Ví dụ, nếu chúng ta có các file mã nguồn như sau:

./src/User.php
./src/Article.php
./src/Controller/UserController.php

Thì các file tests nên được tổ chức như sau:

./tests/UserTest.php
./tests/ArticleTest.php
./tests/Controller/UserControllerTest.php

Tên Lớp (Class)

Tên lớp phải khớp hoàn toàn với tên file. Điều này cũng nên áp dụng với các file mã nguồn khác.

Tên Các Phương Thức (Method) Test

Các phương thức trong các lớp test nên được bắt đầu với từ test (mặc dù bạn có thể sử dụng annotation @test nhưng nên thống nhất viết theo 1 loại cho đồng bộ). Tên phương thức nên có tính mô tả cái gì sẽ được test trong phương thức đó và cũng nên bao gồm tên phương thức đang được test. Tên method không cần phải ngắn hoặc viết tắt.

Ví dụ, bạn đang test phương thức có tên là verifyAccount() và bạn muốn test trường hợp mật khẩu khớp với account, bạn nên đặt tên method trong file test là testVerifyAccountMatchesPasswordGiven().

Sự dài dòng là một lợi ích khi test, bởi vì khi bạn có một trường hợp test failed (thất bại) và bạn sẽ có rất nhiều trường hợp test failed, bạn sẽ thấy được tên method là gì và biết chính xác trường hợp nào đang bị failed.

Phương Thức Public

PHPUnit không thể chạy các method test ở dạng private hay protected, chúng phải là public. Do đó, đối với các method test hay các method helpers (chẳng hạn data provider method) phải ở dạng public. Mục đích của chúng ta là viết test thôi nên không cần phải lo lắng về tính đóng gói.

Kế Thừa PHPUnit

Các class test phải là lớp con cháu của lớp PHPUnitFrameworkTestCase.

Unit Test Đầu Tiên

Unit Test đầu tiên của chúng ta sẽ ngắn gọn. Tạo một file ./tests/StupidTest.php với nội dung sau:

getName();
    }
}

Không có gì đặc biệt cả, chỉ là cho bạn thấy các conventions được áp dụng như thế nào thôi.

Để thử cho biết, bạn muốn chắc chắn một thứ gì đó nó có bằng true hay không (chẳng hạn một hàm nào đó có return true hay không). Assertions là một trong những phương thức cơ bản được dùng trong unit test, và tôi sẽ đề cập nó xuyên suốt trong loạt bài này.

Bây giờ, hãy tạo một method tên là testTrueIsTrue:

 public function testTrueIsTrue() {
        //
    }

Tiếp theo là đến đoạn test code. Bây giờ chỉ đơn giản thế này thôi, chưa cần nghĩ sâu sắc quá:

 public function testTrueIsTrue() {
        $foo = true;
        $this->assertTrue($foo);
    }

Màu Xanh Lá Cây (Green Bar)

Từ thư mục gốc của project, chạy PHPUnit:

./vendor/bin/phpunit

Bạn sẽ thấy cái thanh màu xanh lá cây, nó biểu thị tất cả các test case đã pass. Tuy nhiên, đừng chăm chăm làm sao cho ra được cái màu xanh ấy, không phải viết code chỉ để pass test, mà mục đích ở đây là make your code readable, testable and maintainable.

Ảnh chụp màn hình PHPUnit hiển thị thanh màu xanh lá cây, cho biết tất cả các test đã pass.Ảnh chụp màn hình PHPUnit hiển thị thanh màu xanh lá cây, cho biết tất cả các test đã pass.

Bạn đã chạy 1 file test, 1 test case trong đó có 1 assertion.

Kết Luận

Qua bài này, bạn đã cài được PHPUnit, thiết lập một số cấu hình và chạy unit test đầu tiên.

Xin chúc mừng, bạn đã đi qua bước đầu tiên để đến gần hơn với thiên đường tester!

Trong bài tiếp theo, tôi sẽ giải thích về assertions, giới thiệu một số annotation của PHPUnit trong đó có @dataProvider và giúp bạn viết những unit test thực thụ đầu tiên.