Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Читабельный тест

Anna | 25.06.2014 | нет комментариев

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

Юнит. Что это такое?

Unit testing принято переводить на русский язык как модульное тестирование. Впрочем слово «модуль» имеет несколько иной смысловой оттенок, ассоциирующийся со схемой развертывания. Следственно во избежание непотребных ассоциаций будем применять англицизм «юнит». Еще раз припомним, что такое юнит в рамках терминологии юнит тестирования:

Юнит – это фрагмент кода, дающий в данном окружении при определенных входных данных определенные выходные данные.

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

Примеры

Дабы не быть принесенными в жертву Молоху абстракции, выражаясь словами Ницше, разглядим пару примеров, наглядно демонстрирующих всякий из компонентов этого любовного квадрата.

Пустое окружение

Это особенно распространенная конфигурация для юнитов, написанных в жанре структурной декомпозиции. Функция, получающая доводы и возвращающая итог – это пример юнита, имеющего в качестве входных данных уйма доводов и в качестве выходных данных – итог. Связь итога со входными параметрами и есть суть функции. Скажем, функция y = sin(x) является отображением множества всех вещественных чисел на отрезок [-1,1]. Для всякого входного x однозначно определен y, и ответственность функции sin(x) заключается в обеспечении этой однозначности. sin(x) будет отображать эти x в эти y в любом окружении для всякого пользователя, в всякое время года. Ему не увлекательно окружение.

Пустые выходные данные

Нередко ответственность юнита заключается в том, Дабы не легко что-то воротить, а что-то сделать с некоторым состоянием. Скажем, типичный паттерн медиатор полагает присутствие объекта (юнит), тот, что получает некие события (входные данные) и вызывает у комплекта объектов (окружение) некоторые способы. Данный объект ничего никому не возвращает – его ответственность обеспечивать инварианты между комплектом объектов, объединять которые он и призван. Обеспечение этих инвариантов – это его ответственность. Именно воздействие входных событий на окружение – это определение юнита медиатор. Окно-медиатор, активизирующее некоторую секцию диалога по факту клика на радио кнопке – определенный пример юнитов такой конфигурации.

Пустые входные данные

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

void f()

Функция, ничего не принимающая, ничего не возвращающая и никак не взаимодействующая с окружением имеет немного смысла. Эта некая вещь в себе, которую отменнее не будоражить – не применять и не создавать вообще.

Состояние и юниты

В свете вышеприведенных примеров и определения посмотрим на функцию void std::list::remove(const T& elem). Возможен, elem – это входные данные. А как же быть с окружением и выходными данными? Выходных данных нет – функция void. А окружение тогда что? Официально – память. Но память – это сущность больше низкого яруса чем список. чай никто же не будет утверждать, что «удаление из списка – это освобождение памяти по какой-то хитроумной схеме». Если попытаться определить, что значит (какая у него семантика) «удаление из списка», то звучать это будет приблизительно так: «удаление из списка некоторого э_twomk!>TEST(FileCache, IsInitializedAsEmpty)

Применение сущностей из Assertion в Body

Правило: Идентификаторы, используемые в теле теста, обязаны вторично применять термины, употребленные в заявлении.
Пояснение: Что может быть для читателя очевиднее при чтении теста, чем буквальное повторение терминов из заявления? Если читателю сделали некоторое добротное обещание в Assertion и у него сложились некоторые ожидания, что будет в тесте, ничто так не поможет ему цепляться за употребляемые сущности как повторение терминов из заявления. Допускаются мелкие модификации, такие как метаморфоза падежа, числа и даже употребления каждых коротких суффиксов и префиксов – человеческий мозг (тем больше русский) дюже результативно борется с такими трансформациями символов, считая их равнозначными подлинному символу. Чем огромнее мутаций в термине, тем труднее его узнавать читателю. Следственно даже синонимы не приветствуются.
Примеры:

Нехорошо:

TEST_F(MRUCache, MovesLastAccessedItemToFront)
{
  Items.Touch("http://facebook.com/");
  Items.Touch("http://habrahabr.ru/");
  EXPECT_EQ(0, Items.GetIndex("http://habrahabr.ru/"));
}

Отлично:

TEST_F(MRUCache, SetsIndexOfLastTouchedItemToZero)
{
  MRUCache.Touch(Item("http://facebook.com/"));
  MRUCache.Touch(Item("http://habrahabr.ru/"));
  EXPECT_EQ(0, MRUCache.GetIndex(Item("http://habrahabr.ru/")));
}

Отлично:

TEST_F(MRUCache, MovesLastAccessedItemToFront)
{
  MRUCache.Access(Item("http://facebook.com/"));
  MRUCache.Access(Item("http://habrahabr.ru/"));
  EXPECT_EQ(Item("http://habrahabr.ru/"), MRUCache.Front());
}
Явность тестовых данных

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

Нехорошо:

const Common::String SomeUnixPath = GET_WCHAR("/var/log");
const int SomeUnixPathComponents = 2;

...

TEST(UnixPath, ContainsSlashes)
{
  EXPECT_EQ(SomeUnixPathComponents, Paths::Unix(SomeUnixPath).Components());
}

Отлично:

Common::String Path(const char* value)
{
  return GET_WCHAR(value);
}

...

TEST(UnixPath, ContainsSlashes)
{
  EXPECT_EQ(2, Paths::Unix(Path("/var/log")).Components());
}
Явность потока данных по тесту

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

Нехорошо:

MockFileSystem Files;

void AddFile(std::string path, int size)
{
  ON_CALL(Files, Get(path.c_str()).WillByDefault(Return(File(size)));
}

...

TEST(FileStatistics, SumsFileSizes)
{
  AddFile("/bin/ls", 10);
  AddFile("/bin/bash", 20);
  EXPECT_EQ(30, GetStats(Files, "/bin").Size);
}

Отлично:

MockFileSystem Files;

void AddFile(MockFileSystem& fs, std::string path, int size)
{
  ON_CALL(fs, Get(path.c_str()).WillByDefault(Return(File(size)));
}

...

TEST(FileStatistics, SumsFileSizes)
{
  AddFile(Files, "/bin/ls", 10);
  AddFile(Files, "/bin/bash", 20);
  EXPECT_EQ(10   20, GetStats(Files, "/bin").Size);
}

Завершение

В данной статье рассмотрена теория и практика написания читабельных тестов. Приведенные примеры не исчерпывают каждый спектр трюков, которые делают тест отменнее – это каждого лишь выжимки, которые я подметил в своих тестах, ровно как и в чужих просматриваемых тестах. Основное правило – пишите тест самодостаточным. Если что и выносится во вспомогательную функцию, то само имя функции, ровно как и ее сигнатуры обязаны быть настоль явственными, Дабы не появлялось необходимости и мечты глядеть ее реализацию. Переносите, Дабы спрятать несущественное и назвать значительное!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB