Pull to refresh
29
0
Bender Bending Rodriguez @bendingunit22

Специалист по всему

Send message
Кстати, конфиги из примера очень сильно ухудшают читабельность кода и размазывают бизнес-логику. Плюс не совсем понятно, как пишутся модульные тесты на хендлеры. Сравните:

package ServiceLocator;
...
sub getDbh
{
my $self = shift;

$self->{dbh} ||= DBI->connect(...);
}

my $dbh = $service_locator->getDbh();

против:
my $config = {
dbi => {
handler => sub {
return DBI->connect()
},
settings => { reusable => 1 }
}
}
...
my $dbh = $container->get_by_name('dbi');


где профит? :)

В посте описан обычный service locator. Это альтернатива IoC контейнерам, которая имеет как свои преимущества, так и недостатки. Принципиальная разница между ними: SL — это pull подход, IoCC — push.
Майндсторм вроде поинтересней. Вроде в Microsoft Robotics поддержка есть :)
Внешний слой — это функциональные тесты, так называемые тесты черного ящика. Внутренний слой — это модульные тесты. Модульные тесты обычно пишутся с использованием xUnit, rspec, функциональные — с использованием cucumber, selenium. Проще всего попробовать при реализации какого-нибудь API.
Смотрите комментарий выше про AAA. Act и assert уникальные для каждого теста. Arrange (создание фикстур, инициализация) выделяются в отдельные методы либо самого теста, либо, при частом использовании, в Mother Object. Таким образом устраняется дублирование, а тесты становятся очень лаконичными, обычно меньше 10 строк, и понятными (пример выше).

Что касается дублирования в целом: TDD/BDD гуру утверждают, что в тестах очень важна читабельность и пытаться полностью устранить дублирование, жертвуя читабельностью, затея не самая оправданная. Тут, как и везде, нужен баланс.
Что касается поведения: мы пишем тест на то, что объект ведет себя так, как мы ожидаем. Например, «два плюс два должно быть четыре». Если тест не проходит, объект не соответствует ожидаемому поведению. Should вместо test просто делает это более очевидным :)
Я же написал, основе отличие — два цикла тестирования. Посмотрите на картинку (там, правда, названия устаревших инструментов).
image
В самом начале нашего TDD пути инициализацию для тестов старались унифицировать и засунуть в setup. Недостатки очевидны: низкая скорость, нечитабельные тесты (при чтении теста приходится постоянно заглядывать в setup), сам сетап перегружен и сложен в понимании.

Сейчас мы практикуем принцип трех AAA — Arrange Act Assert (пересказывать не буду, можно почитать тут), т.е. в каждом тестовом методе вся инициализация и создание фикстур выделены в отдельный блок (arrange ). Если замечаем дублирование, просто выделяем отдельный метод и вызываем его во всех нужных тестах. Таким образом тест обычно выглядит следующим образом:

sub shouldSetMessageOnFailed
{
...

# Arrange
my $transaction = $self->_buildFailingTransaction();

# Act
$transaction->process();

# Assert
$self->assert_equals('some error text', $transaction->get('message'));

}


В примере _buildFailingTransaction может явно вызываться в любых тестах. Еще удобно использовать паттерн ObjectMother, но это отдельная история.

К слову, в rspec setup и teardown можно писать для группы методов (еще и используя вложенность). Приведенный выше пример — практически то же самое, только без сахара.
На самом деле не совсем так, почитайте мой комментарий выше. Кстати, правило «один тест — один ассерт» появилось в TDD еще до рождения BDD :)
В вашем примере нарушаются KISS и YAGNI, но он вполне жизненный, сам не раз с таким сталкивался. Как и с любой другой методологией нужно быть осторожным и не перегибать палку.
Вы все отлично расписали, только в своем примере вы тестируете готовую функцию — это не совсем TDD. Представьте, что вы только хотите ее написать. Ставите требование:
1. strip() на пустую строку должна вернуть пустую строку
Пишите тест shouldReturnUndefOnEmptyString. Тест проваливается, пишите код, который заставляет тест проходить. Дальше новое требование:
2. strip() на смешанных пробельных должна вернуть строку без пробелов.
Опять тест, красная полоска, код, зеленая полоска.
3. strip() на пробельных символах с одной из сторон должна вернуть строку без пробелов.
Опять то же самое :)

На выходе получается 3 маленьких теста, которые по сути являются спецификацией вашей функции strip. Глядя только на названия вы получаете огромное количество информации (ведь тесты должны выступать и в роли документации). Сравните это с одним тестом testSuccess.

Помимо вышеперечисленного, такой подход решает проблему первого теста, делает тесты менее хрупкими, позволяет с легкостью наращивать функционал не модифицируя старые тесты, устраняет дублирование в тестах. В общем рекомендую попробовать, через два дня будет сложно представить, как можно было раньше писать иначе :) Заодно сделаете большой шаг в сторону BDD.
На русский язык unit test переводится как модульный тест, что намекает :)
В рубийном BDD два цикла тестирования: внешний и внутренний. Внешний — это функциональные тесты на cucumber, внутренний — модульные на rspec. Сначала пишется функциональный тест, который ломается. Затем пишутся модульные тесты, которые заставляют заработать функциональный. Т.е. тот же самый red / green/ refactor но на двух уровнях.

Второе важное отличие — тестирование поведения, а не состояния. Если отбросить сахар, который дает rspec, он особо не отличается от xUnit.

Многие используют только rspec и пишут модульные тесты, без внешнего цикла. Такое BDD не сильно от TDD отличается :)
В TDD внутренности не тестируются :)
Тогда уж лучше писать на Objective-C :) На самом деле первый абзац моего комментария не следует рассматривать слишком серьезно.
Можно с тем же успехом применить некоторые практики из BDD, оставаясь при этом на привычном xUnit.
В TDD шаги, обычно, поменьше. Интерфейс класса вырисовывается в процессе написания тестов, а не выдумывается заранее. Впрочем, если вы придумали его заранее, модульные тесты в процессе вас обязательно поправят :)
Тесты, содержащие больше одного ассерта, — это, обычно, плохой запах.
При использовании ООП появляются накладные расходы на вызов методов (функции-то быстрее). Так что можно было бы предложить оппоненту использовать процедурное программирование.

TDD упрощает код, так как заставляет следовать interface segregation принципу. Но людям, далеким от современного ООП и методологий разработки, это бывает очень сложно объяснить :)

Information

Rating
Does not participate
Registered
Activity