UNIT TESTING TUTORIAL: VIẾT MỘT KIỂM THỬ CÓ ÍCH VÀ @dataprovider

15/07/2021 40
CODEWELL TESTING

Trong phần trước, chuyên gia từ CO-WELL Asia đã giới thiệu những kiến thức cơ bản về PHPunit – khung kiểm thử đơn vị dành cho ngôn ngữ PHP. Tiếp nối chủ đề này, CO-WELL sẽ hướng dẫn cách viết một kiểm thử có ích và giới thiệu về @dataprovider

Trước hết, ta sẽ làm quen với khái niệm “Các xác nhận – Assertions“:

ASSERTIONS

Một assertion là gì?

Theo wikipedia: Một xác nhận là một vị từ (hàm có giá trị boolean trên không gian trạng thái, thường biểu thị dưới dạng một mệnh đề logic sử dụng biến của chương trình) được đặt trong một chương trình để chỉ ra rằng developer nghĩ rằng vị từ đó luôn luôn đúng ở vị trí đó.

Các xác nhận có thể giúp lập trình viên đọc code, giúp trình biên dịch biên dịch code hoặc giúp chương trình phát hiện các lỗi của chính nó.

 

Assertion value to true – xác nhận luôn đúng

Ở ví dụ trước:

  • Nếu khẳng định true ( if (true== true) ) ta sẽ có một xác nhận đúng một xác nhận đúng (true)
<?php
// ...

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

 

  • Nếu khẳng định false là true (if (false == true)) ta sẽ nhận được một xác nhận sai (false).
<?php
// ...

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

Kết quả:

PHP unit testing

 

  • Điều gì xảy ra nếu ta muốn muốn xác nhận đó  false là false (if (false == false))?
<?php
// ...

    public function testFalseIsFalse()
    {
        $foo = false;
        $this->assertFalse($foo);
    }

Kết quả

PHP unit testing 2
Kiểm thử này sẽ được “pass”, bởi vì khẳng định của chúng ta cho kết quả true, khi phương thức khẳng định là assertFalse().

 

Các assertion có sẵn.

PHPUnit hỗ trợ chúng ta khoảng trên 90 assertion có sẵn, bạn cũng không cần phải nhớ hết nếu bạn dùng một IDE phù hợp thì chỉ cần truy cập thông qua $this->assert*.
Ta cũng không dùng hết tất cả các assertion đấy, mà thường hay sử dụng các assertion sau đây: assertTrue()assertFalse()assertArrayHasKey()assertEquals(), và assertSame(). Tôi cũng chắc rằng tôi chưa sử dụng quá 20% các assertion có sẵn trong thời gian tôi viết unit test cho code của tôi. Vì thế tôi chỉ tập trung vào năm phương thức trên.

Tạo mới assertion.

Trong nhiều trường hợp các assertion của PHPUnit không đáp ứng được yêu cầu của code. Ta hoàn toàn có thể tạo một xác nhận mới cũng đơn giản như việc tạo một phương thức mới. Không có cấu trúc phức tạp cần tìm hiểu – chỉ cần cài đặt phương thức xác nhận trong class kiểm thử của bạn và sử dụng nó. Nếu cần sử dụng nó trong nhiều class hay chuyển nó sang một class cha và các class kiểm thử muốn dùng thì extends class cha.

 

TẠO MỘT KIỂM THỬ CÓ ÍCH

1. Kiểm thử có ích đầu tiên: render một url friendly

Phương thức này sẽ biến một chuỗi thành một chuỗi URL FRIENDLY.

Source code:
Tạo mới một file tại: app/Url.php có nội dung như sau:

<?php
namespace App;

class Url
{
    public function slug($string, $separator = '-', $maxLength = 96)
    {
        $title = iconv('UTF-8', 'ASCII//IGNORE', $string);
        $title = preg_replace("%[^-/+|\w ]%", '', $title);
        $title = strtolower(trim(substr($title, 0, $maxLength), '-'));
        $title = preg_replace("/[\/_|+ -]+/", $separator, $title);

        return $title;
    }
}

Input: String, Output: string.

Với bất kỳ chuỗi nào qua phương thức slug ta cũng được một url friendly và an toàn (url-safe slug).

2. Unit testing

Tạo file kiểm thử tại: tests/UrlTest.php với nội dung như sau:

<?php
namespace Tests;

use PHPUnit\Framework\TestCase;
class UrlTest extends TestCase
{
  //
}

MỘT SỐLƯU Ý

PHP unit testing 3

Nếu chạy phpunit ngay ta sẽ nhận được kết quả như hình trên

Ta thấy 3 kiểm thử, 2 assertions và 1 warnings. Trong đó:

  • Các assertions có từ ví dụ trước
  • Kiểm thử warning vì tests/UrlTest.php không chứa bất kỳ kiểm thử nào. Điều này hoàn toàn bình thường.

