ฉันจะเขียนการทดสอบหน่วยใน PHP ได้อย่างไร [ปิด]


99

ฉันได้อ่านทุกที่เกี่ยวกับความยอดเยี่ยม แต่ด้วยเหตุผลบางอย่างฉันไม่สามารถเข้าใจได้ว่าฉันควรจะทดสอบบางสิ่งอย่างไร อาจมีคนโพสต์โค้ดตัวอย่างและจะทดสอบอย่างไร ถ้ายังไม่เดือดร้อนมากนัก :)


5
เพื่อความสมดุลไม่มีเฟรมเวิร์กการทดสอบ 2 หรือ 3 หน่วยสำหรับ PHP - มีรายการอยู่ที่นี่: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

คำตอบ:


37

มี "เฟรมเวิร์ก" ที่ 3 ซึ่งง่ายต่อการเรียนรู้ - ง่ายกว่าSimple Test ด้วยซ้ำเรียกว่า phpt

สามารถดูไพรเมอร์ได้ที่นี่: http://qa.php.net/write-test.php

แก้ไข:เพิ่งเห็นคำขอรหัสตัวอย่างของคุณ

สมมติว่าคุณมีฟังก์ชันต่อไปนี้ในไฟล์ชื่อlib.php :

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

เรียบง่ายและตรงไปตรงมาจริงๆพารามิเตอร์ที่คุณส่งผ่านจะถูกส่งกลับ ลองดูการทดสอบสำหรับฟังก์ชั่นนี้เราจะเรียกไฟล์ทดสอบว่า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"

สรุปเราให้พารามิเตอร์$barที่มีค่า"Hello World"และเราvar_dump()ตอบสนองของฟังก์ชั่นการโทรไปยังfoo()ตอบสนองของฟังก์ชั่นการโทรไปยัง

ในการเรียกใช้การทดสอบนี้ให้ใช้: pear run-test path/to/foo.phpt

สิ่งนี้ต้องการการติดตั้ง PEAR ที่ใช้งานได้ในระบบของคุณซึ่งเป็นเรื่องปกติธรรมดาในสถานการณ์ส่วนใหญ่ หากคุณจำเป็นต้องติดตั้งฉันขอแนะนำให้ติดตั้งเวอร์ชันล่าสุดที่มี ในกรณีที่คุณต้องการความช่วยเหลือในการตั้งค่าอย่าลังเลที่จะถาม (แต่ให้ OS ฯลฯ )


ควรrun-testsหรือไม่?
Dharman

32

มีสองกรอบที่คุณสามารถใช้สำหรับการทดสอบหน่วย SimpletestและPHPUnitซึ่งฉันชอบ อ่านบทแนะนำเกี่ยวกับวิธีการเขียนและเรียกใช้การทดสอบในหน้าแรกของ PHPUnit ค่อนข้างง่ายและอธิบายได้ดี


22

คุณสามารถทำให้การทดสอบหน่วยมีประสิทธิภาพมากขึ้นโดยการเปลี่ยนรูปแบบการเข้ารหัสเพื่อรองรับ

ขอแนะนำให้เรียกดูบล็อกการทดสอบของ Googleโดยเฉพาะอย่างยิ่งโพสต์เกี่ยวกับการเขียนโค้ดที่ทดสอบได้


7
ฉันคิดว่าคุณพูดถึงโพสต์ที่ดี การเริ่มต้นคำตอบของคุณด้วย 'การทดสอบหน่วยไม่ได้ผลมาก' เกือบจะทำให้ฉันลงคะแนนแม้ว่าการเป็นผู้เชี่ยวชาญด้านการทดสอบ ...
xtofl

2
@xtofl แก้ไขเพื่อเพิ่ม 'positivity' เล็กน้อย :)
icc97

13

ฉันรีดเองเพราะฉันไม่มีเวลาเรียนรู้วิธีทำสิ่งต่างๆของคนอื่นใช้เวลาประมาณ 20 นาทีในการเขียน 10 นาทีเพื่อปรับใช้สำหรับการโพสต์ที่นี่

Unittesting เป็นอย่างมากมีประโยชน์กับผม

มันค่อนข้างยาว แต่อธิบายตัวเองและมีตัวอย่างอยู่ด้านล่าง

