Làm cách nào để viết các bài kiểm tra đơn vị trong PHP? [đóng cửa]


97

Tôi đã đọc khắp nơi về mức độ tuyệt vời của chúng, nhưng vì lý do nào đó mà tôi dường như không thể tìm ra cách chính xác mình phải thử nghiệm thứ gì đó. Có lẽ ai đó có thể đăng một đoạn mã ví dụ và cách họ kiểm tra nó? Nếu nó không quá rắc rối :)


5
Đối với số dư, không có 2, hoặc 3 đơn vị thử nghiệm khuôn khổ cho PHP - có một danh sách ở đây: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Câu trả lời:


36

Có một "khuôn khổ" thứ 3, dễ học hơn - thậm chí còn dễ hơn cả Simple Test, nó được gọi là phpt.

Có thể tìm thấy mồi ở đây: http://qa.php.net/write-test.php

Chỉnh sửa: Vừa thấy yêu cầu của bạn cho mã mẫu.

Giả sử bạn có hàm sau trong một tệp có tên lib.php :

<?php
function foo($bar)
{
  return $bar;
}
?>

Thực sự đơn giản và dễ hiểu, tham số bạn truyền vào sẽ được trả về. Vì vậy, hãy xem xét một bài kiểm tra cho chức năng này, chúng tôi sẽ gọi tệp kiểm tra foo.phpt :

--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"

Tóm lại, chúng tôi cung cấp tham số $barvới giá trị "Hello World"và chúng tôi var_dump()là phản hồi của lời gọi hàm tới foo().

Để chạy thử nghiệm này, hãy sử dụng: pear run-test path/to/foo.phpt

Điều này yêu cầu cài đặt PEAR hoạt động trên hệ thống của bạn, điều này khá phổ biến trong hầu hết các trường hợp. Nếu bạn cần cài đặt nó, tôi khuyên bạn nên cài đặt phiên bản mới nhất có sẵn. Trong trường hợp bạn cần trợ giúp để thiết lập, vui lòng hỏi (nhưng cung cấp hệ điều hành, v.v.).


Có nên không run-tests?
Dharman

30

Có hai khuôn khổ bạn có thể sử dụng để thử nghiệm đơn vị. SimpletestPHPUnit , mà tôi thích hơn. Đọc các hướng dẫn về cách viết và chạy các bài kiểm tra trên trang chủ của PHPUnit. Nó khá dễ dàng và được mô tả tốt.


21

Bạn có thể làm cho kiểm thử đơn vị hiệu quả hơn bằng cách thay đổi kiểu mã hóa của mình để phù hợp với nó.

Tôi khuyên bạn nên duyệt qua Blog thử nghiệm của Google , đặc biệt là bài đăng về Viết mã có thể kiểm tra .


7
Tôi nghĩ rằng bạn đã đề cập đến một bài viết tuyệt vời. Mặc dù vậy, việc bắt đầu câu trả lời của bạn bằng 'Unit testing không hiệu quả lắm' khiến tôi không đồng ý lắm, tuy nhiên, là một người am hiểu về test ... Có thể, diễn đạt lại theo cách tích cực sẽ khuyến khích mọi người đọc bài báo.
xtofl

2
@xtofl chỉnh sửa nó để nâng cao 'tính tích cực' hơi :)
icc97

13

Tôi đã cố gắng của riêng mình vì tôi không có thời gian để học cách làm việc của ai đó, điều này mất khoảng 20 phút để viết lên, 10 để điều chỉnh nó để đăng ở đây.

Unittesting rất hữu ích đối với tôi.

cái này hơi dài nhưng nó tự giải thích và có ví dụ ở phía dưới.

/**
 * Provides Assertions
 **/
class Assert
{
    public static function AreEqual( $a, $b )
    {
        if ( $a != $b )
        {
            throw new Exception( 'Subjects are not equal.' );
        }
    }
}

/**
 * Provides a loggable entity with information on a test and how it executed
 **/
class TestResult
{
    protected $_testableInstance = null;

    protected $_isSuccess = false;
    public function getSuccess()
    {
        return $this->_isSuccess;
    }

    protected $_output = '';
    public function getOutput()
    {
        return $_output;
    }
    public function setOutput( $value )
    {
        $_output = $value;
    }

    protected $_test = null;
    public function getTest()
    {
        return $this->_test;
    }

    public function getName()
    {
        return $this->_test->getName();
    }
    public function getComment()
    {
        return $this->ParseComment( $this->_test->getDocComment() );
    }

    private function ParseComment( $comment )
    {
        $lines = explode( "\n", $comment );
        for( $i = 0; $i < count( $lines ); $i ++ )
        {
            $lines[$i] = trim( $lines[ $i ] );
        }
        return implode( "\n", $lines );
    }

