Jak napisać testy jednostkowe w PHP? [Zamknięte]

98

Wszędzie czytałem o tym, jakie są świetne, ale z jakiegoś powodu nie wiem, jak dokładnie mam coś przetestować. Czy ktoś mógłby opublikować przykładowy kod i jak by go przetestował? Jeśli to nie jest zbyt duży problem :)

letgo
źródło
5
Dla równowagi nie ma 2 lub 3 frameworków do testowania jednostkowego dla PHP - lista znajduje się tutaj: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Odpowiedzi:

36

Istnieje trzeci „framework”, który jest zdecydowanie łatwiejszy do nauczenia - nawet łatwiejszy niż prosty test, nazywa się phpt.

Primer można znaleźć tutaj: http://qa.php.net/write-test.php

Edycja: właśnie zobaczyłem Twoją prośbę o przykładowy kod.

Załóżmy, że masz następującą funkcję w pliku o nazwie lib.php :

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

Zwracany jest parametr, który przekazujesz w naprawdę prosty i bezpośredni sposób. Spójrzmy więc na test dla tej funkcji, nazwiemy plik testowy 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"

Krótko mówiąc, podajemy parametrowi $barwartość "Hello World"i var_dump()odpowiadamy na wywołanie funkcji foo().

Aby uruchomić ten test, użyj: pear run-test path/to/foo.phpt

Wymaga to działającej instalacji PEAR w twoim systemie, co jest dość powszechne w większości przypadków. Jeśli potrzebujesz go zainstalować, polecam zainstalować najnowszą dostępną wersję. Jeśli potrzebujesz pomocy w konfiguracji, możesz zapytać (ale zapewnij system operacyjny itp.).

Do
źródło
Nie powinno być run-tests?
Dharman
31

Istnieją dwie struktury, których można używać do testowania jednostkowego. Simpletest i PHPUnit , które wolę. Przeczytaj samouczki dotyczące pisania i uruchamiania testów na stronie głównej PHPUnit. Jest to dość łatwe i dobrze opisane.

okoman
źródło
22

Możesz zwiększyć efektywność testów jednostkowych, zmieniając styl kodowania, aby go dostosować.

Polecam przejrzenie bloga testującego Google , w szczególności post na temat pisania kodu testowalnego .

Preston
źródło
7
Myślę, że wspomniałeś o świetnym poście. Rozpoczęcie Twojej odpowiedzi słowami „Testowanie jednostkowe nie jest zbyt skuteczne” niemal spowodowało, że przegłosowałem, będąc adeptem testów ... Być może, pozytywne przeformułowanie zachęciłoby ludzi do przeczytania artykułu.
xtofl
2
@xtofl zmodyfikował go, aby nieco zwiększyć „pozytywność” :)
icc97
13

Zrobiłem własne, ponieważ nie miałem czasu, aby nauczyć się czyjegoś sposobu robienia rzeczy, napisanie tego zajęło około 20 minut, 10, aby dostosować go do publikowania tutaj.

Unittesting jest dla mnie bardzo przydatny.

jest to trochę długie, ale wyjaśnia samo siebie i na dole jest przykład.

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

To daje:

Test: TestOne był porażką 
/ **
* Ten test ma się nie powieść
** / (linie: 149-152; plik: /Users/kris/Desktop/Testable.php)
Test: TestTwo odniósł sukces 
Kris
źródło
7

Pobierz PHPUnit. Jest bardzo łatwy w użyciu.

Następnie zacznij od bardzo prostych stwierdzeń. Z AssertEquals możesz zrobić wiele, zanim zajmiesz się czymkolwiek innym. To dobry sposób, aby zmoczyć stopy.

Możesz także spróbować najpierw napisać swój test (ponieważ zadałeś swoje pytanie tag TDD), a następnie napisać swój kod. Jeśli nie zrobiłeś tego wcześniej, to otwiera oczy.

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);
}
PartialOrder
źródło
5

W przypadku prostych testów ORAZ dokumentacji php-doctest jest całkiem niezły i jest to naprawdę łatwy sposób na rozpoczęcie, ponieważ nie musisz otwierać oddzielnego pliku. Wyobraź sobie poniższą funkcję:

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