/**
 * 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();

ผลลัพธ์นี้:

ทดสอบ: TestOne ล้มเหลว 
/ **
* การทดสอบนี้ออกแบบมาให้ล้มเหลว
** / (บรรทัด: 149-152; ไฟล์: /Users/kris/Desktop/Testable.php)
ทดสอบ: TestTwo ประสบความสำเร็จ 

7

รับ PHPUnit ใช้งานง่ายมาก

จากนั้นเริ่มต้นด้วยการยืนยันที่ง่ายมาก คุณสามารถทำได้มากมายด้วย AssertEquals ก่อนที่คุณจะเข้าสู่สิ่งอื่น นั่นเป็นวิธีที่ดีในการทำให้เท้าเปียก

คุณอาจต้องการลองเขียนแบบทดสอบของคุณก่อน (เนื่องจากคุณให้แท็ก TDD แก่คำถามของคุณ) จากนั้นจึงเขียนโค้ดของคุณ หากคุณยังไม่เคยทำมาก่อนก็เป็นการเปิดหูเปิดตา

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

สำหรับการทดสอบและเอกสารอย่างง่ายphp-doctestนั้นค่อนข้างดีและเป็นวิธีที่ง่ายมากในการเริ่มต้นเนื่องจากคุณไม่ต้องเปิดไฟล์แยกต่างหาก ลองนึกภาพฟังก์ชั่นด้านล่าง:

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

หากคุณเรียกใช้ไฟล์นี้ผ่าน phpdt (รันบรรทัดคำสั่งของ php-doctest) 1 การทดสอบจะถูกเรียกใช้ หลักคำสอนอยู่ในบล็อก <code> Doctest มีต้นกำเนิดใน python และเหมาะสำหรับการให้ตัวอย่างที่เป็นประโยชน์และสามารถรันได้ว่าโค้ดควรจะทำงานอย่างไร คุณไม่สามารถใช้งานได้โดยเฉพาะเนื่องจากโค้ดนั้นจะทิ้งไปพร้อมกับกรณีทดสอบ แต่ฉันพบว่ามีประโยชน์ควบคู่ไปกับไลบรารี tdd ที่เป็นทางการมากขึ้น - ฉันใช้ phpunit

คำตอบที่ 1 นี้ที่นี่ผลรวมมันได้เป็นอย่างดี (มันไม่ได้เป็นหน่วย VS doctest)


1
มันไม่ทำให้แหล่งข้อมูลรกไปหน่อยเหรอ?
Ali Ghanavatian

มันสามารถ ควรใช้สำหรับการทดสอบอย่างง่ายเพียงครั้งเดียว ยังเพิ่มเป็นสองเท่าของเอกสาร หากคุณต้องการใช้การทดสอบหน่วยเพิ่มเติม
Sofia

2

phpunit เป็นกรอบการทดสอบหน่วย defacto สำหรับ php นอกจากนี้ยังมีDocTest (มีให้ในแพ็คเกจ PEAR) และอื่น ๆ อีกเล็กน้อย php นั้นได้รับการทดสอบสำหรับการถดถอยและสิ่งที่คล้ายกันผ่านการทดสอบ phptซึ่งสามารถเรียกใช้ผ่านลูกแพร์ได้


2

การทดสอบ Codeception นั้นเหมือนกับการทดสอบหน่วยทั่วไป แต่มีประสิทธิภาพมากในสิ่งที่คุณต้องการการเยาะเย้ยและการขีดฆ่า

นี่คือตัวอย่างการทดสอบคอนโทรลเลอร์ สังเกตว่าต้นขั้วถูกสร้างขึ้นได้ง่ายเพียงใด คุณตรวจสอบวิธีการเรียกใช้ง่ายเพียงใด

<?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');
}
}

นอกจากนี้ยังมีสิ่งที่น่าสนใจอื่น ๆ คุณสามารถทดสอบสถานะฐานข้อมูลระบบไฟล์ ฯลฯ


1

นอกเหนือจากคำแนะนำที่ยอดเยี่ยมเกี่ยวกับกรอบการทดสอบที่ให้ไว้แล้วคุณกำลังสร้างแอปพลิเคชันของคุณด้วยเว็บเฟรมเวิร์ก PHP ที่มีการทดสอบอัตโนมัติในตัวเช่นSymfonyหรือCakePHPหรือไม่? บางครั้งการมีสถานที่สำหรับวางวิธีการทดสอบของคุณจะช่วยลดแรงเสียดทานในการเริ่มต้นบางคนเชื่อมโยงกับการทดสอบอัตโนมัติและ TDD


1

วิธีที่มากเกินไปที่จะโพสต์อีกครั้งที่นี่ แต่นี่เป็นบทความดี ๆเกี่ยวกับการใช้phpt ครอบคลุมหลายแง่มุมรอบ ๆphptที่มักถูกมองข้ามดังนั้นจึงควรค่าแก่การอ่านเพื่อเพิ่มพูนความรู้เกี่ยวกับ php นอกเหนือจากการเขียนแบบทดสอบ โชคดีที่บทความนี้ยังกล่าวถึงการทดสอบการเขียน!

ประเด็นหลักของการสนทนา

  1. ค้นพบวิธีการทำงานของ PHP ที่ได้รับการบันทึกไว้เล็กน้อย (หรือส่วนใดส่วนหนึ่งสำหรับเรื่องนั้น)
  2. เขียนการทดสอบหน่วยง่ายๆสำหรับโค้ด PHP ของคุณเอง
  3. เขียนการทดสอบเป็นส่วนหนึ่งของส่วนขยายหรือเพื่อถ่ายทอดข้อบกพร่องที่อาจเกิดขึ้นไปยังกลุ่มภายในหรือกลุ่ม QA

1

ฉันรู้ว่ามีข้อมูลมากมายที่นี่แล้ว แต่เนื่องจากสิ่งนี้ยังคงปรากฏในการค้นหาของ Google ฉันจึงอาจเพิ่มChinook Test Suiteลงในรายการ เป็นกรอบการทดสอบที่เรียบง่ายและมีขนาดเล็ก

คุณสามารถทดสอบชั้นเรียนของคุณได้อย่างง่ายดายและยังสร้างวัตถุจำลอง คุณเรียกใช้การทดสอบผ่านเว็บเบราว์เซอร์และ(ยังไม่)ผ่านคอนโซล ในเบราว์เซอร์คุณสามารถระบุประเภทการทดสอบหรือวิธีการทดสอบที่จะเรียกใช้ หรือคุณสามารถเรียกใช้การทดสอบทั้งหมด

ภาพหน้าจอจากหน้า github:

กรอบการทดสอบหน่วย Chinook

สิ่งที่ฉันชอบคือวิธีที่คุณยืนยันการทดสอบ ซึ่งเรียกว่า "การยืนยันอย่างคล่องแคล่ว" ตัวอย่าง:

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

และการสร้างวัตถุจำลองก็เป็นเรื่องง่ายเช่นกัน (ด้วยไวยากรณ์ที่คล่องแคล่ว):

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

อย่างไรก็ตามข้อมูลเพิ่มเติมสามารถพบได้ในหน้า github พร้อมตัวอย่างรหัสเช่นกัน:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.