Показаны сообщения с ярлыком DUnit. Показать все сообщения
Показаны сообщения с ярлыком DUnit. Показать все сообщения

вторник, 4 июня 2013 г.

Добрались мы до той ситуации, когда тесты не проходят на ВСЕХ машинах

Добрались мы до той ситуации, когда тесты не проходят на ВСЕХ машинах.

И при этом - КОД ведёт себя - ПРАВИЛЬНО. Ну насколько он может. Себя "правильно вести".

Пока додумались только до "квантификаторов" типа EtalonNeedsComputerName (самое волюнтаристское), EtalonDependsFromOS, EtalonDependsFrom64System.

P.S. И далеко не ВСЁ кстати зависит от нас. Мы например выяснили один простой факт, что в 32-битном приложении exception под OS 64 "тупо" не всплывает из Windows-процедуры. WinProc. В итоге - то, что является ошибкой под 32 бита - отнюдь не является таковой под 64 бита. Мы что-то не так делаем? Руки у нас кривые?

вторник, 7 мая 2013 г.

"Всё уже придумано до нас"

http://delphi.frantic.im/delphi-tdd/

Надеюсь, что автор не обидится, если я его процитирую:

"Беда вводных статей по тестированию кода в том, что они описывают правила написания тестов как догму: “делайте так, только так и никак иначе, потому что так надо”. Догма не гибкая и убивает креативность, а написание тестов требует гибкости и креативности. В этом случае лучше принять правило кармы: “делайте добрые дела и с вами будут случаться хорошие вещи, делайте их так, как умеете и так, как вам это нравится”. Эту формулировки мысли я почерпнул из замечательной книги –The Way of Testivus (очень рекомендую к прочтению). Перевод основных ее мыслей – в конце статьи.
Нужно понимать, что идеально правильного теста не существует. Вспомните свой код, который казался Вам вершиной инженерной мысли год или два назад. Наверняка сейчас, набравшись больше опыта, Вы его таким уже не считаете. С тестами та же история – идеальный тест сегодня уже не будет таким для Вас завтра. Поэтому, не пытайтесь сделать все безупречно! Начинайте с простого! Экспериментируйте! Развивайте свой код и свои тесты итерациями, каждый раз улучшая их структуру и содержание. А если Вы сталкиваетесь с трудностями – почитайте советы опытных разработчиков, наверняка они через это уже прошли.
Также забудьте о 100% покрытии кода тестами (о котором так любят говорить задроты). Оно просто того не стоит. Здесь отлично работает правило 80/20. Всего 20% усилий сможет дать Вам проверку 80% Вашего кода (а это намного больше чем вообще ничего). К остальным 80% усилий, на мой взгляд, относится тестирование графического интерфейса. Если Вы построили приложение так, что UI используется только для передачи данных во внутрь приложения и для вывода результатов, достаточно протестировать только этот внутренний движок.
И немого информации для тех, кто считает тестирование слишком затратным (наверняка, такое ошибочное мнение складывается только из-за небольших трудностей, которые тяжело преодолеть думая о тестах как о догме). Написание теста действительно занимает больше времени, чем ручная проверка и исправление проблем через отладчик, прямо на месте. Но если посмотреть в перспективе – это очень выгодное вложение. Автоматический тест выполняется за доли секунд, ему никогда не лень и он всегда внимателен (в отличии от человека). Если одну и ту же часть программы Вы проверяете 5-7 раз вручную, уже на этом этапе тест бы себя окупил. А если подумать о регрессии (ошибка в одном месте ломает что-то в другом) тесты – незаменимая вещь."
Я лично - готов подписаться под КАЖДОЙ буквой из приведённой цитаты. Лучше - и не скажешь.

Опять же ЗНАКОМО на все 100%:

"Но есть проблемы, не весьма инженерные. Если Вы работаете в команде над проектом заказчика, последний с 99% вероятностью будет против введения юнит-тестирования, т.к. это не дает очевидных видимых для него улучшений. Можно попробовать объяснить, но практика показывает что лучше начинать изменения изнутри, маленькими шагами. Попробуйте внедрить тесты в проект, после нескольких месяцев вы сможете понять насколько проще стал процесс разработки.

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

Понимаю, что перебираю с цитированием, но вот то - до чего я дошёл (и не раз озвучивал устно), но так и не смог написать и то, что "за меня" написал автор статьи:

"Советы из личного опыта:
  1. Делайте тесты «плоскими», без if, while и т.д. В тесте не должно быть логики.
  2. Запускайте тесты как можно чаще. Слишком медленные можно отключить в DUnit (если то, над чем Вы работаете, на них не влияет).
  3. Начинайте с простого. Да, в интернете есть много хитрых инструментов для тестирования, не повышайте сложность без необходимости.
  4. Если используете Continuous Integration, включите запуск консольного варианта тестового проекта в скрипт сборки.
  5. Тестирование не сделает Ваш проект успешным и не избавит от 100% багов. Но очень в этом поможет.
