Rose debug info
---------------

TDD не понравилось

Разработка через тестирование — весьма странное занятие.
Я попробовал на новом проекте и пока нахожусь в смятении. Наверняка дело в мышлении, которое осталось с прошлого опыта: сначала реализация, потом функциональное тестирование приложения. Покрытие кода тестами делалось обычно постфактум, и обычно тесты были не тупыми проверками работоспособности функций, а проверка связанного функционала, поведения то есть. Behavioral.
Мне не нравится все что я тут понаписал потому, что, несмотря на то, что код покрыт тестами, абстракция сосёт!

Но тем не менее. Давайте-ка посмотрим на то, что я тут понаписал.

Требовалось создать демона, который проверяет базу данных на наличие записи в БД с марекером created и делает полезную работу.
Итак, codeflow демона:

  1. найти записи с статусом created, взять данные
  2. изменить статус на processing
  3. сделать по данным из п.1 много полезной работы
  4. сохранить результаты работы в другой таблице
  5. изменить статус на done

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

По всем правилам TDD я создал тест:

//Найти записи со статусом created
    public function testGetCreatedOrders()
    {
        $daemon = new Daemon();

        $result = $daemon->getCreatedOrders();

        $this->assertTrue(is_array($result);
        var_dump($result);
    }

Потом был написан очень простой код в классе Daemon:

class Daemon 
{
    public function getCreatedOrders()
    {
        return Application_Model_Order::getOrderByOurStatus("created");
    }
}

И вот тут первый вопрос у меня.
Нужен ли вообще тест на этот метод, при том, что тест на этот метод уже реализован для класса Application_Model_Order?

Конечно же, можно задать вопрос, нужен ли вообще этот однострочный метод в классе, но например, нужен. Нужен хотя бы по тому, что я только начал, и это полет мысли. Возможно, я потом передумаю, но вот здесь и сейчас мне нужна функциональность, и я просто хочу, чтобы она была такой.

Окей, идем дальше, ко второму пункту.

public function testMarkOrderProcessing()
    {
        $daemon = new Daemon();

        $result = $daemon->markOrderProcessing(1);

 		$this->assertTrue(1==$result);
        
        var_dump($result);
    }
</pre>
Ололо, что нам нужно сделать:
<pre class="brush:php">
    public function markOrderProcessing($orderId)
    {
        return Application_Model_Order::markOrder($orderId, 'processing');
    }

Как удобно :)

Идем к третьему пункту.

public function testParseData()
    {

    	$mock = "{"some":"data", "in":"json format"}";

        $daemon = new Daemon();

        $result = $daemon->necessaryWork($mock);

 		$this->assertTrue(isset($result['blabla']);
        
        var_dump($result);
    }

Отлично, в моем приложении уже появился какой-то интересный код в necessaryWork.

public function necessaryWork($orderId, $rawData)
    {
        $order = Zend_Json_Decoder::decode($rawData);

        $someData = $this->_generateSomeData($order);

        $elseData = $this->_doSomethingElse($order);

        return array($someData, $elseData);
}

Идем к четвртому пункту.

public function testSaveInfo()
    {

    	$mock = array('results'=>'of the work', 'of'=>'necessaryWork method');

        $daemon = new Daemon();

        $result = $daemon->saveData($mock);

 		$this->assertTrue(1==$result);
        
        var_dump($result);
    }

Условный код в классе:

public function saveData($data)
{
	$obj = new Application_Model_Obj();

    $obj->name = $data['name'];
    $obj->city = $data['city'];

    $obj->save();
}

Пятый пункт выполнился совершенно так же, как пункт 2.

Итак, у меня появилась полная функциональность моего демона реализованная через тесты. Осталось сделать один главный метод на демоне, который и будет делать всю работу. Тест:

public function testMain()
    {

        $daemon = new Daemon();

        $result = $daemon->main;

 		$this->assertTrue(1==$result);
        
        var_dump($result);
    }

Рабочий метод в классе демона:

public function main()
	{
		//найти created
        $order = $this->getCreatedOrders();
        if (!isset($order[0])) {
            return false;
        } else {
             $order = $order[0];
        }

        //пометить как processing
        $this->markOrderProcessing($order['id']);

        //сделать полезную работу
        $necessaryData = $this->necessaryWork($order['id'], $order['necessarydata']);

        //сохранить в бд
        $this->saveData($necessaryData);

        //пометить как done
        $this->markOrderDone($order['id']);

        echo 'done for id = ' . $order['id'] . PHP_EOL;
	}

Вот и готов мой демон. Весь тестами покрыт, код читабельный, поддерживаемый.
Но что здесь не так?! Все методы public. Жутко бесит, хочется взять и убрать.

Если бы я не использовал TDD, пабликом у меня был бы только necessaryWork, т. к. только он является реально новым кодом, на который следует создавать тест.

Вот теперь как поступить с этим всем? Оставить все методы пабликами и пусть идет кровь из глаз, или все методы скрыть и оставить наружу только work(), который в цикле постоянно запускает private _main()?

Так-то становится понятным теперь, почему ребятки питонисты из соседней конторы шлют лесом всю инкапсуляцию в своих исходниках. Я надеюсь, что это вот вздор так останется ерундой.

P.S. разумеется, всё сокрыл и ненужные тесты потер. Нечего мне тут.

По теме:
Introduction to Test Driven Development
Простое написание тестов — это не TDD!
Ошибки начинающих TDD-практиков.

1 комментарий
Sergey 2013

Думаю, бдд будет более уместно, т. к. при бдд нет необходимости покрывать тестами методы, относящиеся к протектед или прайвит. Бдд удобно также тем (личный опыт), что призывает разрабатывать только то, что нужно, без лишнего кода. В данном случае, наверное, следует проверить сценарии:

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