Юнит тестирование приватных членов
От: Ceceron Россия  
Дата: 30.03.07 08:32
Оценка: :)
Hi All!

Найдутся ли люди, способные серьезно высказаться по теме.
Немного предистории, так сказать с какого вообще всплыла тема.
В С++ при тестирование нашел достойное применение, в общем то во всех остальных случаях
бесполезному, ключевому слову friend — тестирование закрытых методов и верификация закрытых полей.

Сейчас работаю на C++ CLI и C# во фреймворке. Здесь друзей нет, но зато есть reflection. По моему
мнению это еще лучше, потому как прдакшен код вообще не требует каких либо фишек, необходимых только
для тестирования. Но вот незадача, есть другое мнение — мнение моего менеджера, считающего что reflection
приводит к появлению obscure и fragile тестов. В этом есть конечно поинт, но я уверен что при правильном и
разумном использовании этой технологии тесты будут достаточно понятны и стабильны.

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

Лично меня это ломает, я не хочу оттопыривать наружу ничего кроме интерфейса, поскольку есть понятие инкапсуляции.

Кто что думает? Помогите разобраться, привести веские доводы за и против
Re: Юнит тестирование приватных членов
От: Svjat Украина  
Дата: 30.03.07 09:30
Оценка: 3 (1)
Здравствуйте, Ceceron, Вы писали:

C>Hi All!

.............
C>Кто что думает?

— насчет "перекроить архитектуру, что бы можно было потестить"

так можно делать только если при создании теста обраруживается кривое место в архитектуре.
но "подгонять" под тест однозначно нельзя.

— "тестировать только через интерфейс ( через паблик методы )"
+1

— нужны ли юнит тесты для приватных классов

если очень нужно, то можно использовать некий компромис —
у сборки прописать атрибут
  [assembly: InternalsVisibleTo( "MyAsmUnit" )]    //   у MyAsm.dll

и тогда эта конкретная сборка будет видеть внутренние типы
( в MyAsmUnit.dll будут доступны internal типы MyAsm.dll )

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

можно конечно вообще включить юнит тесты в саму сборку, но имхо это неправильно.

— использовать рефлекшн для тестов — а нафига?
Re[2]: Юнит тестирование приватных членов
От: Ceceron Россия  
Дата: 03.04.07 12:01
Оценка:
Здравствуйте, Svjat, Вы писали:

InternalsVisibleTo валидная тема, я сам недавно на нее вышел, это решило большинство проблем

— "тестировать только через интерфейс ( через паблик методы )"

Не согласен, тестирование через интерфейс — это фактически тестирование черного ящика и никак не юнит тестирование

S>- использовать рефлекшн для тестов — а нафига?


с помощью рефлекшн можно создовать accessor-ы для верифицикации состояние приватных членов, очень удобная вещь.
Re[3]: Юнит тестирование приватных членов
От: Andrei N.Sobchuck Украина www.smalltalk.ru
Дата: 03.04.07 14:13
Оценка: 2 (2)
Здравствуйте, Ceceron, Вы писали:

C>— "тестировать только через интерфейс ( через паблик методы )"


C>Не согласен, тестирование через интерфейс — это фактически тестирование черного ящика и никак не юнит тестирование


S>>- использовать рефлекшн для тестов — а нафига?


C>с помощью рефлекшн можно создовать accessor-ы для верифицикации состояние приватных членов, очень удобная вещь.



Я пробовал тестировать не через публичный интерфейс. Реально ломается при каждому рефакторинге модуля. Имхо, это губит всю идею ЮТ на корню.
Я ненавижу Hibernate
Автор: Andrei N.Sobchuck
Дата: 08.01.08
!
Re[3]: Юнит тестирование приватных членов
От: Svjat Украина  
Дата: 03.04.07 14:30
Оценка:
Здравствуйте, Ceceron, Вы писали:

C>— "тестировать только через интерфейс ( через паблик методы )"

C>Не согласен, тестирование через интерфейс — это фактически тестирование черного ящика и никак не юнит тестирование

а зачем вообще тогда отделять интерфейс от реализации?
если писать тесты для всех приватных методов, да еще проверять состояние приватных членов, то мало того что времени (объем кода) в юнит тестах будет больше самого кода — главное что это смешает разные уровни процесса.

-------------


давай на примерах, вот мой:

в САД есть класс — HrenovinaStorage

его Responsibilities — постоянное хранение объектов типа Hrenovina,
средства добавления / удаления / получения объекта по ключу

соотв. у него есть след. методы
public void Start();
public void Stop();
public void Add( Hrenovina hrn );
public void Remove( string key );
public void Clear();
public Hrenovina Get( string key );