    protected $_exception = null;
    public function getException()
    {
        return $this->_exception;
    }

    static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
    {
        $result = new self();
        $result->_isSuccess = false;
        $result->testableInstance = $object;
        $result->_test = $test;
        $result->_exception = $exception;

        return $result;
    }
    static public function CreateSuccess( Testable $object, ReflectionMethod $test )
    {
        $result = new self();
        $result->_isSuccess = true;
        $result->testableInstance = $object;
        $result->_test = $test;

        return $result;
    }
}

/**
 * Provides a base class to derive tests from
 **/
abstract class Testable
{
    protected $test_log = array();

    /**
     * Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
     **/
    protected function Log( TestResult $result )
    {
        $this->test_log[] = $result;

        printf( "Test: %s was a %s %s\n"
            ,$result->getName()
            ,$result->getSuccess() ? 'success' : 'failure'
            ,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
                ,$result->getComment()
                ,$result->getTest()->getStartLine()
                ,$result->getTest()->getEndLine()
                ,$result->getTest()->getFileName()
                )
            );

    }
    final public function RunTests()
    {
        $class = new ReflectionClass( $this );
        foreach( $class->GetMethods() as $method )
        {
            $methodname = $method->getName();
            if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
            {
                ob_start();
                try
                {
                    $this->$methodname();
                    $result = TestResult::CreateSuccess( $this, $method );
                }
                catch( Exception $ex )
                {
                    $result = TestResult::CreateFailure( $this, $method, $ex );
                }
                $output = ob_get_clean();
                $result->setOutput( $output );
                $this->Log( $result );
            }
        }
    }
}

/**
 * a simple Test suite with two tests
 **/
class MyTest extends Testable
{
    /**
     * This test is designed to fail
     **/
    public function TestOne()
    {
        Assert::AreEqual( 1, 2 );
    }

    /**
     * This test is designed to succeed
     **/
    public function TestTwo()
    {
        Assert::AreEqual( 1, 1 );
    }
}

// this is how to use it.
$test = new MyTest();
$test->RunTests();

Kết quả này là:

Kiểm tra: TestOne là một thất bại 
/ **
* Bài kiểm tra này được thiết kế để không đạt
** / (dòng: 149-152; tệp: /Users/kris/Desktop/Testable.php)
Kiểm tra: TestTwo thành công 

7

Nhận PHPUnit. Nó rất dễ sử dụng.

Sau đó, hãy bắt đầu với những khẳng định rất đơn giản. Bạn có thể làm rất nhiều với AssertEquals trước khi tham gia vào bất kỳ điều gì khác. Đó là một cách tốt để làm ướt chân của bạn.

Bạn cũng có thể muốn thử viết bài kiểm tra của mình trước (vì bạn đã đặt thẻ TDD cho câu hỏi của mình) và sau đó viết mã của bạn. Nếu bạn chưa làm điều này trước khi nó mở rộng tầm mắt.

require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';