Советы от Testivus (вольный перевод)
  1. Если пишете код, пишите тесты
  2. Не воспринимайте тестирование как догму
  3. Воспринимайте тестирование как карму
  4. Думайте о коде и тесте как об одном целом
  5. Тестирование важней чем ограничивающие правила
  6. Лучше всего писать тесты когда код еще горячий
  7. Тесты, которые не запускаются систематично, пустая трата времени
  8. Неидеальный тест сегодня лучше, чем идеальный тест когда-то в будущем
  9. Некрасивый тест лучше чем вообще без теста
  10. Хорошие тесты проваливаются"
И КРАЙНЕ советую - прочитать ВСЕ комментарии внизу статьи.

Там ещё маленький холиварчик про FreeAndNil. Я лично предпочитаю использовать его - ВЕЗДЕ.

Update. Один мой коллега, который ПЛОТНО занимается Unit-тестированием, после прочтения данной статьи написал лишь - "Да, сильно.". Это многое значит.

воскресенье, 7 апреля 2013 г.

Тесты DUnit и QATime

Маленькая ремарка. Не мог не сказать. Пока не забыл.

Тесты DUnit и AQTime. Просто созданы друг для друга.

Практически никакой возни с триггерами. Поставил в AQTime триггер на тело теста. Выбрал интересующий тест галочкой. И всё - профилируй себе на здоровье.

Главное конечно этот тест выделить. Но это - ОЧЕНЬ часто возможно. Я постараюсь в своих постах рассказать - как.

Учитывая то, что AQTime порой плохо переваривает БОЛЬШИЕ проекты, а зачастую и падает на них, то без триггеров - никуда. И посему на БОЛЬШОМ РЕАЛЬНОМ проекте подобрать "правильный" набор триггеров и поддерживать его при профилировании - нетривиальная задача. Чаще оказывается проще выделить из этого набора триггеров отдельностоящий тест и в конечном итоге оперировать только ЭТИМ тестом и ОДНИМ триггером.

пятница, 5 апреля 2013 г.

Почему так важны тесты?

Не знаю как у вас, а у нас в организации - любая ошибка замеченная пользователем, а особенно  - AV или, не дай бог, assert - это "скандал и разборки". Чтобы не погрешить против истины и никого не обидеть - скажу мягше - "все расстраиваются".

И потому нам очень важно тестирование.

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


Я ещё не убедил вас в важности тестов? Я буду продолжать это делать.

Попробуйте наконец. Может и вам понравится.

среда, 3 апреля 2013 г.

"Нельзя вот так просто взять и начать использовать TDD для Delphi"?



Можно! И НУЖНО!

И не только для Delphi. Для ВСЕГО! Для ЛЮБОГО кода!

Который написан - ВАМИ.

Для начала.

Напишите хотя бы "атомарный тест" - http://18delphi.blogspot.com/2013/04/blog-post_2326.html

Он вам поможет хотя бы при переходе на НОВУЮ версию Delphi.

Где "эти умники" поменяли AnsiString на WideString.

Правите ошибку, которая пришла вам от пользователей или от Группы Качества - напишите тест - http://18delphi.blogspot.com/2013/03/blog-post_54.html.

Не считайте себя "ГЕНИАЛЬНЫМ программистом, который всё предусмотрел". Такие в природе - НЕ ВСТРЕЧАЮТСЯ,

Есть в ТЗ фраза - "надо прохерачить красную линию". Вы программируете эту линию? ПИШИТЕ тест!

Не зря "люди которые умнее вас" - внесли эту фразу в ТЗ. Они - ДУМАЛИ. Долго и нудно.

И не зря они эту фразу там написали. Это - ТРЕБОВАНИЕ к системе.

А раз есть требование - значит его НАДО ПРОВЕРЯТЬ. Пишите тест!

Не дайте "этим умникам" выставить вас идиотом. БЫВАЮТ противоречивые ТЗ. А значит - БУДУТ противоречивые требования.

Так пусть "эти умники" - разбираются с ПРОТИВОРЕЧИВЫМИ требованиями. Которые показывают ДЕТЕРМИНИРОВАННЫЕ тесты. У тестов - НЕТ ЛИЧНЫХ предпочтений!

ВЫ - своё дело сделали. Написали код и и проверку к нему.

Пусть другие теперь - "чешут репу".

Я вас не убедил?

Идём дальше.

Мне вот тут начали писать - "вы пишете КАК, но не пишите ЗАЧЕМ". В тут я и пытаюсь донести - ЗАЧЕМ.

Зачем? Очень просто - есть ТРЕБОВАНИЯ, есть ошибки. Так или иначе - вы что-то кодируете. Если просто кодировать - не увидит НИКТО. Если при этом ещё и написать тест - УВИДЯТ ВСЕ. В частности - ваши коллеги, которые работают рядом.

Пока - просто поверьте мне на слово.

Требования и ошибки писали неглупые люди. И им ВАЖНО, чтобы ТРЕБОВАНИЯ выполнялись, а ОШИБКИ - больше не повторялись.

Посему имеем две ветки тестирования - функциональное - http://ru.wikipedia.org/wiki/%D0%A4%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5 и регрессионное - http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D1%80%D0%B5%D1%81%D1%81%D0%B8%D0%BE%D0%BD%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5.

