Як записати одиничні тести на PHP? [зачинено]


97

Я читав всюди про те, наскільки вони великі, але чомусь не можу зрозуміти, як саме я повинен щось перевірити. Може хтось може опублікувати фрагмент прикладу коду і як вони це перевірять? Якщо це не надто багато проблем :)


5
Для балансу для PHP не існує 2 або 3 модульних модулів тестування - тут є список: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Фентон,

Відповіді:


36

Є третій "фреймворк", який набагато простіше засвоїти - навіть простіше, ніж простий тест, його називають 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 у вашій системі, що досить часто в більшості обставин. Якщо вам потрібно встановити його, я рекомендую встановити останню доступну версію. Якщо вам потрібна допомога з його налаштуванням, сміливо запитуйте (але надайте ОС тощо).


Чи не повинно бути run-tests?
Дхарман

30

Є дві рамки, які можна використовувати для тестування одиниць. Найпростіший і PHPUnit , який я віддаю перевагу. Прочитайте підручники про те, як писати та запускати тести на домашній сторінці PHPUnit. Це досить легко і добре описано.


21

Ви можете зробити тестування одиниць більш ефективним, змінивши стиль кодування для його розміщення.

Я рекомендую переглядати Блог тестування Google , зокрема публікацію про написання Тестового коду .


7
Я думаю, ви згадали чудовий пост. Починаючи свою відповідь із "Тестування одиниць не дуже ефективно", це мало не спричинило заперечення, але, будучи адекватним тестом ... Можливо, перефразування в позитивному порядку заохочуватиме людей читати статтю.
xtofl

2
@xtofl відредагував це, щоб трохи підняти "позитивність" :)
icc97

13

Я згорнув свою власну, тому що я не встиг навчитись комусь іншим способу робити справи, на це потрібно було близько 20 хвилин, щоб написати 10, щоб адаптувати її для розміщення тут.

Тестування для мене дуже корисно.

це якось довго, але це пояснює само собою, і є приклад внизу.

/**
 * 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.

Ця перша відповідь тут красиво підсумовує (це не одиниця проти doctest).


1
це не робить джерело трохи захаращеним?
Алі Ганаватіан

це може. його слід використовувати лише для одиночних простих тестів. також подвоюється як документація. якщо вам потрібно більше використовувати тестовий блок.
Софія

2

phpunit - це майже дефакто-фреймворк модульного тестування для php. є також DocTest (доступний у вигляді пакету PEAR) та кілька інших. Сам php тестується на регресії тощо, за допомогою тестів phpt, які також можна запустити за допомогою груші.


2

Тести кодекцепції схожі на загальні одиничні тести, але є дуже потужними в справах, де вам потрібно глузувати і заїкати.

Ось тест контролера вибірки. Зверніть увагу, як легко створюються заглушки. Наскільки легко ви перевіряєте, чи був викликаний метод.

<?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. Напишіть тести як частину розширення або для передачі потенційної помилки внутрішнім органам або групам контролю якості

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.