Bagaimana cara menulis pengujian unit di PHP? [Tutup]


97

Saya telah membaca di mana-mana tentang betapa hebatnya mereka, tetapi untuk beberapa alasan saya sepertinya tidak tahu bagaimana tepatnya saya harus menguji sesuatu. Bisakah seseorang mungkin memposting sepotong kode contoh dan bagaimana mereka akan mengujinya? Jika tidak terlalu merepotkan :)


5
Untuk keseimbangan, tidak ada kerangka pengujian 2, atau 3 unit untuk PHP - ada daftarnya di sini: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Jawaban:


36

Ada "framework" ke-3, yang jauh lebih mudah dipelajari - bahkan lebih mudah daripada Simple Test, disebut phpt.

Primer dapat ditemukan di sini: http://qa.php.net/write-test.php

Edit: Baru saja melihat permintaan Anda untuk kode contoh.

Anggaplah Anda memiliki fungsi berikut dalam file bernama lib.php :

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

Sangat sederhana dan lurus ke depan, parameter yang Anda berikan, dikembalikan. Jadi mari kita lihat pengujian untuk fungsi ini, kita akan memanggil file pengujian 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"

Singkatnya, kami menyediakan parameter $bardengan nilai "Hello World"dan kami var_dump()menanggapi panggilan fungsi ke foo().

Untuk menjalankan tes ini, gunakan: pear run-test path/to/foo.phpt

Ini membutuhkan penginstalan PEAR yang berfungsi di sistem Anda, yang cukup umum dalam banyak situasi. Jika Anda perlu menginstalnya, saya sarankan untuk menginstal versi terbaru yang tersedia. Jika Anda membutuhkan bantuan untuk mengaturnya, silakan bertanya (tetapi sediakan OS, dll.).


Bukankah seharusnya begitu run-tests?
Dharman

30

Ada dua kerangka kerja yang dapat Anda gunakan untuk pengujian unit. Paling sederhana dan PHPUnit , yang saya suka. Baca tutorial tentang cara menulis dan menjalankan pengujian di beranda PHPUnit. Ini cukup mudah dan dijelaskan dengan baik.


21

Anda dapat membuat pengujian unit lebih efektif dengan mengubah gaya pengkodean Anda untuk mengakomodasi itu.

Saya sarankan untuk menjelajahi Blog Pengujian Google , khususnya posting tentang Menulis Kode yang Dapat Diuji .


7
Saya pikir Anda menyebutkan pos yang bagus. Memulai jawaban Anda dengan 'Pengujian unit tidak terlalu efektif' hampir membuat saya tidak setuju, meskipun, menjadi ahli tes ... Mungkin, mengulang dengan cara yang positif akan mendorong orang untuk membaca artikel.
xtofl

2
@xtofl mengeditnya untuk meningkatkan 'kepositifan' sedikit :)
icc97

13

Saya menggulung sendiri karena saya tidak punya waktu untuk mempelajari cara orang lain melakukan sesuatu, ini membutuhkan waktu sekitar 20 menit untuk menulis, 10 untuk menyesuaikannya untuk posting di sini.

Unittesting sangat berguna bagi saya.

ini agak panjang tapi menjelaskan dirinya sendiri dan ada contoh di bawah.

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

Output ini:

Tes: TestOne gagal 
/ **
* Tes ini dirancang untuk gagal
** / (baris: 149-152; file: /Users/kris/Desktop/Testable.php)
Test: TestTwo sukses 

7

Dapatkan PHPUnit. Sangat mudah digunakan.

Kemudian mulailah dengan pernyataan yang sangat sederhana. Anda dapat melakukan banyak hal dengan AssertEquals sebelum Anda masuk ke hal lain. Itu cara yang bagus untuk membasahi kaki Anda.

Anda mungkin juga ingin mencoba menulis tes Anda terlebih dahulu (karena Anda memberi pertanyaan Anda tag TDD) dan kemudian menulis kode Anda. Jika Anda belum pernah melakukan ini sebelumnya, ini adalah pembuka mata.

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