Функциональное - это проверка ТЗ, регрессионное - проверка УЖЕ встреченных ошибок.

Бывает ещё - нагрузочное тестирование - http://ru.wikipedia.org/wiki/%D0%9D%D0%B0%D0%B3%D1%80%D1%83%D0%B7%D0%BE%D1%87%D0%BD%D0%BE%D0%B5_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5

Но это - ВООБЩЕ уже отдельная тема.

Не пытайтесь сделать "супер-тест", который "протестирует ВСЁ".

Вы потерпите неудачу. Если не потерпите, то обязательно напишите мне. Мне очень интересно на такой тест посмотреть.

Серебряной пули - нет.

"Путь в две тысячи ли начинается с одного шага".

Не думайте - "как протестировать ВСЁ и СРАЗУ".

Начните с МАЛОГО!

Пишите тесты по ТЗ и по ОШИБКАМ.

Я готов поспорить - после пары месяцев работы у вас наберётся внушительная база тестов.

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

ПЕРВЫЕ тесты писать долго и нудно. По себе знаю. У вас ещё нет должной инфраструктуры.

Но переступите через себя. В конце-концов - ПРОФЕССИОНАЛ вы или нет!?

Дальше - будет проще. Будет нарастать инфраструктура. База знаний. Эталоны. Типичные тесты.

Вы будете "наполнять скелет мясом".

И когда вы очередной раз исправите "эту маленькую ошибку от ГК" - у вас упадёт половина тестов.

И вы оперативно будете знать - "что, когда и зачем".

Исправите ОДНУ запятую и у вас пройдут ВСЕ тесты.

И вы напишете - НОВЫЙ. На эту "запятую". И может быть - скажете мне - "спасибо". А может - не скажете. Я - не гордый. Не расстроюсь.

Это я попытался написать - ЗАЧЕМ. Если опять же не убедил - буду писать дальше. Буду КРАЙНЕ признателен, если кто-нибудь пришлёт КОНКРЕТНЫЕ вопросы. Многие вещи мне лично кажутся - "очевидными", но это не значит, что они действительно очевидны.

Поверьте мне на слово.

Я уже не раз пытался "продать слона" (то бишь тесты) и не очень удачно пока. Значит я был неубедителен. Значит не нашёл нужных слов.

Если вы поняли ЗАЧЕМ, то - КАК - я буду долго и вдумчиво рассказывать. Следите за обновлениями.

Или я вас всё ещё не убедил?

P.S. Если у вас наберётся более менее внушительная база тестов - просто запустите из под утилитой QACoverage и вы увидите - сколько процентов кода у вас УЖЕ покрыто, а над сколькими - надо ещё работать.

вторник, 2 апреля 2013 г.

Тесты и эталоны

Очень большой класс тестов можно сделать по-следующему принципу:


CheckWithEtalon(DoSomethingThatProduceFileAndReturnItsName);


DoSomethingThatProduceFileAndReturnItsName - делает всю работу теста, сохраняет результаты в файл и возвращает ссылку на него.

CheckWithEtalon - проверяет (побайтно) указанный файл на совпадение с эталоном. Если эталона нет, то указанный файл объявляется эталоном и заносится в БД эталонов. О чём делается соответствующая запись в лог.

Всё просто. Написали тест. Вывели результаты в файл. Получили эталон на первом прогоне.

Дальше просто смотрим на совпадение с эталоном. Если тест не прошёл - смотрим дифу. Процесс вывода дифы - тоже можно автоматизировать.

Главное, что НИКАКИЕ эталоны РУКАМИ - делать не приходится. Алгоритм сам готовит себе эталонные данные. И потом - сам себя проверяет.

Попробуйте. Может быть - вам понравится.

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

Про конкретную реализацию этого на DUnit - я чуть позже напишу.

... to be continued ...

Анти-тесты

Ещё более интересная тема, чем просто тесты. Проверка AV, выхода за границ массивов и прочих граничных условий. Да так, чтобы гарантировать ДЕТЕРМИНИРОВАННОСТЬ поведения. ExpectedException - из этой серии.

... to be continued ...

пятница, 29 марта 2013 г.

Шаблоны и тесты DUnit

Предыдущая серия была тут:
http://18delphi.blogspot.com/2013/03/dunit_29.html

Будем развивать идею тестирования TIntStack и TStringStack. Только сделаем это ОДНОЙ примесью. Убрав дубликаты вида:

procedure TIntStackTest.DoIt;
const
 cEtalons : array [0..3] of integer = (10, 20, 3, 5);
var
 l_S : TIntStack;
 l_I : Integer;
begin
 l_S := TIntStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TIntStackTest.DoIt


и:

procedure TStringStackTest.DoIt;
const
 cEtalons : array [0..3] of String = ('мыма', 'мыла', 'раму', 'весело');
var
 l_S : TStringStack;
 l_I : Integer;
begin
 l_S := TStringStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TStringStackTest.DoIt


Нарисуем следующую диаграмму:

Получаем следующий код:

SandBox.dpr:
 
program SandBoxTest;
 
