Как написать модульные тесты в PHP?

Я читал всюду о том, насколько они хороши, но по какой-то причине я не могу понять, как именно я должен что-то проверять. Может ли кто-нибудь, возможно, разместить кусок примерного кода и как они его проведут? Если это не так много проблем 🙂

Существует 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 в вашей системе, что довольно часто встречается в большинстве случаев. Если вам необходимо установить его, я рекомендую установить последнюю версию. Если вам нужна помощь в настройке, не стесняйтесь спрашивать (но предоставить ОС и т. Д.).

Существуют две структуры, которые можно использовать для модульного тестирования. Simpletest и PHPUnit , которые я предпочитаю. Прочтите учебники о том, как писать и запускать тесты на домашней странице PHPUnit. Это довольно легко и хорошо описано.

Вы можете сделать модульное тестирование более эффективным, изменив стиль кодирования, чтобы приспособить его.

Я рекомендую просмотреть блог Google Testing , в частности сообщение о написании тестового кода .

Я катался самостоятельно, потому что у меня не было времени научить кого-то elses делать что-то, это заняло около 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 был успешным 

Получите 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); } 

Для простых тестов и документации php-doctest довольно хорош, и это очень простой способ начать работу, так как вам не нужно открывать отдельный файл. Представьте себе следующую функцию:

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

Если вы сейчас запустите этот файл через phpdt (runter php-doctest) из командной строки, будет запущен тест. Учение содержится внутри блока <code>. Doctest создан в python и отлично подходит для предоставления полезных и управляемых примеров того, как должен работать код. Вы не можете использовать его исключительно потому, что сам код будет зависеть от тестовых примеров, но я обнаружил, что он полезен наряду с более формальной библиотекой tdd – я использую phpunit.

Этот 1-й ответ здесь суммирует его хорошо (это не единица против доктрины).

phpunit – это в значительной степени базовая платформа тестирования defacto для php. есть также DocTest (доступен как пакет PEAR) и несколько других. php сам тестируется на регрессии и т. п. посредством тестов phpt, которые также могут выполняться через грушу.

Тесты с коллаксией во многом похожи на обычные модульные тесты, но они очень эффективны в ситуациях, когда вам нужно насмехаться и стучать.

Вот тест контроллера образца. Обратите внимание, как легко создаются заглушки. Как легко вы проверили метод, был вызван.

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

Также есть и другие интересные вещи. Вы можете проверить состояние базы данных, файловую систему и т. Д.

Помимо отличных предложений по уже существующим тестовым средам, вы строите свое приложение с помощью одной из веб-фреймворков PHP, в которой встроено автоматическое тестирование, например Symfony или CakePHP ? Иногда наличие места для простота в методах тестирования снижает начальное трение, которое некоторые люди связывают с автоматическим тестированием и TDD.

Путь слишком много для повторной публикации здесь, но вот отличная статья об использовании phpt . Он охватывает несколько аспектов вокруг phpt , которые часто упускаются из виду, поэтому было бы полезно прочитать, чтобы расширить свои знания php за пределами простого написания теста. К счастью, в статье также обсуждаются письменные тесты!

Основные моменты обсуждения

  1. Узнайте, как незначительно документированные аспекты работы PHP (или почти любая часть в этом отношении)
  2. Напишите простые модульные тесты для собственного PHP-кода
  3. Записывать тесты как часть расширения или передавать потенциальную ошибку для внутренних или групп QA

Я знаю, что здесь уже много информации, но так как это все еще появляется в результатах поиска Google, я могу добавить в список Chinook Test Suite . Это простая и небольшая тестовая структура.

Вы можете легко протестировать свои классы, а также создать макет объектов. Вы запускаете тесты через веб-браузер и (еще не) через консоль. В браузере вы можете указать, какой тест-класс или какой метод тестирования выполнить. Или вы можете просто запустить все тесты.

Снимок экрана с страницы github:

Схема тестирования чинуковских модулей

Что мне нравится в этом, так это то, как вы утверждаете тесты. Это делается с помощью так называемых «плавных утверждений». Пример:

 $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