Целесообразность unit-тестов

confguru

ExAdmin
Команда форума
Ну давайте.. только выступать перед аудиторией будет один..
2-йные докладчики - зло .. :)
А вот 2 головы всегда лучше.. :)
Тем более что надо отсечь все лишнее :)
 

varan

Б̈́̈̽ͮͣ̈Л̩̲̮̻̤̹͓ДͦЖ̯̙̭̥̑͆А͇̠̱͓͇̾ͨД͙͈̰̳͈͛ͅ
Съездить что-ли в Киев, никогда не был
 

baldanders

Guest
сходил я по ссылкам, тут приведенным, почитал про Mock-объекты, Stubs. Но вот чего я не понял:
Допустим, есть класс А , у которого есть некий атрибут. В конструкторе класса А этот атрибут инициализируется объектом класса Б. Теперь я хочу протестировать класс А заменив при этом объект класса Б заглушкой. Для этого надо каждый раз перед тестированием изменять конструктор класса А что ли, что бы он инициализировал атрибут заглушкой?
 

pachanga

Новичок
Автор оригинала: baldanders
Для этого надо каждый раз перед тестированием изменять конструктор класса А что ли, что бы он инициализировал атрибут заглушкой?
Ты что, ни в коем случае! В твоем случае я вижу, по крайней мере, три способа тестирования:

1) IOC - inversion of control, тебе надо будет явно передавать Б в конструктор A, таким образом ты сможешь сделать полный мок(full mock) на объект Б

2) Завести в А фабричный метод(factory method), который бы создавал объект Б, скажем А::_getB(). После этого можно сделать частичный мок(partial mock, смотри документацию SimpleTest) на А на метод _getB() и опять же вернуть полный мок на Б

3) Отрефакторить код :)

-~{}~ 24.01.05 11:56:

Автор оригинала: admin
pacha

Ну так как - созрел? :)
http://phpclub.ru/talk/showthread.php?s=&threadid=61195&rand=19
Давай тогда там общаться, чтобы топик не мусорить.....
 

baldanders

Guest
pacha, спасибо!
воспользуюсь первым методом. В следующий раз буду сразу во время дизайна классов учитывать подобные моменты, если будут планироваться unit-тесты.
 

pachanga

Новичок
Автор оригинала: baldanders
В следующий раз буду сразу во время дизайна классов учитывать подобные моменты, если будут планироваться unit-тесты.
Лучше вначале пиши тесты, а уж потом учитывай дизайн классов - так оно правильнее будет :)
 

TAHK

Guest
Автор оригинала: GD
иногда бывает так, что проект содержит очень большое кол-во кода...
спустя год, после начала разработки проекта мало кто помнит все тонкости взаимодействия обьектов...

вот именно в таком случае удобнее взять тесты, которые этот год писали, запустить и через 1...2 минуты понять что именно ты успел поломать за последнюю правку...
Гораздо проще при этом напоротся на то, что то тест был неправильно написан или в нем не была учтена вся изменившаяся специфика модуля. И ты начинаешь кроме тестирования новой функции тестиирования теста и охоту на ведьм
 

Crazy

Developer
TAHK, тест пишут по той же спецификации, что и код. А потому в реальной практике программирования (в профессиональных командах) возникает такое редко.

Другое дело, что тесты часто не проверяют какую-то хитрую ветку в коде. Но на этот счет есть инструменты, контролирующие покрытие кода тестами. Не знаю, есть ли они конкретно для PHP...
 

TAHK

Guest
Автор оригинала: Crazy
TAHK, тест пишут по той же спецификации, что и код. А потому в реальной практике программирования (в профессиональных командах) возникает такое редко.
Вот именно что в профессиональных командах, а в непрофесиональных командах иногда даже и спецификации нету. Так что целесообразность применения такого подхода надо определять рассматриваю конкретную команду разработки и конкретный проект.

Другое дело, что тесты часто не проверяют какую-то хитрую ветку в коде. Но на этот счет есть инструменты, контролирующие покрытие кода тестами. Не знаю, есть ли они конкретно для PHP...
Сам не знаю, знаю точно что JUnit в Java - редкая гадость :)
 

TAHK