Jeśli teraz uruchomisz ten plik przez phpdt (program uruchamiający wiersz poleceń php-doctest), zostanie uruchomiony 1 test. Doctest jest zawarty w bloku <code>. Doctest wywodzi się z języka Python i nadaje się do podawania przydatnych i wykonalnych przykładów działania kodu. Nie możesz go używać wyłącznie, ponieważ sam kod zaśmiecałby przypadki testowe, ale odkryłem, że jest przydatny razem z bardziej formalną biblioteką tdd - używam phpunit.

Ta pierwsza odpowiedź tutaj ładnie to podsumowuje (nie jest to test unit vs doctest).

Sofia
źródło
1
czy to nie powoduje, że źródło jest trochę zaśmiecone?
Ali Ghanavatian
to może. powinien być używany tylko do pojedynczych prostych testów. podwaja się również jako dokumentacja. jeśli potrzebujesz więcej, użyj testu jednostkowego.
Sofia
2

phpunit to w zasadzie framework do testów jednostkowych defacto dla php. jest też DocTest (dostępny jako pakiet PEAR) i kilka innych. Sam php jest testowany pod kątem regresji i tym podobnych za pomocą testów phpt, które można również uruchomić za pomocą gruszki.

kguest
źródło
2

Testy Codeception są bardzo podobne do zwykłych testów jednostkowych, ale są o wiele potężniejsze w rzeczach, w których potrzebujesz kpiny i krojenia.

Oto przykładowy test kontrolera. Zwróć uwagę, jak łatwo są tworzone kody pośredniczące. Jak łatwo sprawdzić, czy metoda została wywołana.

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

Są też inne fajne rzeczy. Możesz przetestować stan bazy danych, system plików itp.

Davert
źródło
1

Oprócz doskonałych sugestii dotyczących już podanych frameworków testowych, czy tworzysz swoją aplikację z jednym z frameworków WWW PHP, które ma wbudowane automatyczne testowanie, takim jak Symfony lub CakePHP ? Czasami posiadanie miejsca, w którym można po prostu rzucić okiem na swoje metody testowe, zmniejsza tarcie związane z uruchamianiem, które niektórzy ludzie kojarzą z testowaniem automatycznym i TDD.

bradheintz
źródło
1

O wiele za dużo, aby ponownie opublikować tutaj, ale tutaj jest świetny artykuł na temat korzystania z phpt . Obejmuje wiele aspektów związanych z phpt, które są często pomijane, więc warto ją przeczytać, aby poszerzyć swoją wiedzę o php poza zwykłym napisaniem testu. Na szczęście artykuł omawia również pisanie testów!

Główne punkty dyskusji

  1. Odkryj, jak marginalnie udokumentowane aspekty działania PHP (lub prawie każdą część w tym zakresie)
  2. Napisz proste testy jednostkowe dla własnego kodu PHP
  3. Napisz testy jako część rozszerzenia lub w celu przekazania potencjalnego błędu do wewnętrznych lub grup kontroli jakości
quickshiftin
źródło
1

Wiem, że jest tu już wiele informacji, ale ponieważ nadal pojawiają się one w wyszukiwaniach Google, równie dobrze mogę dodać do listy pakiet Chinook Test Suite . Jest to prosta i niewielka platforma testowa.

Możesz z łatwością testować swoje klasy, a także tworzyć pozorowane obiekty. Testy przeprowadzasz za pomocą przeglądarki internetowej i (jeszcze nie) przez konsolę. W przeglądarce możesz określić, jaką klasę testową lub nawet metodę testową uruchomić. Możesz też po prostu uruchomić wszystkie testy.

Zrzut ekranu ze strony github:

Struktura testów jednostkowych Chinook

Podoba mi się sposób, w jaki przeprowadzasz testy. Odbywa się to za pomocą tak zwanych „płynnych asercji”. Przykład:

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

Tworzenie pozorowanych obiektów również jest proste (z płynną składnią):

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

W każdym razie więcej informacji można znaleźć na stronie github z przykładowym kodem:

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

Vivendi
źródło