Предыдущая серия была тут - http://programmingmindstream.blogspot.ru/2014/02/2.html
Все исходники доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter3/
В предыдущей серии мы добавили тест СЛОЖЕНИЯ.
Теперь добавим ещё три теста - ВЫЧИТАНИЯ, УМНОЖЕНИЯ и ДЕЛЕНИЯ.
Для начала немного переработаем наш TPlusTest.
Выделим ещё один АБСТРАКТНЫЙ тест - TOperationTest.
Вот он:
А TPlusTest теперь принимает такой вид:
Теперь добавляем TMinusTest, TMulTest и TDivTest:
И вот что получаем:
Что мы в итоге имеем?
Мы покрыли ВСЕ операции нашего калькулятора.
Так сказать "обеспечили минимальное тестовое покрытие" всех прецедентов использования нашего приложения.
Тут можно поговорить о том, что надо тестировать "разные наборы данных".
Или о тестировании "случайных наборов".
А также можно поговорить о тестировании граничных условий. Например деления на ноль.
Также можно говорить о тестировании валидации введённых данных.
Мы об этом поговорим в последующих постах.
Также мы поговорим об "изменении архитектуры". Мы уже почти вплотную подобрались к этому.
Поговорим. Но в следующих постах.
Пока возьму тайм-аут. А вы поглядите на то, что я вам предоставил.
Ну и почитайте вот что, если ещё не читали - http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html
P.S. Попросили тут нарисовать UML-ДИАГРАММУ классов для данного приложения и его тестов. Это ИНТЕРЕСНО? НУЖНО? И можно ли обойтись ОДНОЙ лишь диаграммой классов? Или нужна ещё и sequence-диаграмма?
Все исходники доступны тут - https://sourceforge.net/p/rumtmarc/code-0/HEAD/tree/trunk/Blogger/DraftsAndScketches/SomeTestProjects/DummyCalculator/Chapter3/
В предыдущей серии мы добавили тест СЛОЖЕНИЯ.
Теперь добавим ещё три теста - ВЫЧИТАНИЯ, УМНОЖЕНИЯ и ДЕЛЕНИЯ.
Для начала немного переработаем наш TPlusTest.
Выделим ещё один АБСТРАКТНЫЙ тест - TOperationTest.
Вот он:
unit OperationTest; interface uses CalculatorGUITest, MainForm ; type TOperation = (opAdd, opMinus, opMul, opDiv); TOperationTest = class(TCalculatorGUITest) protected procedure VisitForm(aForm: TfmMain); override; function GetOp: TOperation; virtual; abstract; end;//TOperationTest implementation uses TestFrameWork, SysUtils ; procedure TOperationTest.VisitForm(aForm: TfmMain); const aA = 10; aB = 20; begin aForm.Edit1.Text := IntToStr(aA); aForm.Edit2.Text := IntToStr(aB); case GetOp of opAdd: begin aForm.Button1.Click; Check(StrToFloat(aForm.Edit3.Text) = aA + aB); end; opMinus: begin aForm.Button2.Click; Check(StrToFloat(aForm.Edit3.Text) = aA - aB); end; opMul: begin aForm.Button3.Click; Check(StrToFloat(aForm.Edit3.Text) = aA * aB); end; opDiv: begin aForm.Button4.Click; Check(StrToFloat(aForm.Edit3.Text) = aA / aB); end; end;//case GetOp end; end.
А TPlusTest теперь принимает такой вид:
unit PlusTest; interface uses OperationTest ; type TPlusTest = class(TOperationTest) protected function GetOp: TOperation; override; end;//TPlusTest implementation uses TestFrameWork, SysUtils ; function TPlusTest.GetOp: TOperation; begin Result := opAdd; end; initialization TestFramework.RegisterTest(TPlusTest.Suite); end.
Теперь добавляем TMinusTest, TMulTest и TDivTest:
unit MinusTest; interface uses OperationTest ; type TMinusTest = class(TOperationTest) protected function GetOp: TOperation; override; end;//TMinusTest implementation uses TestFrameWork, SysUtils ; function TMinusTest.GetOp: TOperation; begin Result := opMinus; end; initialization TestFramework.RegisterTest(TMinusTest.Suite); end.
unit MulTest; interface uses OperationTest ; type TMulTest = class(TOperationTest) protected function GetOp: TOperation; override; end;//TMulTest implementation uses TestFrameWork, SysUtils ; function TMulTest.GetOp: TOperation; begin Result := opMul; end; initialization TestFramework.RegisterTest(TMulTest.Suite); end.
unit DivTest; interface uses OperationTest ; type TDivTest = class(TOperationTest) protected function GetOp: TOperation; override; end;//TDivTest implementation uses TestFrameWork, SysUtils ; function TDivTest.GetOp: TOperation; begin Result := opDiv; end; initialization TestFramework.RegisterTest(TDivTest.Suite); end.
И вот что получаем:
Что мы в итоге имеем?
Мы покрыли ВСЕ операции нашего калькулятора.
Так сказать "обеспечили минимальное тестовое покрытие" всех прецедентов использования нашего приложения.
Тут можно поговорить о том, что надо тестировать "разные наборы данных".
Или о тестировании "случайных наборов".
А также можно поговорить о тестировании граничных условий. Например деления на ноль.
Также можно говорить о тестировании валидации введённых данных.
Мы об этом поговорим в последующих постах.
Также мы поговорим об "изменении архитектуры". Мы уже почти вплотную подобрались к этому.
Поговорим. Но в следующих постах.
Пока возьму тайм-аут. А вы поглядите на то, что я вам предоставил.
Ну и почитайте вот что, если ещё не читали - http://programmingmindstream.blogspot.ru/2014/02/blog-post_4473.html
P.S. Попросили тут нарисовать UML-ДИАГРАММУ классов для данного приложения и его тестов. Это ИНТЕРЕСНО? НУЖНО? И можно ли обойтись ОДНОЙ лишь диаграммой классов? Или нужна ещё и sequence-диаграмма?
Мне кажется, с наследованием ты переборщил. И кода больше, и сложнее. Ради такого примера точно не стоит.
ОтветитьУдалитьИМХО
Думаешь? Ну может быть. Я подумаю.
УдалитьПросто мне лично так - оказалось проще написать.
Ну плюс - "мысли забежали несколько вперёд". Я думал о параметризации тестов. Может быть ЗРЯ. На ДАННОМ этапе.
УдалитьДа и привык я как-то "нарезать классы тонкими ломтями". Каждому по "маленькому кусочку ответственности".
УдалитьИ ещё. В работе над тестами я лично предпочитаю из КОНКРЕТНЫХ тестов выделять АБСТРАКТНЫЕ (параметризуемые), чтобы из них потом сделать НОВЫЕ конкретные. И так по кругу. Так "мышечная масса" тестов очень хорошо нарастает. Опять же - может быть тут это и ПРЕЖДЕВРЕМЕННО. Но есть уже просто "наработанные стереотипы" мышления.
УдалитьНу дело твое. Тут все абсолютно правильно, технически упрекать не в чем. Просто мне казалось, что цель данного конкретного примера - показать простой путь. Ну чтобы неофиты могли понять суть, не отвлекаясь на технически изощренные вещи.
УдалитьНу для меня лично наследование (как впрочем и примеси) - это чисто "техническая вещь", а не концептуальная. Мне что один класс, что десять - всё едино. Я на классы смотрю лишь как на "зоны ответственности". Надеюсь, что наследование "неофитам" мозг не порвёт.
УдалитьИзвините не понял, где мы TOperationTest поднимаем, и MainForm собственно ?
ОтветитьУдалитьФорма и тесты поднимаются в dpr на уровне запуска программы. Хотелось бы отделения формы от приложения. :)
ОтветитьУдалить"Форма и тесты поднимаются в dpr на уровне запуска программы. Хотелось бы отделения формы от приложения. :)"
Удалить-- я эту проблему решаю ОТДЕЛЬНЫМ приложением ТЕСТОВЫМ, которое КОПИРУЕТ ВЕСЬ функционал "настоящего" и генерируется из UML. "Обычным" же людям, у которых нет кодогенерации для начала могу порекомендовать использование IfDef.
Я предвидел этот вопрос, но забыл...
насколько я помню форму mdiChild нельзя сделать без главного окна, и стает вопрос как это сделать для тестирования ?
ОтветитьУдалить"насколько я помню форму mdiChild нельзя сделать без главного окна, и стает вопрос как это сделать для тестирования ?"
УдалитьЭто в копилку вот к чему - http://programmingmindstream.blogspot.ru/2014/02/2.html?showComment=1393364099651#c6835117592870444331
Если форма создана ЗАКОННЫМИ СРЕДСТВАМИ приложения, то она - ТЕСТИРУЕМА.
Если хочется что-то "эмулировать" - то можно "помудрить". Например не ставить MDIChild в Design-time, а ставить его в Run-time.
А можно и не "мудрить", а сделать Factory Mathod или Dependency Injection.
У меня для Вас плохие новости - в этом наследовании нет никакой абстракции.
ОтветитьУдалитьНе удивлюсь, если я не знаю чего-либо, что делает этот код "хорошим", но пока для меня это выглядит как "очень плохой" код ._.
Son of a gun
УдалитьКак бы Вы написали это код "хорошо" ?
>>У меня для Вас плохие новости - в этом наследовании нет никакой абстракции.
УдалитьСвязывание "абстракции" неспосредственно с наследованием есть результат зауженного книжками мышления. Если учиться по книжкам, то всегда будешь чуть хуже автора книжек. В какой-то момент нужно чуть расширить сознание. Кстати, даже такой маститый гуру как Страуструп говорит, что "я придумал С++ так, как я его видел... но вы можете/должны применять его так, как вам удобно для решения ваших задач".
Например: наследование есть ПРОСТО МЕХАНИЗМ РАЗДЕЛЕНИЯ КОДА. И не обязательно в контексте "абстрактное-конкретное". Все-таки "абстрактное-конкретное" отличается от "общее-частное".
Молоток - общее, молоток с гвоздодёром - частное. А есть еще и с обрезиненной ручкой. Все три объекта (класса) весьма конкретны, но наследование здесь бесспорно.
Давайте посмотрим на пример Александра. Идёт "операционное разделение" методов по категориям классов. Здесь слово "класс" (наконец) начинает означать то, что и должно. Элемент классификации.
И классификация может быть многопараметрической, точнее может/должно быть МНОГО классификаций. Александр применил "наследование" в чистом виде как средство разделения (структуризации) кода.
Опять же - интефейсы (как другой пример). Они - не абстрактны! Они - функционально конкретны. Что может быть функционально-конкретнее IUnknown? Но никто не протестует против "втискивание" интерфейсов в единый класс-реализацию. Согласитесь, здесь вообще нет "абстрактное-конкретное". Есть конкретный функциональный набор, разбросанный по разным I-интефейсам (чистым классам в С++), который конкретно в-реализуется в уже реальном классе (не абстрактном в плане без чисто виртуальных функций).
И вот эта "свёртка" интерфейсов в реализационный класс и есть у Александра, только наоборот :) Т.е. правильно - сверху вниз, когда общий (но НЕ абстрактный) функционал распадается на конкретные функциональные конгломераты (объединенные общим предком). Это просто способ построить функционально-тестирующую модель в иерархическом виде.
Вот и сказочке конец, кто не понял - F1.
> Связывание "абстракции" неспосредственно с наследованием есть результат зауженного книжками мышления. Если учиться по книжкам, то всегда будешь чуть хуже автора книжек. В какой-то момент нужно чуть расширить сознание.
ОтветитьУдалитьКо мне это не относится, я не учу по книгам. Когда я говорил об абстракции, я говорил об этом: "АБСТРАКТНЫЙ тест - TOperationTest.".
>Давайте посмотрим на пример Александра. Идёт "операционное разделение" методов по категориям классов. Здесь слово "класс" (наконец) начинает означать то, что и должно. Элемент классификации.
Нет здесь никакой "классификации", только странная, неочивидная архитектура. Запросто можно было написать проще и гибче. По этому я и называю её "плохой".
>Опять же - интефейсы (как другой пример). Они - не абстрактны!
Интерфейс это механизм абстракции, не надо пустословить.
>чистым классам в С++
Не "чистым", а "чисто виртуальным" классам. Хотя обычно их называют "абстрактные классы", что как-бы намекает. =)
>Как бы Вы написали это код "хорошо" ?
Написал бы функцию, принимающую функцию от двух чисел и кнопку, в качестве своих параметров. Затем просто применил бы её для соответствующего набора кнопок и операторов.
Пример на хаскеле: http://pastebin.com/RwkA2CTA
"Написал бы функцию, принимающую функцию от двух чисел и кнопку, в качестве своих параметров. Затем просто применил бы её для соответствующего набора кнопок и операторов."
Удалить-- вот тут - согласен.
Одно только но. Которое я писал Роману. Я "думал вперёд" (что может быть и ПРЕЖДЕВРЕМЕННО) - об избавлении от нажатия на кнопки.
УдалитьПравда ОТДЕЛЬНОСТОЯЩАЯ функция - ничуть НЕ ЛУЧШЕ, функции класса. ОБА подхода - равноценны. Ничего "ужасного" ни в том, ни в другом - не вижу.
УдалитьЯ кстати отдельным постом пожалуй напишу как преобразовать этот тест в несколько иной, с использованием:
УдалитьButtons : array [TOperation] of TButton = (Button1, Button2, Button3, Button4);
Ops : array [TOperation] of reference to function (a, b: double) : double = (function (a, b : bouble) : double begin result := a + b end, function (a, b : bouble) : double begin result := a - b end, function (a, b : bouble) : double begin result := a * b end, function (a, b : bouble) : double begin result := a / b end);
>>Интерфейс это механизм абстракции
УдалитьИнтерфейс - это механизм. Абстракция есть некая идея, которую можно поддержать "виртуальными" и "чисто виртуальными" методами, что есть "интерфейсы" в отдельной ООП концепции. Но называть "розетку" (=интерфейс) или "разъем USB" абстракцией... Однозначно сказывается "книжное" воспитание и нежелание мыслить чуть шире, чем забито в справочной системе по конкретным тулзам.
>>не надо пустословить.
Классический индикатор зауженного и формального сознания. Делаю так, как учили в школе.
>>Не "чистым", а "чисто виртуальным" классам.
Слово "чистые" было взято в кавычки. Если уж следовать книжным традициям, то кавычки означают отход от жёсткой трактовки. Слабая попытка поймать на терминах, коллега.
>>Хотя обычно их называют "абстрактные классы", что как-бы намекает. =)
А Вы помните, почему их так называют? Только с точки зрения компилятора (и то не любого). Проблемы инстанцирования. Где-то warning с abstraction error, где-то compilation error. Никто ни на что не намекает, есть конретные проблемы компиляции и времени исполнения в случае инстанцирования "абстрактных" классов.
Ещё раз - есть "абстракция" как способ избежания дублирования (поддержанная наследованием и полиморфизмом).
Есть "абстракция" как классификационный признак при работе компилятора.
Есть "абстракция" как метод умозрительной трактовки сущностей.
Редкая книга пишет сразу об этом. Чаще всего, книжка описывает лишь один из трёх пунктов.
>>Написал бы функцию
Частица "бы" очень многое объясняет при трактовке мотивационной составляющей Ваших комментариев.
>>и кнопку, в качестве своих параметров.
УдалитьА какую? FMX.StdCtrls.TButton или VCL.StdCtrls.TButton?
Или абстрактную кнопку? :)
>Интерфейс - это механизм
УдалитьЯ тоже самое написал, читайте внимательнее. Дальнейшие рассуждения не имеют смысла.
>Классический индикатор зауженного и формального сознания. Делаю так, как учили в школе.
На хабре Вас не научили, что все эти Ваши игры в психологов, задорновщину и прочую угадайку, не работают? Ни на йоту же не попадаете. =)
>Слабая попытка поймать на терминах, коллега.
На самом деле не было никакой попытки, просто поправил. Спокойно.
>А Вы помните, почему их так называют? Только с точки зрения компилятора (и то не любого). Проблемы инстанцирования
Вы путаете причину и следствие. Абстрактные классы так называют прежде всего потому, что они призваны обеспечить слой абстракции.
>Или абстрактную кнопку? :)
Можно просто IO (), если хотите. =)
Ввод (Fractional a, Num a) => a -> IO (), вывод (Fractional a, Num a) => IO a.
Ещё глупые вопросы будут?
>Ввод (Fractional a, Num a) => a -> IO (), вывод (Fractional a, Num a) => IO a.
УдалитьМожно просто a -> IO () и IO a. Так даже абстрактнее. =)
Мне понравилось. Продолжайте, пожалуйста.
ОтветитьУдалить:-) продолжу :-) обязательно продолжу
ОтветитьУдалить