Việc này cũng cho ta biết phpunit có chạy file UrlTest.php vì đã đúng quy tắc viết phpunit. Đôi khi lập trình viên sẽ viết xong mới chạy file, nhưng sau đó mới nhận ra là phpunit không chạy file kiểm thử đã viết vào. Vì thế khi tạo file test mới, bạn nên chạy phpunit luôn để chắc chắn rằng phpunit đã chạy file test đó của bạn.

 

3. Tạo một phương thức kiểm thử trong file test vừa viết

Bước tiếp theo để biến màu vàng thành màu xanh lá, đó là tạo ra một phương thức kiểm thử trong file test vừa tạo

Đối với thử nghiệm này ta muốn xác nhận rằng phương thức slug trong app/Url.php trả về một “url-safe slug” vì vậy cần đặt tên cho phương thức kiểm thử phù hợp.

  • Chúng ta bắt đầu với một xác nhận:

‘Lorem Ipsum is simply dummy text.’ => lorem-ipsum-is-simply-dummy-text

  • Để kiểm tra Url::slug() ta cũng cần phải khởi tạo class Url
  • Tiếp theo thực thi phương thức slug để có kết quả gán vào $result.
  • Cuối cùng là xác nhận $result với nội dung mà ta kỳ vọng ($expectedResult) bằng xác nhận: assertEquals()
<?php
namespace Tests;

use PHPUnit\Framework\TestCase;
use App\Url;
class UrlTest extends TestCase
{
  public function testSlugReturnSlugUrl()
  {
      $originalString = 'Lorem Ipsum is simply dummy text.';
      $expectedResult = 'lorem-ipsum-is-simply-dummy-text';

      $url = new Url();
      $result = $url->slug($originalString);
      $this->assertEquals($expectedResult, $result);
  }
}

Chạy vendor/bin/phpunit cho ta kết quả màu xanh lá, tuyệt vời:

PHP unit testing 4

4. Thêm các kịch bản test khác

Mặc dù kiểm thử đầu tiên đã…”trót lọt”, nhưng có một vấn đề là ta mới kiểm tra một chuỗi chứa các ký tự Az, khoảng trắng, và dấu chấm cuối câu.

