четверг, 13 февраля 2014 г.

Ещё раз о "тестировании приватных (private) методов"

Многие люди занимающиеся тестированием начиная от Ника Ходжеса и Сергея Теплякова и кончая вашим покорным слугой говорят и пишут о том, что приватные методы ТЕСТИРОВАТЬ НЕ НАДО, да и НЕЛЬЗЯ.

О том, что ТЕСТИРОВАТЬ надо ПУБЛИЧНЫЕ методы.

Ну на КРАЙНИЙ случай - ЗАЩИЩЁННЫЕ (protected). Ну это "я от себя добавил".

Однако находится много людей, которые ПЫТАЮТСЯ ЭТО ДЕЛАТЬ.

Вот пример (надеюсь, что автор не обидится на меня):

http://programmingmindstream.blogspot.ru/2014/02/q.html?showComment=1392299266663#c4542358958422539893

Цитирую:

А я выкрутился таким образом.

Класс который необходимо тестировать.

FDiscountSystem: TDiscountSystem;

В нём есть приватная процедура DestroyDSItems;

Далее делаем хелпер класс, и в ней пишем процедуру которую будем тестировать:

TDiscountSystemHelper = class helper for TDiscountSystem
procedure GetDestroyDSItemsInHelper;
end;

procedure TDiscountSystemHelper.GetDestroyDSItemsInHelper;
var
MethodPtr: procedure of object;
begin
MethodPtr := Self.DestroyDSItems;
MethodPtr;
end;

Собственно сам тест:

procedure TestTDiscountSystem.TestDestroyDSItems;
begin
FDiscountSystem.GetDestroyDSItemsInHelper;
CheckTrue(FDiscountSystem.CountItems=0, 'Мы уничтожаем все объекты и обнуляем массив');
end;

Конец цитаты.

Понимаете в чём дело?

"Выкручиваться" можно МНОГО и ПО-РАЗНОМУ.

Например:

1. Те же helper'ы.
2. Вычисление адреса приватной процедуры.
3. Собственная эмуляция RTTI (регистрация ПРИВАТНЫХ методов в "мапе" строка-указатель).

И т.д. и т.п.

ВЫКРУТИТЬСЯ - ВСЕГДА МОЖНО.

Но! Что я хочу сказать?

Так скажем по стопам Теплякова и Ходжеса.

ПРИВАТНЫЕ методы на то и ПРИВАТНЫЕ, что их НЕЛЬЗЯ вызывать НАПРЯМУЮ.

Почему?

Попробую объяснить. Опять же - "по стопам".

В чём НА САМОМ ДЕЛЕ суть ПРИВАТНЫХ методов и ЗАЧЕМ они вообще используются.

ПРИВАТНЫЕ методы отличаются от ПУБЛИЧНЫХ и ЗАЩИЩЁННЫХ тем, что они НА САМОМ деле подразумевают некоторые ИНВАРИАНТЫ поведения системы.

Если таких ИНВАРИАНТОВ нет, то это не ПРИВАТНЫЕ методы, а скорее всего - ЗАЩИЩЁННЫЕ.

Какие бывают ИНВАРИАНТЫ?

Ну скажем такие:
1. ПРИВАТНЫЕ методы подразумевают, что ДАННЫЕ правильного формата. (Надо объяснять?)
2. ПРИВАТНЫЕ методы подразумевают, что ВСЕ ОБЪЕКТЫ созданы. (Ну скажем, что все входные объектные параметры <> nil).
3. ПРИВАТНЫЕ методы подразумевают, что выполнен определённый "протокол настройки". (Ну например база открыта или вообще клиент залогинен).
4. ПРИВАТНЫЕ методы подразумевают, что выполнены определённые ГРАНИЧНЫЕ условия (например индекс в массиве - валидный).

Ну и т.д. и т.п.

Если же методы которые ОБЪЯВЛЕНЫ ПРИВАТНЫМИ никаких ИНВАРИАНТОВ не содержат, то это НА САМОМ деле как минимум - ЗАЩИЩЁННЫЕ методы. И в ПРИВАТНЫЕ они попали "по ошибке".