uses
  TestFrameWork
  GUITestRunner,
  IntStack,
  IntStackTest,
  StringStack,
  StringStackTest,
  IntStackTestViaMixIn,
  StringStackTestViaMixIn
  ;
 
begin
 GUITestRunner.RunRegisteredTests;
end.

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

StackTest.imp.pas:

{$IfNDef StackTest_imp}
 
{$Define StackTest_imp}
 TEtalonData = ItemsHolder;
 
 _StackTest_ = {mixin} class(TTestCase)
 published
   procedure DoIt;
 protected
 // protected methods
   function GetEtalonData: TEtalonData; virtual; abstract;
   function ArrayToEtalon(const aData: array of _ItemType_): TEtalonData;
     {* Вспомогательная функция появившаяся оттого, что динамические массивы умеет к открытым приводиться автоматом, а обратно - нет }
 end;//_StackTest_
 
{$Else StackTest_imp}
 
procedure _StackTest_.DoIt;
var
 l_Etalons : TEtalonData;
 l_S : _StackType_;
 l_I : Integer;
begin
 l_S := _StackType_.Create;
 try
  l_Etalons := GetEtalonData;
  for l_I := Low(l_Etalons) to High(l_Etalons) do
   l_S.Push(l_Etalons[l_I]);
  for l_I := High(l_Etalons) downto Low(l_Etalons) do
   Check(l_S.Pop = l_Etalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;
 
function _StackTest_.ArrayToEtalon(const aData: array of _ItemType_): TEtalonData;
var
 l_I : Integer;
begin
 SetLength(Result, Length(aData));
 for l_I := Low(aData) to High(aData) do
  Result[l_I] := aData[l_I];
end;
 
{$EndIf StackTest_imp}

--------------------------------------
IntStackTestViaMixIn.pas:

unit IntStackTestViaMixIn;
 
interface
 
uses
  IntStack,
  TestFrameWork
  ;
 
type
 _StackType_ = TIntStack;
 {$Include StackTest.imp.pas}
 TIntStackTestViaMixIn = class(_StackTest_)
 protected
 // realized methods
   function GetEtalonData: TEtalonData; override;
 end;//TIntStackTestViaMixIn
 
implementation
 
uses
  SysUtils
  ;
 
{$Include StackTest.imp.pas}
 
function TIntStackTestViaMixIn.GetEtalonData: TEtalonData;
begin
 Result := ArrayToEtalon([10, 20, 3, 5, 6, 19, 21]);
end;
 
initialization
 TestFramework.RegisterTest(TIntStackTestViaMixIn.Suite);
 
end.


--------------------------------------
StringStackTestViaMixIn.pas:

unit StringStackTestViaMixIn;
 
interface
 
uses
  StringStack,
  TestFrameWork
  ;
 
type
 _StackType_ = TStringStack;
 {$Include StackTest.imp.pas}
 TStringStackTestViaMixIn = class(_StackTest_)
 protected
 // realized methods
   function GetEtalonData: TEtalonData; override;
 end;//TStringStackTestViaMixIn
 
implementation
 
uses
  SysUtils
  ;
 
{$Include StackTest.imp.pas}
 
function TStringStackTestViaMixIn.GetEtalonData: TEtalonData;
begin
 Result := ArrayToEtalon(['мама', 'мыла', 'раму', 'весело', 'и', 'споро']);
end;
 
initialization
 TestFramework.RegisterTest(TStringStackTestViaMixIn.Suite);
 
end.

---------------------------------
Получаем тесты:

По-моему - здорово :-)
Попробуйте. Может быть вам понравится.

Понятно, что то же самое можно проделать и с родными Generic'ами.

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

Немного об использовании DUnit

Тут мы говорили о шаблонах и примесях:
http://18delphi.blogspot.com/2013/03/blog-post_29.html

Теперь я расскажу о простейшем использовании фреймворка DUnit.

ЗАЧЕМ нужны тесты я немного написал вот тут - http://18delphi.blogspot.com/2013/03/blog-post.html

Думаю, что со временем приведу примеры "из жизни".

Пока рассмотрим лишь технику на абстрактном примере.

Буду описаться на классы полученные в предыдущем примере.

Как всегда начнём с диаграммы:
Теперь, что получаем на Delphi:

SandBox.dpr:

program SandBoxTest;
 
uses
  TestFrameWork
  GUITestRunner,
  IntStack,
  IntStackTest,
  StringStack,
  StringStackTest;
 
 
begin
 GUITestRunner.RunRegisteredTests;
end.

----------------------------------
IntStackTest.pas:

unit IntStackTest;
 
interface
 
uses
  TestFrameWork
 
  ;
 
type
 TIntStackTest = {final} class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TIntStackTest
 
implementation
 
uses
  IntStack,
  SysUtils
  ;
 
// start class TIntStackTest
 
procedure TIntStackTest.DoIt;
const
 cEtalons : array [0..3] of integer = (10, 20, 3, 5);
var
 l_S : TIntStack;
 l_I : Integer;
begin
 l_S := TIntStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TIntStackTest.DoIt
 
initialization
 TestFramework.RegisterTest(TIntStackTest.Suite);
 