Điều gì xảy ra nếu ta thêm vào các đối tượng khác như:

  • Một chuỗi chứa số,và/hoặc các ký tự đặc biệt khác (~!@#$%^&*()_+)?
  • Các ký tự không là tiếng Anh
  • Chuỗi trống

Một bộ kiểm thử thích hợp đảm bảo tất cả các khả năng cơ bản được xác nhận, vì vậy ta cần tạo nhiều kịch bản kiểm thử hơn:

<?php
namespace Tests;

use PHPUnit\Framework\TestCase;
use App\Url;
class UrlTest extends TestCase
{
  public function testSlugReturnSlugUrl()
  {
      $originalString = 'Lorem Ipsum is simply dummy text.';
      $expectedResult = 'lorem-ipsum-is-simply-dummy-text';

      $url = new Url();
      $result = $url->slug($originalString);
      $this->assertEquals($expectedResult, $result);
  }

  public function testSlugReturnsExpectedForStringsContainingNumbers()
  {
      $originalString = 'Lorem1 Ipsum20 is10 simply12 dummy34 text55';
      $expectedResult = 'lorem1-ipsum20-is10-simply12-dummy34-text55';

      $url = new URL();

      $result = $url->slug($originalString);

      $this->assertEquals($expectedResult, $result);
  }

  public function testSlugReturnsExpectedForStringsContainingSpecialCharacters()
  {
      $originalString = 'Lorem! @Ipsum#$ %$is() "simply dummy text.';
      $expectedResult = 'lorem-ipsum-is-simply-dummy-text';

      $url = new URL();

      $result = $url->slug($originalString);

      $this->assertEquals($expectedResult, $result);
  }

  public function testSlugReturnsExpectedForStringsContainingNonEnglishCharacters()
  {
      $originalString = 'Giá mít Thái giảm còn vài nghìn đồng một kg';
      $expectedResult = 'gi-mt-thi-gim-cn-vi-nghn-ng-mt-kg';

      $url = new URL();

      $result = $url->slug($originalString);

      $this->assertEquals($expectedResult, $result);
  }

  public function testSlugReturnsExpectedForEmptyStrings()
  {
      $originalString = '';
      $expectedResult = '';

      $url = new URL();

      $result = $url->slug($originalString);

      $this->assertEquals($expectedResult, $result);
  }
}

Kết quả bộ kiểm thử:

PHP unit testing 5

5. Copy code quá nhiều

Nếu bạn đã nhiều năm là một developer bạn sẽ nhận ra vấn đề của mã nguồn kiểm thử là code bị duplicate quá nhiều. Vi phạm nguyên lý lập trình DRY.
May thay, PHPUnit có bộ công cụ tích hợp ở dạng dataprovider annotation

Annotation là gì? tham khảo phpunit.de

Có rất nhiều annotation đi kèm PHPUnit, nhưng bài viết này sẽ đề cập đến dataprovider.

@Dataprovider là gì?

PHPUnit định nghĩa các dataprovider là:

“Một phương pháp kiểm thử có thể chấp nhận các đối số tùy ý. Các đối số này sẽ được cung cấp bởi một phương thức cung cấp dữ liệu.”

Một dataprovider có thể được sử dụng để tạo nhiều bộ thông tin được truyền vào một kiểm thử duy nhất, loại bỏ nhu cầu tạo nhiều kiểm thử trùng lặp như đã làm ở trên.

Thay vì tạo nhiều phương thức kiểm thử, ta chỉ cần tạo một phương thức duy nhất chấp nhận các tham số tương ứng với dữ liệu có thể thay đổi giữa các kiểm thử và tạo phương thức @dataprovider để cung cấp dữ liệu cho kiểm thử:

Ví dụ:

<?php
// ...

/**
 * @dataProvider providerTestFoo
 */
public function testFoo($variableOne, $variableTwo)
{
    //
}

public function providerTestFoo()
{
    return [
        ['test 1, variable one', 'test 1, variable two'],
        ['test 2, variable one', 'test 2, variable two'],
        ['test 3, variable one', 'test 3, variable two'],
        ['test 4, variable one', 'test 4, variable two'],
        ['test 5, variable one', 'test 5, variable two'],
    ];
}

Ở đây tôi đã tạo ra một kiểm thử và một dataprovider duy nhất. Lưu ý @dataProvider comment xác định phương thức nào đang cung cấp dữ liệu cho kiểm thử testFoo().

Một @dataprovider  chỉ bao gồm một array chứa các array với bất kỳ thông tin nào.
Array ngoài cùng đơn giản chỉ là thùng chứa các dữ liệu, nghĩa là @dataprovider phải trả về một array. Array thứ cấp sẽ cung cấp các bộ dữ liệu thực tế cho kiểm thử. Trong ví dụ trên ta có 5 bộ tương ứng với 5 kiểm thử.

Hãy xem @dataprovider áp dụng với kiểm thử hữu ích đầu tiên ở trên như thế nào.

6. Kiểm thử với @dataProvider

Thay thế nội dung của tests/UrlTest.php như sau:

<?php
namespace Tests;

use PHPUnit\Framework\TestCase;
use App\Url;

class UrlTest extends TestCase
{
    /**
     * Testing slug method
     * @param string $originalString String to be slug
     * @param string $expectedResult What we expect our slug result to be
     *
     * @dataProvider providerTestSlugReturnsSlugUrl
     */
    public function testSlugReturnSlugUrl($originalString, $expectedResult)
    {
        $url = new Url();

        $result = $url->slug($originalString);

        $this->assertEquals($expectedResult, $result);
    }

    public function providerTestSlugReturnsSlugUrl()
    {
        return [
            [
                'Lorem Ipsum is simply dummy text.',
                'lorem-ipsum-is-simply-dummy-text'
            ],
            [
                'LOREM IPSUM IS SIMPLY DUMMY TEXT.',
                'lorem-ipsum-is-simply-dummy-text'
            ],
            [
                'Lorem1 Ipsum20 is10 simply12 dummy34 text55',
                'lorem1-ipsum20-is10-simply12-dummy34-text55'
            ],
            [
                'Lorem! @Ipsum#$ %$is() "simply dummy text.',
                'lorem-ipsum-is-simply-dummy-text'
            ],
            [
                'Giá mít Thái giảm còn vài nghìn đồng một kg',
                'gi-mt-thi-gim-cn-vi-nghn-ng-mt-kg'
            ],
            [
                '',
                ''
            ]
        ];
    }
}

Chạy kiểm thử: vendor/bin/phpunit

PHP unit testing 6

Kết quả: pass qua nhiều bộ kiểm thử nhưng code không bị duplicate.

Tác giả: Trịnh Văn Thành

TỔNG KẾT

Bài này chúng ta đã thảo luận về các xác nhận (assertions), viết kiểm thử hữu ích đầu tiên, và hơn nữa chúng ta tìm hiểu một annotation rất hữu hiệu là @dataProvider. Mặc dù còn phải đọc / làm nhiều để tìm hiểu. Tuy nhiên bạn hoàn toàn có thể bắt đầu với những kiểm thử không quá phức tạp để dần nâng cao trình độ.

Đừng quên theo dõi chuyên mục CODEWELL trên website CO-WELL Asia để đón đọc những bài viết công nghệ bổ ích nhé!