class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
    private $ClassYouWantToTest;

   protected function setUp ()
    {
        parent::setUp();
        $this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
    }

    protected function tearDown ()
    {
        $this->ClassYouWantToTest = null;
        parent::tearDown();
    }

    public function __construct ()
    {   
        // not really needed
    }

    /**
     * Tests ClassYouWantToTest->methodFoo()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);

    /**
     * Tests ClassYouWantToTest->methodBar()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}

5

Đối với các bài kiểm tra VÀ tài liệu đơn giản, php-doctest khá hay và đó là một cách thực sự dễ dàng để bắt đầu vì bạn không phải mở một tệp riêng biệt. Hãy tưởng tượng chức năng bên dưới:

/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
    return $a + $b;   
}

Nếu bây giờ bạn chạy tệp này thông qua phpdt (trình chạy dòng lệnh của php-doctest) thì 1 bài kiểm tra sẽ được chạy. Học thuyết được chứa bên trong khối <code>. Doctest có nguồn gốc từ python và rất phù hợp để đưa ra các ví dụ hữu ích và có thể chạy được về cách mã hoạt động. Bạn không thể sử dụng nó độc quyền vì bản thân mã sẽ chứa nhiều test case nhưng tôi thấy rằng nó hữu ích cùng với thư viện tdd chính thức hơn - tôi sử dụng phpunit.

Câu trả lời đầu tiên này ở đây tóm tắt nó một cách độc đáo (nó không phải đơn vị so với học thuyết).


1
nó không làm cho nguồn hơi lộn xộn?
Ali Ghanavatian

nó có thể. nó chỉ nên được sử dụng cho các thử nghiệm đơn giản. cũng tăng gấp đôi dưới dạng tài liệu. nếu bạn cần kiểm tra đơn vị sử dụng nhiều hơn.
Sofia

2

phpunit là khung thử nghiệm đơn vị defacto cho php. cũng có DocTest (có sẵn dưới dạng gói PEAR) và một số ứng dụng khác. Bản thân php được kiểm tra các hồi quy và tương tự thông qua các bài kiểm tra phpt cũng có thể được chạy qua lê.


2

Các bài kiểm tra mã hóa giống như các bài kiểm tra đơn vị thông thường nhưng mạnh hơn nhiều ở những thứ mà bạn cần chế nhạo và khai thác.

Đây là thử nghiệm bộ điều khiển mẫu. Chú ý cách tạo sơ khai dễ dàng. Bạn dễ dàng kiểm tra phương thức đã được gọi như thế nào.

<?php
use Codeception\Util\Stub as Stub;

const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;

class UserControllerCest {
public $class = 'UserController';


public function show(CodeGuy $I) {
    // prepare environment
    $I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
    $I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
    $I->setProperty($controller, 'db', $db);

    $I->executeTestedMethodOn($controller, VALID_USER_ID)
        ->seeResultEquals(true)
        ->seeMethodInvoked($controller, 'render');

    $I->expect('it will render 404 page for non existent user')
        ->executeTestedMethodOn($controller, INVALID_USER_ID)
        ->seeResultNotEquals(true)
        ->seeMethodInvoked($controller, 'render404','User not found')
        ->seeMethodNotInvoked($controller, 'render');
}
}

Ngoài ra còn có những thứ hay ho khác. Bạn có thể kiểm tra trạng thái cơ sở dữ liệu, hệ thống tệp, v.v.


1

Bên cạnh những gợi ý tuyệt vời về các khuôn khổ thử nghiệm đã được đưa ra, bạn có đang xây dựng ứng dụng của mình bằng một trong các khuôn khổ web PHP đã tích hợp sẵn tính năng kiểm tra tự động, chẳng hạn như Symfony hoặc CakePHP ? Đôi khi có một nơi để chỉ bỏ qua các phương pháp thử nghiệm của bạn làm giảm sự khó khăn khi bắt đầu mà một số người liên kết với thử nghiệm tự động và TDD.


1

Quá nhiều để đăng lại ở đây, nhưng đây là một bài viết tuyệt vời về cách sử dụng phpt . Nó bao gồm một số khía cạnh xung quanh phpt mà thường bị bỏ qua, vì vậy nó có thể đáng đọc để mở rộng kiến ​​thức của bạn về php ngoài việc viết một bài kiểm tra. May mắn thay, bài báo cũng thảo luận về các bài kiểm tra viết!

Các điểm chính của cuộc thảo luận

  1. Khám phá cách thức hoạt động của các khía cạnh được ghi chép lại bằng tài liệu của PHP (hoặc gần như bất kỳ phần nào cho vấn đề đó)
  2. Viết các bài kiểm tra đơn vị đơn giản cho mã PHP của riêng bạn
  3. Viết các bài kiểm tra như một phần của tiện ích mở rộng hoặc để truyền đạt một lỗi tiềm ẩn cho các bên nội bộ hoặc nhóm QA

1

Tôi biết đã có rất nhiều thông tin ở đây, nhưng vì thông tin này vẫn hiển thị trên các tìm kiếm của Google nên tôi cũng có thể thêm Chinook Test Suite vào danh sách. Nó là một khung thử nghiệm đơn giản và nhỏ.

Bạn có thể dễ dàng kiểm tra các lớp của mình với nó và cũng có thể tạo các đối tượng giả. Bạn chạy các bài kiểm tra thông qua trình duyệt web và (chưa) thông qua bảng điều khiển. Trong trình duyệt, bạn có thể chỉ định lớp kiểm tra nào hoặc thậm chí là phương pháp kiểm tra nào để chạy. Hoặc bạn có thể chỉ cần chạy tất cả các bài kiểm tra.

Ảnh chụp màn hình từ trang github:

Khung kiểm tra đơn vị Chinook

Điều tôi thích về nó là cách bạn khẳng định các bài kiểm tra. Điều này được thực hiện với cái gọi là "khẳng định trôi chảy". Thí dụ:

$this->Assert($datetime)->Should()->BeAfter($someDatetime);

Và việc tạo các đối tượng giả cũng rất dễ dàng (với cú pháp tương tự thông thạo):

$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');

Dù sao, bạn cũng có thể tìm thấy thêm thông tin trên trang github với ví dụ về mã:

https://github.com/w00/Chinook-TestSuite

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.