end.


----------------------------------
StringStackTest.pas:

unit StringStackTest;
 
interface
 
uses
  TestFrameWork
  ;
 
type
 TStringStackTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TStringStackTest
 
implementation
 
uses
  StringStack,
  SysUtils
  ;
 
procedure TStringStackTest.DoIt;
const
 cEtalons : array [0..3] of String = ('мыма', 'мыла', 'раму', 'весело');
var
 l_S : TStringStack;
 l_I : Integer;
begin
 l_S := TStringStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TStringStackTest.DoIt
 
 
initialization
 TestFramework.RegisterTest(TStringStackTest.Suite);
 
end.


-- получили тесты работоспособности наших классов :-)

По-моему - здорово. Особенно если тесты КАЖДЫЙ день гонять :-) Если гоняем каждый день, то оперативно видим - чего сломали.

Не говорите только - "это ОЧЕНЬ просто" :-) Дьявол - он всегда в деталях.

Более сложные примеры "из жизни" - я постараюсь позже привести.

Главное, что для многих проектных классов - тесты это очень простой путь отладки - чтобы не поднимать полномасштабное приложение, которое компилируется 10 мин. Не разворачивать сервер БД и т.п. Придумали "данные из головы", написали тест - и всё - можно отлаживать. Данный конкретный класс.

Понятно, что ИМЕННО НА ЭТИХ данных. Но ничто не мешает - постепенно пополнять базу тестов и базу "данных из головы".

Позже я напишу - как я пользуюсь mock'ами. На основе файлов эталонов.

Что это такое пока читайте тут:

Если вы скажете - "покажите тестирование форм и GUI", то я отвечу вам - "покажу. Непременно. У меня - богатый опыт".

А пока - сосредоточьтесь на том факте, что формы как раз - "не страшно трогать". Ибо они "торчат наружу юзеру". А вот базовые классы - страшно. Особенно если их использует несколько десятков форм. Разных. Особенно если они написаны "тем парнем который пять лет назад уволился". Особенно если нет тестов. И чем более "базовый" класс. И чем больше в нём найдено ошибок, тем больше для него будет написано тестов. Со временем. Если вы будете прислушиваться к моим рекомендациям. И тем более стабильным - он станет.

Попробуйте - может быть вам понравится.

Следующая серия тут:

среда, 27 марта 2013 г.

Надстройки над DUnit, которые были сделаны

1. Вывод результатов тестов в файлы и возможность сравнения их с эталонами.
2. Замеры времени. Вложенные. С оформлением результатов замеров в виде таблиц.
3. Возможность использовать не только Delphi-native-тесты, но и тесты на скриптах FORTH-машины.
4. Публикация результатов тестов во внешние источники - через CORBA и MTDOrb. Ну и просто на файловую систему конечно.
5. Механизм preset'ов - можно составлять наборы тестов.
6. Запуск тестов в случайном порядке. С отслеживанием RandomSeed.
7. Запись списков в файлы - какие тесты прошли, а какие - нет.
8. Доработка дерева тестов. Типа включить все тесты до данного. Найти тест по контексту. Инвертировать выделение и т.п.
9. Списки исключений для тестов.
10. Генерация тестов из Rational Rose (ну это понятно).
11. Размножение одного теста по параметризации данных. Пишем тест который перемывает HTML в RTF. Объявляем его размножаемым. Складываем ему под ноги 100 HTML'ей. Получаем 100 разных тестов. Такая идея. Без перекомпиляции конечно же.
12. Возможность регистрации и создания пользовательских форм. Для того, чтобы тестировать отдельные формы приложения.
13. InsiderTest'ы - возможность включения тестовой машинки в уже готовое приложение на VCL и VGScene (частично). С обвязкой для доступа к контролам и их формам. В итоге получаем не только Unit-тестирование, но и тестирование готовых приложений.

вторник, 26 марта 2013 г.

О TDD И "гигиене мозга" (C)

Что мы знаем о TDD?

http://ru.wikipedia.org/wiki/%D0%A0%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D1%87%D0%B5%D1%80%D0%B5%D0%B7_%D1%82%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5


Добавление теста

Запуск всех тестов: убедиться, что новые тесты не проходят

Написать код

Запуск всех тестов: убедиться, что все тесты проходят

Рефакторинг

Повторить цикл

Нам важны все эти этапы? Стоит ли рассматривать TDD как "серебряную пулю"? Усложняет ли или упрощает TDD разработку?

Первое, что я хочу написать - "серебряной пули - НЕТ". НЕТ и не будет.

Так же как и "мытья рук перед едой". Вы всегда моете руки? А в походе? А на скальном перевале, когда вниз десять верёвок и вверх - "непонятно сколько".. А вода - "была вчера".. И дальше когда будет - непонятно...

Так и с TDD, RUP, "водопадом", шаблонами от GoF.. Прочими "крутыми практиками"..

ВСЕ они конечно хороши. Но! Мозги включать надо - ВСЕГДА. Если если "нет воды", то глупо - "мыть руки".

Test First! Ага! Здорово!

ВСЕГДА Test First!