Guest
Автор оригинала: Crazy
Иные случаи обсуждать бессмысленно.


"Не люблю кошек. Шерсть, запах..." (c)
Согласен, но в принципе вопрос стоял в общем, я в общем и ответил :)

А по поводу кошек - ты их просто готовить не умеешь :) (с)
 

syfisher

TDD infected!!
Автор оригинала: TAHK
Гораздо проще при этом напоротся на то, что то тест был неправильно написан или в нем не была учтена вся изменившаяся?? специфика модуля. И ты начинаешь кроме тестирования новой функции тестиирования теста ??? и охоту на ведьм
По-моему, тест запускают всегда до того, как отрефакторить модуль. Плюс правильнее сначала править тест, затем уже сам модуль. Коммит в репозиторий делают только тогда, когда все тесты сработали.

Новые баги локализуем всегда через тест. То есть если нашли баг, пишем тест, который покажет этот баг. Только тосле этого правим бажный код.

Даже, если тест не покрывает какую-либо ветку, возникнет ситуация, когда код нужно будет поправить. Можно именно тогда написать тест, а потом уже рефакторить.

Выполнение таких правил позволит избежать данных ситуаций.

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

Crazy

Developer
Согласен со всем сказанным выше с одним исключением: согласно моим представлениям, при рефакоринге кода тест править незачем. Ибо внешние проявления меняться не должны. Иначе, в моем понимании, это не рефакторинг.
 

syfisher

TDD infected!!
Автор оригинала: Crazy
Согласен со всем сказанным выше с одним исключением: согласно моим представлениям, при рефакоринге кода тест править незачем. Ибо внешние проявления меняться не должны. Иначе, в моем понимании, это не рефакторинг.
Тесты бывают различных уровней:
* web-тесты (приемочные)
* unit-тесты (модульные).

web-тесты тестируют системы как чертный ящик. Для них это правило действует на 100%.

Для модульных тестов это не совсем так. Для простых случаев - так и есть. Это когда тестируемый класс не содержит сложных внешних зависимостей.

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

Если изолированием не пользоваться (для соблюдения правила рефакторинг без изменения тестов) то сами тесты будут получаться слишком сложными для написания и по сути они будут тестировать только или самые простые классы, или фасады.

Если нам необходимо что-либо отрефакторить более или менее сложное и достаточно старое, мы пишем приемочный тест, который учитывает все внешние проявления. Этот тест обычно не такой подробный, как модульные, но позволяет проверять код в целом. Я думаю, именно о таких тестах, говорит Фаулер и прочие, когда заявляют, что рефакторинг не должен затрагивать тесты.
 

Crazy

Developer
syfisher, мое мнение заметно отличается от твоего. :) BTW, термин " web-тесты" в контексте классификации уровней тестов я слышу впервые в жизни.
 

syfisher

TDD infected!!
Автор оригинала: Crazy
syfisher, мое мнение заметно отличается от твоего. :) BTW, термин " web-тесты" в контексте классификации уровней тестов я слышу впервые в жизни.
Ну мы же все здесь (ну или почти все) разрабатываем именно web-приложения. А какие тесты могут считаться приемочными для web-приложений - именно тесты самих страниц. Термин "web-тесты" мы используем именно для приемочных тестов, где объектом проверок являются web-страницы.

http://www.lastcraft.com/simple_test.php#web

http://www.lastcraft.com/web_tester_documentation.php

С внедрением web-тестов мы начали практиковать практически чистое XP:
1) Пишутся короткие карточки вида

* Администратор может создавать сервисы. Для каждого сервиса указывается:
o Идентификатор
o Название.
o Класс.
o Поведение.
Сервисы могуть быть иерархически организованы.
Админстратор может изменять сервис. Класс сервиса менять нельзя.
* Администратор должен иметь возможность упорядочивать категории по своему усмотрению.
* На странице с сервисом выводится список дочерних сервисов. Для каждого дочернего сервиса выподятся поля:
* название,
* заголовок
* класс
* поведение
На странице с сервисом отображается данные по текущему сервису.
* На странице сервисов должна быть возможность переходить на страницу дочернего сервиса, а также на страницу родительского сервиса.
* и т.д.

2) Потом пишется web-тест, приблизительно такой:

