Mihail
Guest
Re: Что для вас TDD (разработка через тестирование)?
По поводу тестов предложенных Вами на PHP конференции.
1. Исходя из того, что свойства объекта можно изменять только через его методы, тесты базируются на тестировании методов класса.
2. Разработчики SimpleTest предлагают свою методику, подход к реализации тестирования кода, но это не значит, что тесты нельзя написать своими руками.
3. Подход который предлагают разработчики SimpleTest не нов, очень уж похож на подход реализации исключений в таких библиотеках как VCL и MFC. Замечу, в SimpleTest и в VCL и MFC можно писать свои исключения.(между прочим, в SimpleTest они так и называются expectation, а базовый класс находится в файле expectation.php)
4. Что касается мастер-класса , а в русском варианте урока(показательный урок специалиста для студентов) мне кажется, что легче было бы использовать пример из файла unit_tester_test.php в каталоге test. На этом примере можно показать не только, как писать свой тест, но и как устроен сам SimpleTest. К тому же этот тест показывает на чем базируется принцип тестирования.
Без всяких лишних слов приведу пример кода:
1. function testAssertEqualReturnsAssertionAsBoolean() {
2. $this->assertTrue($this->assertEqual(5, 5));
3. }
Обратите внимание на строку 2, в особенности $this->assertEqual(5, 5). Как думаете, что выдаст функция? Приведу код ниже.
/**
* Will trigger a pass if the two parameters have
* the same value only. Otherwise a fail.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertEqual($first, $second, $message = "%s") {
return $this->assertExpectation(
new EqualExpectation($first),
$second,
$message);
}
/**
* Runs an expectation directly, for extending the
* tests with new expectation classes.
* @param SimpleExpectation $expectation Expectation subclass.
* @param mixed $test_value Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertExpectation(&$expectation, $test_value, $message = '%s') {
return $this->assertTrue(
$expectation->test($test_value),
sprintf($message, $expectation->overlayMessage($test_value)));
}
function test($compare, $nasty = false) {
return (($this->_value == $compare) && ($compare == $this->_value));
}
Конечно (если код правильный) TRUE. ( Я бы разжевал код, да боюсь потратить много ненужных слов.)
Суть происходящего ясна, если нет, то вот она:
Мы задаем методу класса параметры, а потом проверяем результат. Если результат который мы ожидали - значит все хорошо, если нет - надо сделать так чтоб было все хорошо.
Вы спросите:
– А зачем столько накручено;
Я отвечу:
- А бог его знает, так захотели.
Что нам предлагает SimpleTest:
Скачаем его с сайта и изучим исходные тексы. Не буду приводить весь путь вызовов, а приведу основные функции на которых строится работа SimpleTest по тестированию методов класса:
Файл simple_test.php
function assertTrue($result, $message = false) {
if (! $message) {
$message = 'True assertion got ' . ($result ? 'True' : 'False');
}
if ($result) {
$this->pass($message);
return true;
} else {
$this->fail($message);
return false;
}
}
Файл runner.php
/**
* Runs the test methods in the test case.
* @param SimpleTest $test_case Test case to run test on.
* @param string $method Name of test method.
* @access public
*/
function run() {
$methods = get_class_methods(get_class($this->_test_case));
$invoker = &$this->_test_case->createInvoker();
foreach ($methods as $method) {
if (! $this->_isTest($method)) {
continue;
}
if ($this->_isConstructor($method)) {
continue;
}
$this->_scorer->paintMethodStart($method);
if ($this->_scorer->shouldInvoke($this->_test_case->getLabel(), $method)) {
$invoker->invoke($method);
}
$this->_scorer->paintMethodEnd($method);
}
}
function invoke($method) {
set_error_handler('simpleTestErrorHandler');
parent::invoke($method);
$queue = &SimpleErrorQueue::instance();
while (list($severity, $message, $file, $line, $globals) = $queue->extract()) {
$test_case = &$this->getTestCase();
$test_case->error($severity, $message, $file, $line, $globals);
}
restore_error_handler();
}
Краткие пояснения к методу:
function run() - все что мы должны вынести из этой функции, это проход по всем методам класса.
function invoke($method) - все что мы должны вынести из этой функции, это запись журнала времени исполнения.
Если копаться в коде более подробно, конечно можно выяснить дополнительные подробности, но зачем? Все предельно ясно: Идем по методам выполняем их, заполняем журнал результатов, на экран галку.
По поводу проводимого урока и примера в нем.
Взять сторонний продукт и понять принцип его работы, а тем более особенности его использования даже за 1 час не представляется возможным.
Refactoring -improving a computer program by reorganising its internal structure without altering its external behaviour. – это так мимоходом пролетело.
Примеры настолько синтетические, что можно застрелиться.
Ведь можно было намного проще взять примеры.
Пока не посмотрел в исходных текстах
/**
* Sets up unit test wide variables at the start
* of each test method. To be overridden in
* actual user test cases.
* @access public
*/
function setUp() {
}
Долго думал – что это такое. Ведь вы ни где не дали объяснений по ней, хотя черным по белому в коде написано, что она выполняет.
Покажите мне хоть один листинг который у реально работает, а не представляет кусок кода. Где у Вас такие строки:
$test = &new GroupTest('This should fail');
$test->addTestFile('test_with_parse_error.php');
$test->run(new HtmlReporter());
Отсюда получается, что большинство примеров я не смогу проверить.
Не один листинг не содержит номера (не говоря уже о номерах строк), как мне Вам задать вопрос по коду?
Вот к примеру возьмем эту часть, страница 158:
class A {
static public function doSomethingComplex() { // Implements comp
}
class B {
public function doSomethingUsefull(){
$result = A :: doSomethingComplex();
if ($result) return $this->_method1();
else return $this->_method2();
}
protected function _method1(){}
protected function _method2(){}
}
class ClassBTest extends TestCase {
function setUp(){
$this->_fixtureForClassA();
$this->_fixtureForClassB();
}
function testMethod1(){
$this->_provideAResult1();
$b = new B();
$this->assert($b->doSomethingUsefull(), $as_method1);
}
function testMethod1(){
$this->_provideAResult2();
$b = new B();
$this->assert($b->doSomethingUsefull(), $as_method2);
}
}
Начну задавать вопросы:
_fixtureForClassA() – что это такое, я иак и не нашел объяснения.
_fixtureForClassB()
testMethod1 – два метода с одинаковым именем, практически везде.
Смотрим дальше, страница 160
/**
* Clears the data set in the setUp() method call.
* To be overridden by the user in actual user test cases.
* @access public
*/
function tearDown() {
}
В тексте нет объяснения, что это такое.
Про объяснение Mock объектов я уже не говорю:
Mock :: generate
Mock :: generatePartial
$b->tally()
А дальше пошло , поехало – Не используйте Singelton используйте Registry и т.д. и т.п.
Мы изучаем шаблоны проектирования или способы тестирования?
Сейчас, я выскажусь про Mock:
/**
* Clones a class' interface and creates a mock version
* that can have return values and expectations set.
* @param string $class Class to clone.
* @param string $mock_class New class name. Default is
* the old name with "Mock"
* prepended.
* @param array $methods Additional methods to add beyond
* those in th cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @static
* @access public
*/
function generate($class, $mock_class = false, $methods = false) {
if (! SimpleTestCompatibility::classExists($class)) {
return false;
}
if (! $mock_class) {
$mock_class = "Mock" . $class;
}
if (SimpleTestCompatibility::classExists($mock_class)) {
return false;
}
return eval(Mock::_createClassCode(
$class,
$mock_class,
$methods ? $methods : array()) . " return true;");
}
/**
* Generates a version of a class with selected
* methods mocked only. Inherits the old class
* and chains the mock methods of an aggregated
* mock object.
* @param string $class Class to clone.
* @param string $mock_class New class name.
* @param array $methods Methods to be overridden
* with mock versions.
* @static
* @access public
*/
function generatePartial($class, $mock_class, $methods) {
if (! SimpleTestCompatibility::classExists($class)) {
return false;
}
if (SimpleTestCompatibility::classExists($mock_class)) {
trigger_error("Partial mock class [$mock_class] already exists");
return false;
}
return eval(Mock::_extendClassCode($class, $mock_class, $methods));
}
Хоть бы где по тексту кратко, да нет нигде. Аргументы не убедительны, да и не видел я таких аргументов в пользу использования Mock объектов.
А теперь прочтите, цитирую ( страница 161) «… есть один не протестированный метод, а именно _doSomethingComplex' класса B».
Что в этом случае было создано, а потом протестировано -Mock :: generatePartial('B', 'BTestVersion', array('_doSomethingComplex'));
Вот этот пример(страница 161), по моему мнению, работает не правильно, так как переменная $b имеет тип BTestVersion и имеет как Mock объект только один метод _doSomethingComplex.
function testMethod1(){
$b = new BTestVersion($this);
$b->expectOnce('_doSomethingComplex');
$b->setReturnValue('_doSomethingComplex', $result1);
$this->assert($b->doSomethingUsefull(), $as_method1);
$b->tally();
}
Ух, и это не конец.
Я уже устал, но могу по тексту продолжить.
Если есть желание еще критику выслушать, напишу еще.
С Уважение к авторам статьи.
По поводу тестов предложенных Вами на PHP конференции.
1. Исходя из того, что свойства объекта можно изменять только через его методы, тесты базируются на тестировании методов класса.
2. Разработчики SimpleTest предлагают свою методику, подход к реализации тестирования кода, но это не значит, что тесты нельзя написать своими руками.
3. Подход который предлагают разработчики SimpleTest не нов, очень уж похож на подход реализации исключений в таких библиотеках как VCL и MFC. Замечу, в SimpleTest и в VCL и MFC можно писать свои исключения.(между прочим, в SimpleTest они так и называются expectation, а базовый класс находится в файле expectation.php)
4. Что касается мастер-класса , а в русском варианте урока(показательный урок специалиста для студентов) мне кажется, что легче было бы использовать пример из файла unit_tester_test.php в каталоге test. На этом примере можно показать не только, как писать свой тест, но и как устроен сам SimpleTest. К тому же этот тест показывает на чем базируется принцип тестирования.
Без всяких лишних слов приведу пример кода:
1. function testAssertEqualReturnsAssertionAsBoolean() {
2. $this->assertTrue($this->assertEqual(5, 5));
3. }
Обратите внимание на строку 2, в особенности $this->assertEqual(5, 5). Как думаете, что выдаст функция? Приведу код ниже.
/**
* Will trigger a pass if the two parameters have
* the same value only. Otherwise a fail.
* @param mixed $first Value to compare.
* @param mixed $second Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertEqual($first, $second, $message = "%s") {
return $this->assertExpectation(
new EqualExpectation($first),
$second,
$message);
}
/**
* Runs an expectation directly, for extending the
* tests with new expectation classes.
* @param SimpleExpectation $expectation Expectation subclass.
* @param mixed $test_value Value to compare.
* @param string $message Message to display.
* @return boolean True on pass
* @access public
*/
function assertExpectation(&$expectation, $test_value, $message = '%s') {
return $this->assertTrue(
$expectation->test($test_value),
sprintf($message, $expectation->overlayMessage($test_value)));
}
function test($compare, $nasty = false) {
return (($this->_value == $compare) && ($compare == $this->_value));
}
Конечно (если код правильный) TRUE. ( Я бы разжевал код, да боюсь потратить много ненужных слов.)
Суть происходящего ясна, если нет, то вот она:
Мы задаем методу класса параметры, а потом проверяем результат. Если результат который мы ожидали - значит все хорошо, если нет - надо сделать так чтоб было все хорошо.
Вы спросите:
– А зачем столько накручено;
Я отвечу:
- А бог его знает, так захотели.
Что нам предлагает SimpleTest:
Скачаем его с сайта и изучим исходные тексы. Не буду приводить весь путь вызовов, а приведу основные функции на которых строится работа SimpleTest по тестированию методов класса:
Файл simple_test.php
function assertTrue($result, $message = false) {
if (! $message) {
$message = 'True assertion got ' . ($result ? 'True' : 'False');
}
if ($result) {
$this->pass($message);
return true;
} else {
$this->fail($message);
return false;
}
}
Файл runner.php
/**
* Runs the test methods in the test case.
* @param SimpleTest $test_case Test case to run test on.
* @param string $method Name of test method.
* @access public
*/
function run() {
$methods = get_class_methods(get_class($this->_test_case));
$invoker = &$this->_test_case->createInvoker();
foreach ($methods as $method) {
if (! $this->_isTest($method)) {
continue;
}
if ($this->_isConstructor($method)) {
continue;
}
$this->_scorer->paintMethodStart($method);
if ($this->_scorer->shouldInvoke($this->_test_case->getLabel(), $method)) {
$invoker->invoke($method);
}
$this->_scorer->paintMethodEnd($method);
}
}
function invoke($method) {
set_error_handler('simpleTestErrorHandler');
parent::invoke($method);
$queue = &SimpleErrorQueue::instance();
while (list($severity, $message, $file, $line, $globals) = $queue->extract()) {
$test_case = &$this->getTestCase();
$test_case->error($severity, $message, $file, $line, $globals);
}
restore_error_handler();
}
Краткие пояснения к методу:
function run() - все что мы должны вынести из этой функции, это проход по всем методам класса.
function invoke($method) - все что мы должны вынести из этой функции, это запись журнала времени исполнения.
Если копаться в коде более подробно, конечно можно выяснить дополнительные подробности, но зачем? Все предельно ясно: Идем по методам выполняем их, заполняем журнал результатов, на экран галку.
По поводу проводимого урока и примера в нем.
Взять сторонний продукт и понять принцип его работы, а тем более особенности его использования даже за 1 час не представляется возможным.
Refactoring -improving a computer program by reorganising its internal structure without altering its external behaviour. – это так мимоходом пролетело.
Примеры настолько синтетические, что можно застрелиться.
Ведь можно было намного проще взять примеры.
Пока не посмотрел в исходных текстах
/**
* Sets up unit test wide variables at the start
* of each test method. To be overridden in
* actual user test cases.
* @access public
*/
function setUp() {
}
Долго думал – что это такое. Ведь вы ни где не дали объяснений по ней, хотя черным по белому в коде написано, что она выполняет.
Покажите мне хоть один листинг который у реально работает, а не представляет кусок кода. Где у Вас такие строки:
$test = &new GroupTest('This should fail');
$test->addTestFile('test_with_parse_error.php');
$test->run(new HtmlReporter());
Отсюда получается, что большинство примеров я не смогу проверить.
Не один листинг не содержит номера (не говоря уже о номерах строк), как мне Вам задать вопрос по коду?
Вот к примеру возьмем эту часть, страница 158:
class A {
static public function doSomethingComplex() { // Implements comp
}
class B {
public function doSomethingUsefull(){
$result = A :: doSomethingComplex();
if ($result) return $this->_method1();
else return $this->_method2();
}
protected function _method1(){}
protected function _method2(){}
}
class ClassBTest extends TestCase {
function setUp(){
$this->_fixtureForClassA();
$this->_fixtureForClassB();
}
function testMethod1(){
$this->_provideAResult1();
$b = new B();
$this->assert($b->doSomethingUsefull(), $as_method1);
}
function testMethod1(){
$this->_provideAResult2();
$b = new B();
$this->assert($b->doSomethingUsefull(), $as_method2);
}
}
Начну задавать вопросы:
_fixtureForClassA() – что это такое, я иак и не нашел объяснения.
_fixtureForClassB()
testMethod1 – два метода с одинаковым именем, практически везде.
Смотрим дальше, страница 160
/**
* Clears the data set in the setUp() method call.
* To be overridden by the user in actual user test cases.
* @access public
*/
function tearDown() {
}
В тексте нет объяснения, что это такое.
Про объяснение Mock объектов я уже не говорю:
Mock :: generate
Mock :: generatePartial
$b->tally()
А дальше пошло , поехало – Не используйте Singelton используйте Registry и т.д. и т.п.
Мы изучаем шаблоны проектирования или способы тестирования?
Сейчас, я выскажусь про Mock:
/**
* Clones a class' interface and creates a mock version
* that can have return values and expectations set.
* @param string $class Class to clone.
* @param string $mock_class New class name. Default is
* the old name with "Mock"
* prepended.
* @param array $methods Additional methods to add beyond
* those in th cloned class. Use this
* to emulate the dynamic addition of
* methods in the cloned class or when
* the class hasn't been written yet.
* @static
* @access public
*/
function generate($class, $mock_class = false, $methods = false) {
if (! SimpleTestCompatibility::classExists($class)) {
return false;
}
if (! $mock_class) {
$mock_class = "Mock" . $class;
}
if (SimpleTestCompatibility::classExists($mock_class)) {
return false;
}
return eval(Mock::_createClassCode(
$class,
$mock_class,
$methods ? $methods : array()) . " return true;");
}
/**
* Generates a version of a class with selected
* methods mocked only. Inherits the old class
* and chains the mock methods of an aggregated
* mock object.
* @param string $class Class to clone.
* @param string $mock_class New class name.
* @param array $methods Methods to be overridden
* with mock versions.
* @static
* @access public
*/
function generatePartial($class, $mock_class, $methods) {
if (! SimpleTestCompatibility::classExists($class)) {
return false;
}
if (SimpleTestCompatibility::classExists($mock_class)) {
trigger_error("Partial mock class [$mock_class] already exists");
return false;
}
return eval(Mock::_extendClassCode($class, $mock_class, $methods));
}
Хоть бы где по тексту кратко, да нет нигде. Аргументы не убедительны, да и не видел я таких аргументов в пользу использования Mock объектов.
А теперь прочтите, цитирую ( страница 161) «… есть один не протестированный метод, а именно _doSomethingComplex' класса B».
Что в этом случае было создано, а потом протестировано -Mock :: generatePartial('B', 'BTestVersion', array('_doSomethingComplex'));
Вот этот пример(страница 161), по моему мнению, работает не правильно, так как переменная $b имеет тип BTestVersion и имеет как Mock объект только один метод _doSomethingComplex.
function testMethod1(){
$b = new BTestVersion($this);
$b->expectOnce('_doSomethingComplex');
$b->setReturnValue('_doSomethingComplex', $result1);
$this->assert($b->doSomethingUsefull(), $as_method1);
$b->tally();
}
Ух, и это не конец.
Я уже устал, но могу по тексту продолжить.
Если есть желание еще критику выслушать, напишу еще.
С Уважение к авторам статьи.