А если мы не знаем - что мы вообще делаем и что из этого получится - тоже Test First?

Мой ответ - "КОНЕЧНО нет".

Давайте попробуем ввяжемся в драку, а там посмотрим - что получится. Может и тесты. А может и ещё, что поинтереснее.

Когда ТОЧНО нужны тесты?

Моё мнение - если "сторонняя" Группа Качества (или не дай Бог - ПОЛЬЗОВАТЕЛИ) находит ОЧЕРЕДНУЮ ошибку, которой нет в Базе Ошибок, то тут уж точно - ТЕСТ НЕОБХОДИМ.

Но это - не Test First, это Test After. Сделали. Выпустили. Нашли ошибку - будьте любезны - ПЕРЕД исправлением ОШИБКИ - написать тест, если он конечно не КОСМИЧЕСКИ СЛОЖЕН (я мало таких видел).

Написали тест - исправляйте ошибку с лёгким сердцем. В следующий раз - вы её (а она - появится и не раз, не будьте оптимистами) - СРАЗУ найдёте.

Исправили ПЯТЬ ошибок - у вас УЖЕ есть БАЗА из пяти тестов. Просто, не правда ли?

Когда Test First?

А вот когда:

Вы ЗНАЕТЕ, что вам делать, но не договорились со "смежниками" откуда какие данные будут поступать и куда уходить.

А начальство включило вам задачу в план. И делать надо СЕГОДНЯ.

Вот тут - Test First.

Делаем тест. Придумываем вход и отправляем выход "куда-то". Желательно в эталонные файлы теста, которые потом будут сравниваться.

И спокойно пишем свой код. И отлаживаем его. Наблюдая за тестированием преобразования "вход-выход". Не более того.

Но! Вы можете придумать несколько входов и получить несколько выходов - на то вы и хороший разработчик. И в итоге получаете ОТЛАЖЕННЫЙ код. На детерминированном входе и детерминированном выходе.

И не зависите от смежников.

И получаете "бесплатно" ещё и "комплект тестов", который конечно не стоит выкидывать. А стоит включить в базу тестов. Они потом вам ещё пригодятся. Особенно когда смежники - раскочегарятся.

Если вы "действительно хороший разработчик" - добавляете ещё несколько тестов на граничные условия. Test First.

И ВСЁ - СВОЮ работу - вы СДЕЛАЛИ. Дальше - дело за смежниками.

Сам регулярно так делаю. И счастлив.

Когда ТОЧНО надо писать тест?

Моё мнение - тест ТОЧНО надо писать, когда мы попадаем в "программистские качели". Пока читаем тут:
http://18delphi.blogspot.com/2013/03/blog-post.html

... to be continued ...

Вместо приветствия...


18-ть лет программирую на Delphi. Знаю кроме всего прочего C, C++, Objective-C, asm PDP-11, asm-Intel86, FORTH и несколько "птичьих языков". Хочу делится тут ТЕХНИЧЕСКИМИ подробностями своей работы программистом. И своими мыслями по программированию/архитектуре.

Кроме программирования сильно интересуюсь TDD и UML.

Про TDD - регулярно слышу - "это нужно только Шуре" :-) и я к этому - "почти привык".

Действительно - тестирование - В ПЕРВУЮ ОЧЕРЕДЬ нужно - программистам. ХОРОШИМ программистам.. Ну или наоборот - ПЛОХИМ.. Не гениям... Тем кто не может ПРЕДСКАЗАТЬ - как поведёт их код при внесении МАЛЕЙШИХ изменений.

Приведу один пример. Я долго и нудно и с удовольствием и с интересом программировал редактор Эверест. И на нём сделан КОММЕРЧЕСКИЙ продукт.

Так вот - в Эвересте одна из граней функциональности - печать и предварительный просмотр печати. И как это отлаживать?

Когда документы бывают по нескольку тысяч страниц. Печатать? Несколько ПАЧЕК бумаги?

Даже виртуальный принтер не помогает.

И я ОЧЕНЬ долго правил код "машины печати". И попадал в "программистские качели". Правишь тут - сломалось там.. правишь там - сломалось тут.. И так - БЕСКОНЕЧНО... Мозги готовы били вскипеть... Хотелось убить кого-нибудь рядом... Функционал - не сходился...

А ещё Группа Качества находила одну ошибку за другой.. Группа Качества была как Бич Божий!! Они находили ошибки БЫСТРЕЕ, чем я их исправлял...

Всё время находилась 150-я страница документа, или 300-я или 550-я.. Которая не печаталась.. Или на которой приложение тупо падало....


И тут я задумался о ТЕСТИРОВАНИИ... Было страшно и жутко... Хотелось ПОКРЫТЬ весь функционал тестами.. Но я не понимал как это сделать... Мысли разбегались как тараканы.. Я думал - "как же найти СУПЕР-тест - чтобы СРАЗУ покрыл ВСЁ"....

И хотелось пойти и убиться головой об стену....


Но тут нашёлся умный человек...

Александр Можаев... Он сказал - "есть DUnit - Unit-тесты для Delphi... ты наверное знаешь.. ничего космического.. но очень полезно..." Я - НЕ ЗНАЛ...