Untuk tes dan dokumentasi sederhana, php-doctest cukup bagus dan ini adalah cara yang sangat mudah untuk memulai karena Anda tidak perlu membuka file terpisah. Bayangkan fungsi di bawah ini:

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

Jika Anda sekarang menjalankan file ini melalui phpdt (command-line runner of php-doctest) 1 tes akan dijalankan. Doctest berada di dalam blok <code>. Doctest berasal dari python dan baik untuk memberikan contoh yang berguna & dapat dijalankan tentang bagaimana kode seharusnya bekerja. Anda tidak dapat menggunakannya secara eksklusif karena kode itu sendiri akan mengotori kasus uji tetapi saya telah menemukan bahwa itu berguna bersama dengan perpustakaan tdd yang lebih formal - saya menggunakan phpunit.

Jawaban ini 1st sini merangkum baik (itu bukan satuan vs doctest).


1
bukankah itu membuat sumbernya sedikit berantakan?
Ali Ghanavatian

Bisa. itu hanya boleh digunakan untuk satu tes sederhana. juga berfungsi sebagai dokumentasi. jika Anda membutuhkan lebih banyak gunakan unit test.
Sofia

2

phpunit cukup banyak kerangka pengujian unit defacto untuk php. ada juga DocTest (tersedia sebagai paket PEAR) dan beberapa lainnya. php sendiri diuji untuk regresi dan sejenisnya melalui tes phpt yang juga dapat dijalankan melalui pir.


2

Tes codeception sangat mirip dengan tes unit umum tetapi jauh lebih kuat dalam hal-hal di mana Anda perlu mengejek dan mematikan.

Berikut adalah contoh uji pengontrol. Perhatikan betapa mudahnya stub dibuat. Seberapa mudah Anda memeriksa metode itu dipanggil.

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

Juga ada hal keren lainnya. Anda dapat menguji status database, sistem file, dll.


1

Selain saran bagus tentang framework pengujian yang telah diberikan, apakah Anda membangun aplikasi dengan salah satu framework web PHP yang memiliki pengujian otomatis bawaan, seperti Symfony atau CakePHP ? Terkadang memiliki tempat untuk memasukkan metode pengujian Anda mengurangi gesekan start-up yang diasosiasikan beberapa orang dengan pengujian otomatis dan TDD.


1

Terlalu banyak untuk memposting ulang di sini, tapi berikut adalah artikel bagus tentang penggunaan phpt . Ini mencakup sejumlah aspek seputar phpt yang sering diabaikan, sehingga layak dibaca untuk memperluas pengetahuan Anda tentang php lebih dari sekadar menulis tes. Untungnya artikel tersebut juga membahas tentang tes menulis!

Poin utama dari diskusi

  1. Temukan seberapa sedikit aspek terdokumentasi dari pekerjaan PHP (atau hampir semua bagian dalam hal ini)
  2. Tulis pengujian unit sederhana untuk kode PHP Anda sendiri
  3. Tulis tes sebagai bagian dari ekstensi atau untuk menyampaikan potensi bug ke internal atau grup QA

1

Saya tahu sudah ada banyak info di sini, tetapi karena ini masih muncul di pencarian Google, saya mungkin juga menambahkan Chinook Test Suite ke daftar. Ini adalah kerangka pengujian yang sederhana dan kecil.

Anda dapat dengan mudah menguji kelas Anda dengannya dan juga membuat objek tiruan. Anda menjalankan pengujian melalui browser web dan (belum) melalui konsol. Di browser Anda dapat menentukan kelas pengujian apa atau bahkan metode pengujian apa yang akan dijalankan. Atau Anda bisa menjalankan semua tes.

Tangkapan layar dari halaman github:

Kerangka kerja Uji Unit Chinook

Yang saya suka tentang ini adalah cara Anda melakukan pengujian. Ini dilakukan dengan apa yang disebut "pernyataan lancar". Contoh:

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

Dan membuat objek tiruan juga sangat mudah (dengan sintaksis seperti fasih):

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

Bagaimanapun, info lebih lanjut dapat ditemukan di halaman github dengan contoh kode juga:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.