Если они "попали по ошибке", то СМЕЛО делайте эти методы ЗАЩИЩЁННЫМИ, а к защищённым методам можно добраться уже многими ДРУГИМИ способами. Самый правильный конечно - создание класса-НАСЛЕДНИКА (тут правда может понадобиться Dependency Injection, чтобы иметь возможность "подменять" проектные класс на его наследника, создаваемого исключительно для тестов).

Но! Если ПРИВАТНЫЕ методы СОДЕРЖАТ подобные ИНВАРИАНТЫ, то тестировать их НАПРЯМУЮ скорее всего - НЕЛЬЗЯ.

Если они "плохо написаны" БЕЗ ЯВНОГО указания ИНВАРИАНТОВ, то они будут приводить к "плавающим ошибкам".

Если они "хорошо написаны". С ЯВНЫМ указанием ИНВАРИАНТОВ через скажем те же Assert'ы, то они будут ПАДАТЬ. Когда ИНВАРИАНТЫ не выполняются.

Это кстати один из "признаков" того, что ПРИВАТНЫЙ метод является "истинно приватным" - написаны в нём Assert'ы или нет.

Итак. Подводя итоги.

ПРИВАТНЫЕ методы делаются ПРИВАТНЫМИ - не "по пьяни" и не по "злому умыслу".

Они делаются приватными - ОСОЗНАННО. Ну или "разработчик недодумал".

Если "разработчик недодумал", то СМЕЛО делайте их ЗАЩИЩЁННЫМИ и ТЕСТИРУЙТЕ себе на здоровье. Делая наследников и применяя Dependency Injection (если это НЕОБХОДИМО).

Если же это РЕАЛЬНО ПРИВАТНЫЕ методы, то тестировать их НЕЛЬЗЯ.

Точнее скажем так - тестировать их глупо.

Что ещё хочу сказать?

ВСЕ архитектуры - они НЕ ИДЕАЛЬНЫ.

Во многих архитектурах есть ПРИВАТНЫЕ методы, которые ТАКОВЫМИ быть НЕ ДОЛЖНЫ.

Понятное дело. "Унаследованный код" и всё такое.

Тут скажу вот что - если ПРИВАТНЫЕ методы МЕШАЮТ вам НАЧАТЬ тестирование того или иного куска функциональности - конечно же - "выкручивайтесь". Ищите методы "тестирования ПРИВАТНЫХ методов" (часть озвучена выше).

Чтобы НЕ СТОЯТЬ НА МЕСТЕ.

Чтобы не было "отговорок" типа - "не могу протестировать, потому, что ПРИВАТНЫЕ методы".

Но!

КАЖДЫЙ СЛУЧАЙ "выкручивания" с ПРИВАТНЫМИ методами надо заносить в "бортовой журнал". И ставить ЖИРНУЮ галочку.

Чтобы ПОТОМ вернуться к этому случаю и ПЕРЕСМОТРЕТЬ его.

И этот "бортовой журнал" надо ЧИСТИТЬ время от времени.

Ну мы примерно так и делаем. И у нас число тестов перевалило за несколько тысяч. И "бортовой журнал" у нас обычно - "чист".

Чего и вам желаю :-)

3 комментария:

  1. Есть моменты когда мой класс или программа получают не те данные которые я задумывал при реализации. В нашем екзешнике реализован перехват эксепшенов которые выставляют "определенного уровня сигнал"(у нас есть индикатор в маленьком углу :)). Но как показывает прктика эксепшн о том что не так ого поля в БД заставляет проводить "время" в отладке. Как Вы считаете, когда и как стоит принимать в коде assert вместо применения exception. и момент с тем что если я повешу ассерт а данных действительно нет, (в билде). Что делать. Всё таки exception ?

    ОтветитьУдалить
    Ответы
    1. Не.. Смотрите.

      Тут надо ПОНЯТЬ одно!

      Должна ошибка до пользователя доехать или нет. Или это ИНВАРИАНТ.

      Если это ИНВАРИАНТ, то это assert.

      если это ПЛАНИРУЕМАЯ ошибка, то это Exception.

      Если я непонятно ответил - пишите, попробую понятнее объяснить.

      Слово ИНВАРИАНТ понятно?

      Удалить
    2. Кстати вот ссылка про использование Assert - http://18delphi.blogspot.ru/2013/04/blog-post.html

      Удалить