И решил попробовать....

Когда я взял DUnit и посмотрел "внутрь" - РАЗАЧАРОВАНИЕ настигло меня... Не то что "ничего космического..." а..... аа... "ВООБЩЕ - НИЧЕГО"... код простой и "ровный"...

"Я тоже так умею"...

Я спросил Можаева - "и чо?"...


Он мне сказал - "попробуй.. понравится..."...


Я пожал плечами и решил попробовать...

К тому моменту за моими плечами было несколько написанных фреймворков.. Да и вообще я думал, что "я уж знаю как всех научить программировать" :-)

(это был сарказм конечно)

Взял я DUnit И начал "крутить в руках"...

"ничего необычного".. "где же тут серебряная пуля?"

ну published-методы, которые "зачем-то" показываются в "каком-то дереве"... ну и что?

Но я решил - "раз люди советуют - значит оно того стоит"...


И начал крутить дальше... И написал СВОЙ ПЕРВЫЙ ТЕСТ (конечно не первый в жизни, но ПЕРВЫЙ под DUnit).. Который просто печатал текст... Точнее строил "предварительный просмотр печати".. Через виртуальную канву.

И тест прошёл :-) И я увидел "зелёную полоску".

Дальше я занялся "сферическим конём в вакууме".. Я стал пытаться запустить этот тест в автоматическом режиме.. И тут меня постигло ОЧЕРЕДНОЕ РАЗАЧАРОВАНИЕ...

Я то думал, что достаточно запустить "экзюк" (спасибо Всеволоду Леонову за это слово - мы раньше говорили - "экзешник") и указать "экзюку" ключ запуска - "запусти мне тест в автоматическом режиме"....


НЕ ТУТ ТО БЫЛО!!!

ОКАЗАЛОСЬ - "ключа не было" НИКАКОГО....

Я стал "курить исходники" (как говаривал ещё один мой коллега).

После вдумчивого "курения исходников" - выяснилось, что для того, чтобы тесты выполнялись  в автоматическом режиме - надо собрать ДРУГОЙ "экзюк" и в "консольной моде". Не GUITestRunner,  а ConsoleTestRunner!

"что за чёрт!" - сказал я себе - "эти авторы DUnit наверное издеваются надо мной"....

Надо МНОЙ, над человеком, который укротил "птичий язык" и Rational Rose (это тоже был сарказм). Они издеваются?!!!

Но потом я подумал.. потом подумал ещё (и мне понравилось). И я решил - а почему собственно - я не могу иметь в ОДНОМ приложении и GUITestRunner и ConsoleTestRunner. Оказалось, что - НЕ МОГУ. Но это были ЧИСТО ТЕХНИЧЕСКИЕ сложности. И я их решил.

Я скрестил "ежа с ужом".. То есть GUITestRunner и ConsoleTestRunner, которые "допилил напильником". И ConsoleTestRunner получил ещё и наследника - KTestRunner - я тогда уже думал о публикации результатов работы тестов в нашей "базе знаний" и bug-треккере.

И в итоге тесты (ОДИН тест) - заработали как в "ручном" режиме, так и в автоматическом (если задавался ключ командной строки - ItsLAW).

Дальше я вставил тесты в автоматическом режиме в автоматический запуск из Scheduler'а и был СЧАСТЛИВ.

Счастлив - потому, что мне удалось заставить "это" работать, а не потому, что я понимал - "зачем это мне".

Пока.. Пока ТЕСТ НЕ УПАЛ!!! :-) И тут я ПОНЯЛ!! Меня - БУКВАЛЬНО осенило!

Я правил "очередную нудную ошибку от ГК". Опять же про ПЕЧАТЬ...

ВЧЕРА поправил, ЗАКРЫЛ ошибку и счастливый ушёл домой СПАТЬ.

Когда я пришёл утром - УПАЛ МОЙ ПЕРВЫЙ ТЕСТ!!!

И тут меня будто током ударило!!! Вот ОНО!! Я теперь "Знаю - ЗАЧЕМ"!!!

Я решил разобраться - "а в чём собственно дело? что же я сломал вчера?"

И тут (о боги!!!) я сделал НАИМУДРЕЙШУЮ вещь.. Я НЕ СТАЛ ПРАВИТЬ код... Я сделал ВТОРОЙ ТЕСТ.. Да.. да.. Это ОЧЕНЬ ПРОСТО.. когда знаешь это.. но я тогда - НЕ ЗНАЛ...

Я сделал (повторю) ВТОРОЙ ТЕСТ.. по мотивам ошибки, которую я закрыл вчера, и которую ГК проверила и тоже радостно закрыла.

И ВТОРОЙ ТЕСТ - КОНЧЕНО же прошёл...

А ПЕРВЫЙ ТЕСТ - как не проходил, так и не прошёл...

Дальше я НЕДЕЛЮ возился с тем, что я сломал в ПЕРВОМ ТЕСТЕ (ГК это пока не нашла) и это не давало мне покоя...


