пятница, 30 июня 2017 г.

Объектно ориентированное юниттестирование

Возникло неудержимое желание написать статью. :)

Последнее время все мои мысли занимает true oop. Вокруг в этом плане царит уныние. И хуже того. Читая достаточно старые книги, например про Smalltalk, я с грустью наблюдаю объекты, количество методов у которых исчисляется десятками. Это какой-то неправильный ООП.

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

Но что не так в тестовых фреймворках сейчас? - спросите вы. Вообщем ничего страшного с ними нет, только они совсем не про ООП.


Взять к примеру googletest:

Tests use assertions to verify the tested code's behavior.

Но давайте заглянем в книги... к примеру в эту: Шаблоны тестирования XUnit, которую я крайне рекоммендую.

Нормальный способ проверки результата называется проверкой состояния (State Verification).

Проверка поведения сложнее в связи с динамической природой поведения и требует введения тестовых двойников разных типов. Обычно здесь используются различные Mock-фреймворки и тд.

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

Недавно слышал предложение о том, что std::string слишком велик (150 методов), давайте растащим его на свободные функции. Ну йо...
Честно признаюсь, по работе я все тесты делю на секции - Given/When/Then. Но последнее время я понимаю, что и это тоже ошибка. Тот самый процедурный подход в действии.

Проверка поведения - это не про ООП!


Каким же должен быть объектно ориентированный подход в плане юниттестового фреймворка?

Ну для начала в ООП фреймворке не должно быть тестовых методов. Объект, который описывает в себе несколько разных тестов как минимум нарушает SRP. Каждый объект должен делать свое дело.

В ООП фреймворке не должно быть утверждений. Я программирую на c++, и в c++ это обычно реализуется через макросы, которые совсем не про ООП. Кроме того выделяя утверждения - мы даем возможность писать в одном тесте по нескольку утверждений. Хотя все гуру говорят что это плохо, но тем не менее все так делают по моим наблюдениям. Кроме того отдельные утверждения как бы подразумевают, что существует и другой код, и что система нуждается в настройке - а это процедурный подход. Правильно приготовленный объект в настройке не нуждается!

Все тесты реализуются через проверку состояния.

В ООП фреймворке должна быть объектная композиция всех тестов. В Объектно-ориентированном программировании задачи решаются через композицию взаимосвязанных объектов. Логика тестирования должна быть заложена на этапе создания тестового приложения.

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

Современные фреймворки слишком толерантно относятся к вкусам пользователей. Позволяя писать почти как угодно. Никак не дисциплинируя их. :) Так нельзя!

В процессе размышлений и экспериментов у меня сформировалась следующая концепция:

Существуют классы тестов. - Тест сам по себе является утверждением. Каждый вид теста рассчитан на определенный тип проверок. Например есть TestEqual - класс, заточенный на проверку равенства. Таких тестов будет некоторое количество встроенных, и пользователь сможет добавлять свои, если очень понадобиться.

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

Тесты не умеют принимать на вход любые типы. Тесты заточены на строки и текстовые представления. Пользователь может сделать текстовые представления для своих классов и пользоваться стандартными тестами.

В результате это выглядит примерно так.

Не знаю, можно ли этим уже пользоваться - сам попробую в ближайшее время. Но за обратную связь буду благодарен.

Follow me on github

PS: Наверное после долгого перерыва статья получилась немного сумбурная. Ну уж извиняйте. :)