PHP:
class ServiceNodeWebTest extends WebTestCase
{
...
  function testCreateDisplayEditServiceNodes()
  {
    $this->_login();

    $this->assertTrue($this->get(APP_WEB_TESTS_HOST . 'services'));

    // Create child service
    $check_map1 = $this->_generatePageCheckMap('1');

    $this->_createObject('create_service_node', $check_map1);

    // child object display right after it was created on parent object page
    if(!$this->assertWantedPattern('/' . $check_map1['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['behaviour_name'] . '/')
       )
      $this->showSource();

    // check new object itself
    $object1 =& $this->_getLastObject('ServiceNode');

    // go to object page
    $this->_clickDisplayObject($object1);

    if(!$this->assertWantedPattern('/' . $check_map1['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['behaviour_name'] . '/')
       )
      $this->showSource();

    // create child object for the first object
    $check_map2 = $this->_generatePageCheckMap('2');
    $this->_createObject('create_service_node', $check_map2);

    // child object display right after it was created on parent object page
    if(!$this->assertWantedPattern('/' . $check_map2['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2['behaviour_name'] . '/')
       )
      $this->showSource();

    $object2 =& $this->_getLastObject('ServiceNode');

    $this->_clickDisplayObject($object2);

    if(!$this->assertWantedPattern('/' . $check_map2['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2['behaviour_name'] . '/')
       )
      $this->showSource();

    // Check if breadrumbs works
    $this->_clickDisplayObject($object1);

    if(!$this->assertWantedPattern('/' . $check_map1['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map1['behaviour_name'] . '/')
       )
      $this->showSource();

    // Edit second object
    $check_map2_edited = $this->_generatePageCheckMap('2_edited');

    $cm1 = $check_map2;
    $cm2 = $check_map2_edited;

    unset($cm1['class_name']);
    unset($cm2['class_name']);

    $this->_editObject($object2, $cm1, $cm2);

    // After editing we redirected to modified object page
    if(!$this->assertWantedPattern('/' . $check_map2_edited['title'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2_edited['class_name'] . '/') ||
       !$this->assertWantedPattern('/' . $check_map2_edited['behaviour_name'] . '/')
       )
      $this->showSource();

  }

  function _createObject($link, $values)
  {
    $this->assertTrue($this->clickLinkById($link), 'cant find create link = '. $link);

    if(!$this->assertWantedPattern('/Create text page/'))
      $this->showSource();

    foreach($values as $key => $value)
      $this->setField($key, $value);

    $this->assertTrue($this->clickSubmit('Create'));
  }

  function _editObject($object, $ensure_values, $new_values)
  {
    if(empty($object))
    {
      $this->assertTrue(false, 'object is empty');
      return;
    }

    $this->clickLinkById('edit_' . $object->get('oid'));

    foreach($ensure_values as $key => $value)
      $this->assertField($key, $value);

    foreach($new_values as $key => $value)
      $this->setField($key, $value);

    $this->assertTrue($this->clickSubmit('Edit'));
  }

...

}

3) После этого приходит очередь имплементации, разработанной через TDD, используя модульные тесты. Как только web-тесты выполнились, мы показываем результат заказчику (можно и менеджеру по проектам внутри компании), чтобы получить новую порцию заданий.

P.S. Хотелось бы услышать другое мнение ;)
 

Crazy

Developer
Автор оригинала: syfisher
Термин web-тесты" мы используем именно для приемочных тестов, где объектом проверок являются web-страницы.
Я не имею ничего против того, чтобы в своих проектах вы испорльзовали любую изобретенную вами терминологию.

-~{}~ 26.03.05 21:37:

Автор оригинала: syfisher
Если нам необходимо что-либо отрефакторить более или менее сложное и достаточно старое, мы пишем приемочный тест, который учитывает все внешние проявления. Этот тест обычно не такой подробный, как модульные, но позволяет проверять код в целом. Я думаю, именно о таких тестах, говорит Фаулер и прочие, когда заявляют, что рефакторинг не должен затрагивать тесты.
Я еще раз просмотрел "Refactoring: Improving the Design of Existing Code" и не вижу в нем подтверждения этого тезиса.
 
Сверху