теперь пишем простой тест:
  
   HrenovinaStorage storage = new HrenovinaStorage( TmpName );
   storage.Start();
   storage.Clear();   // сразу все чистим

   storage.Add( new Hrenovina( key ) );   // добавляем новый объект
   storage.Get( key );    // проверяем добавил или нет 
    
   storage.Remove( key ); // ...
   storage.Get( key );    // ...

   // теперь проверяем как он перманентно хранит объекты

   storage.Add( new Hrenovina( key2 ) );
   storage.Add( new Hrenovina( key3 ) );
   storage.Stop();
   storage = null;
   HrenovinaStorage storage = new HrenovinaStorage( TmpName );
   storage.Start();
   storage.Get( key2 );   // проверяем сохранил или нет
   storage.Get( key3 );
   // -------------------

все, если тесты срабатывают, значит класс делает то, что должен

теперь покажи где здесь место для проверки приватных методов и полей? ( а они таки есть ).
Re[4]: Юнит тестирование приватных членов
От: Ceceron Россия  
Дата: 04.04.07 08:28
Оценка:
Здравствуйте, Svjat, Вы писали:

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

Допустим те хреновины, что хранятся в HrenovinaStorage, не только там хранятся, но еще и как то
обрабатываются. Ну типа, если item уже находится в хранилище 5 суток, то у него надо изменит один
из филдов, ну допустим молоко прокисло, выставляем флаг на удаление. При этом есть набор правил для
разных видов продуктов. Допусти мука 3 года там может валятся. У нас появляются как минимум список правил
и менеджер, который чистит хранилище по этим правилам. Причем этот менеджер получает этот список из какого-
либо outside ресурса, файла к примеру. Само-собой эти члены должны быть приватными, кому какое дело по каким
правилам производится хранение, главное что бы со склада можно было что то взять или положить туда.

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

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

S>давай на примерах, вот мой:


S>в САД есть класс — HrenovinaStorage


S>его Responsibilities — постоянное хранение объектов типа Hrenovina,

S>средства добавления / удаления / получения объекта по ключу

S>соотв. у него есть след. методы

S>
S>public void Start();
S>public void Stop();
S>public void Add( Hrenovina hrn );
S>public void Remove( string key );
S>public void Clear();
S>public Hrenovina Get( string key );
S>

S>теперь пишем простой тест:
S>
  
S>   HrenovinaStorage storage = new HrenovinaStorage( TmpName );
S>   storage.Start();
S>   storage.Clear();   // сразу все чистим

S>   storage.Add( new Hrenovina( key ) );   // добавляем новый объект
S>   storage.Get( key );    // проверяем добавил или нет 
    
S>   storage.Remove( key ); // ...
S>   storage.Get( key );    // ...

S>   // теперь проверяем как он перманентно хранит объекты

S>   storage.Add( new Hrenovina( key2 ) );
S>   storage.Add( new Hrenovina( key3 ) );
S>   storage.Stop();
S>   storage = null;
S>   HrenovinaStorage storage = new HrenovinaStorage( TmpName );
S>   storage.Start();
S>   storage.Get( key2 );   // проверяем сохранил или нет
S>   storage.Get( key3 );
S>   // -------------------
S>

S>все, если тесты срабатывают, значит класс делает то, что должен

S>теперь покажи где здесь место для проверки приватных методов и полей? ( а они таки есть ).
Re[5]: Юнит тестирование приватных членов
От: Andrei N.Sobchuck Украина www.smalltalk.ru
Дата: 04.04.07 09:55
Оценка:
Здравствуйте, Ceceron, Вы писали:

C>То есть ты хочешь сказать, что любой класс можно привести к виду твоего примера?


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

Теперь смотри. Парсер конфигурацию парсит и передаёт список правил твоей экспайрилке. Соответсвенно, парсер файла — отдельная функциональность и тестируется отдельно. Установка правил (неважно отпарсенных или созданных руками) — тестируется отдельно. Проверка объектов на экспайренность согласно правилам — тестируется отдельно.

Приватные члены нигде не фигурируют.
Я ненавижу Hibernate
Автор: Andrei N.Sobchuck
Дата: 08.01.08
!
Re[6]: Юнит тестирование приватных членов
От: Ceceron Россия  
Дата: 04.04.07 12:06
Оценка:
Здравствуйте, Andrei N.Sobchuck, Вы писали:

Парсер файла — это отдельный класс. Но агрегируется он как приватный член нашего класса. "Установка правил" — это приватный член, который использует парсер файла для получения правил в каком-то формате и выполняет их настройку в соответствиии со своим алгоритмом. Что значит "тестируется отдельно"? Ты этот метод вынесешь в отдельный класс?

ANS>Теперь смотри. Парсер конфигурацию парсит и передаёт список правил твоей экспайрилке. Соответсвенно, парсер файла — отдельная функциональность и тестируется отдельно. Установка правил (неважно отпарсенных или созданных руками) — тестируется отдельно. Проверка объектов на экспайренность согласно правилам — тестируется отдельно.


ANS>Приватные члены нигде не фигурируют.
Re[5]: Юнит тестирование приватных членов
От: Svjat Украина  
Дата: 04.04.07 19:19
Оценка: +1
Здравствуйте, Ceceron, Вы писали:

C>Допустим те хреновины, что хранятся в HrenovinaStorage, не только там хранятся, но еще и как то

C>обрабатываются. Ну типа, если item уже находится в хранилище 5 суток, то у него надо изменит один
C>из филдов, ну допустим молоко прокисло, выставляем флаг на удаление. При этом есть набор правил для
C>разных видов продуктов. Допусти мука 3 года там может валятся.

1. предположем добавилось требование "удалять просроченные продукты"

добавляем в Hrenovina
     public bool Expired { get; } 
     public DataTime FinalDatetime { get; set; }

добавляем еще один тест для HrenovinaStorage
[Test]
public void TestExpired()
{
   Hrenovina hrenovina;
   HrenovinaStorage storage = new HrenovinaStorage( TmpName );
   storage.HrenovinaExpired += OnExpired;
   storage.Start();
   
   hrenovina = new Hrenovina();
   hrenovina.FinalDateTime = .... // 
   Expired = false;
   storage.Add( hrenovina );
   ....... // ждем соотв. время
   Assert.IsTrue( Expired );
   Assert.IsNull( storage.Get( hrenovina.key ) );
   ..............
   // + еще десяток подобных тестов
}
private bool Expired;
private void OnExpired(){ Expired = true; /* ну или проверяем время удаления */ }

на данном этапе больше ничего не нужно, логика действий хранилища ясна.

2. возникает необходимость в более "умной" проверке на просроченность.
тогда логику этой проверки выделяем в отдельный класс HrenovinaInspector,
экземпляр кот. передаем хранилищу и тестим его отдельно

кстати, вот ты сам и пишешь:

C>и менеджер, который чистит хранилище по этим правилам.

> Само-собой эти члены должны быть приватными, кому какое дело по каким
C>правилам производится хранение, главное что бы со склада можно было что то взять или положить туда.

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

bool HrenovinaInspector.HrenovinaExpired( Hrenovina hrn );
прогоняем в тесте этот метод с 10-30 разными вариантами (Hrenovina hrn) и все.

3. если правила нужно загружать из внешнего источника:
тогда слегка изменяем тест. (2)
т.е. после создания объекта HrenovinaInspector засовываем в него набор тестовых правил.

--------

другое дело, что сам класс инспектора может нет смысла высовывать наружу сборки, тогда на помощь приходит InternalsVisibleTo
Re: Юнит тестирование приватных членов
От: Svjat Украина  
Дата: 04.04.07 19:22
Оценка:
Здравствуйте, Ceceron, Вы писали:

C>................., есть другое мнение — мнение моего менеджера, считающего что reflection


а какого .... менеджер в тех. вопросы лезет???
Re: Юнит тестирование приватных членов
От: _doctor Финляндия http://agilesoftwaredevelopment.com
Дата: 06.04.07 20:29
Оценка:
Здравствуйте, Ceceron, Вы писали:

C>Hi All!


C>Найдутся ли люди, способные серьезно высказаться по теме.

C>Немного предистории, так сказать с какого вообще всплыла тема.
C>В С++ при тестирование нашел достойное применение, в общем то во всех остальных случаях
C>бесполезному, ключевому слову friend — тестирование закрытых методов и верификация закрытых полей.
...
C>Лично меня это ломает, я не хочу оттопыривать наружу ничего кроме интерфейса, поскольку есть понятие инкапсуляции.

C>Кто что думает? Помогите разобраться, привести веские доводы за и против


С моей точки зрения наличие значительного количество private методов, нуждающихся в тестировании, говорит о том, что внутри класса просятся родиться другие сущности
Например, если необходимо оттестировать XML-parsing часть класса CConfigFileReader, очень может быть что просится наружу класс CXmlParser, а может быть и CFileReader.

Если же Ваш метод разработки не предусматривает частого рефакторинга, то private-методы можно сделать тестируемыми, передекларировав как protected и создав dummy наследника исключительно в целях тестирования. Инкапсуляция слегка нарушится, но код станет более тестируемым.

P.S.
Немного оффтопик: в Test-Driven-Development проблемы аналогичные Вашим редки, ибо почти всё становится тестируемым ещё до момента рождения.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Chief Software Engineer,
Scrum Master, Symbian
Re[7]: Юнит тестирование приватных членов
От: Andrei N.Sobchuck Украина www.smalltalk.ru
Дата: 10.04.07 07:24
Оценка: +1
Здравствуйте, Ceceron, Вы писали:

C>Парсер файла — это отдельный класс. Но агрегируется он как приватный член нашего класса. "Установка правил" — это приватный член, который использует парсер файла для получения правил в каком-то формате и выполняет их настройку в соответствиии со своим алгоритмом. Что значит "тестируется отдельно"? Ты этот метод вынесешь в отдельный класс?


Естественно. Если у него свой хитроалгоритм, то что он делает в "чужом" классе? Если ты таким способом экономиш классы, то это зря.
Я ненавижу Hibernate
Автор: Andrei N.Sobchuck
Дата: 08.01.08
!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.