Я запускал этот тест в ручном режиме и крутил всякие разные колёсики... Я же - ПЛОХОЙ программист - я не могу АНАЛИТИЧЕСКИ решить сложную задачу (я пробовал). Я лишь могу - "крутить колёсики"....

И в какой-то момент... в какой-то момент.. в какой-то момент... Я КРУТАНУЛ НУЖНОЕ колёсико... И ПЕРВЫЙ ТЕСТ - ОТРАБОТАЛ!!!

Я запустил его раз  и РАЗ.. и он - ОТРАБОТАЛ...

(напомню - я запускал тесты в "ручном" режиме  - не ВСЕ сразу, а ОДИН - первый).

Я покоммител код. Написал ошибку и закрыл её. И довольный УШЁЛ ДОМОЙ....

Когда я пришёл на работу на следующий день - очередное РАЗАЧАРОВАНИЕ ждало меня...

Упал ВТОРОЙ ТЕСТ :-(

И опять хотелось пойти и "убиться головой об стену"....

Но я взял себя в руки (это опять был сарказм) :-)

И прогнал тесты - снова и снова... И "номер первый" (на деревянной ноге) - проходил, а "номер второй" (с босыми ногами) - упорно не хотел проходить...

Моё самолюбие "гениального программиста" и "укротителя Rational Rose" было уязвлено этим "маленьким босоногим дикарём", и я решился взяться за него всерьёз...

ЦЕЛЫЙ день я искал причину его падения.. и не мог найти... В конце-концов часам к 12-ти ночи причина была найдена.. Она крылась в "маленькой запятой".. Но что это была за запятая!!! Серебряная пуля меркнет по сравнению с ней. Но она была найдена.

И НОМЕР ВТОРОЙ - прошёл!!!

Счастью моему не было предела.


Я покоммител код. И собрался домой... Вышел из комнаты.. Запер её... Пошёл на стоянку...


Но ЧЕРВЬ СОМНЕНИЯ глодал меня... я осознавал, что "я что-то не доделал"....

И я вернулся...

И прогнал ОБА ТЕСТА в автоматическом режиме...

НОМЕР ВТОРОЙ - отработал.. А НОМЕР ПЕРВЫЙ - упал :-(

Моё самолюбие было опять уязвлено..

Идти домой катастрофически расхотелось...

"Качели" подумал я..

"Эти чёртовы пограммистские качели"...

И я не ушёл....

Ошибка нашлась сама собой.. Я же находился "в потоке" и я - ЗНАЛ - что я правил и в НОМЕРЕ ПЕРВОМ и в НОМЕРЕ ВТОРОМ.. И я проанализировал изменения...

И "узорчик"!!! СОШЁЛСЯ...

Я внёс изменнения и прогнал НОМЕРА ПЕРВОГО.. И он - ПРОШЁЛ..

Тогда я прогнал и НОМЕРА ВТОРОГО.. и он.. ТОЖЕ ПРОШЁЛ...

Я счастливый отправился ДОМОЙ...

Теперь - я ЗНАЛ, ЧТО ДЕЛАТЬ и "зачем всё это нужно"...

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


"Регрессионное тестирование" - спросите вы...
"Конечно!!" - отвечу я...

Но! Плохой Гениальный программист и "укротитель Rational Rose" - НЕ ЗНАЛ этого, хотя и прочитал "миллион статей".. где "умные люди" - пытались донести "это" до "него"...

Но теперь - Я ТОЧНО ЗНАЮ ЧТО ДЕЛАТЬ...

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

Теперь у нас порядка 2000 "регрессионных" тестов.. которые гоняются КАЖДУЮ ночь.. и КАЖДЫЙ день на РАЗНЫХ машинах.. немногих.. их по пальцам пересчитать.. и мы ЗНАЕМ ЧТО ДЕЛАТЬ.. главное, что мы можем - ОПЕРАТИВНО реагировать.. находясь "в потоке".. пока "вчерашние" изменения в памяти и на слуху.. и "узорчик" - чаще - складывается, чем не складывается...

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

Ну а тестов печати у нас - всего штук 100 (оценка сверху) и СУПЕР-тест, который "покрывает всё" - нам не понадобился.. И спим мы спокойно :-)

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

P.S. в следующий раз, если не остыну, я расскажу вам о знакомстве с VGScene (ака FireMonkey) И как я её ругал.. и как тупил.. и не мог проникнуть в "слоёный пирог" стилей.. и как Антон (сам того может быть не зная) - навёл меня на понимание гениальности архитектуры данной библиотеки...

P.P.S. Также хочется рассказать - как мы локализовывали приложения под Delphi и что мы из этого вынесли.

P.P.P.S Ещё хочется рассказать как я "на коленке" делал FORTH-машину для тестирования приложений под DUnit. Как Дима писал к этому обвязку и что из этого получилось.

P.P.P.P.S Интересно также рассказать про "программирование на UML" vs. "прототипирование на UML" и метафоре "номенклатуры микросхем" vs. "собирание логики на триггерах".

P.P.P.P.P.S Ну и конечно хочется рассказать "о деле всей жизни" - редакторе Эверест. Технические подробности и решения. Которые родились в процессе этой разработки.

Ну и команда, которая делала "ту самую версию":