среда, 25 ноября 2009 г.

Суррогатные объекты (Mock Objects)

Статья незакончена! Опубликована как предварительный материал к лекции по Mock-объектам.


Вольный пересказ статьи Mocks Aren't Stubs by Martin Fowler
См. также XUnit Test Patterns by Gerard Meszaros

Пример


Для иллюстрации будем использовать следующий пример: выполнение заказа со склада. Заказ (Order) включает всего один товар (для простоты товары будут кодироваться символами типа #article1) с указанием количества (amount). Склад (warehouse) знает про наличие товаров (inventory). Когда мы просим заказ "выполниться", возможны два сценария: (1) если на складе имеется достаточное количество указанного товара, то заказ помечается как выполненный (filled) и со склада списывается соответствующее количество этого товара; (2) если нужного количества нет, заказ остается невыполненным, а со складом ничего не происходит.

Обычные тесты

OrderStateTests >>
setUp
warehouse := Warehouse new.
warehouse
add: 50 of: #article1.

testOrderIsFilledIfEnoughInWarehouse
| order |
order := Order on: 50 of: #article1.
order fillBy: warehouse.
self assert: order isFilled.
self assert: (warehouse inventoryOf: #article1) isZero.

testOrderDoesNotRemoveIfNotEnough
| order |
order := Order on: 51 of: #article1.
order fillBy: warehouse.
self deny: order isFilled.
self assert: (warehouse inventoryOf: #article1) = 50.

Тесты в xUnit (SUnit в данном случае) обычно, как и в данном случае, можно разбить на четыре стадии: установка, выполнение, проверка, демонтаж.
Установка в данном примере выполняется в методе setUp, а также в самих тестах --- созданием заказа. Выполнение состоит в передаче сообщения #fillBy: созданном заказу. С помощью assert-ов выполняется проверка. А стадия демонтажа явно не представлена (на самом деле, она выполняется "за кадром" сборщиком мусора).

На стадии установки мы конфигурируем два объекта: склад (warehouse) и заказ (order). Можно заметить, что с точки зрения теста эти объекты не равнозначны.

Один из них --- заказ --- собственно и подвергается тестированию (точнее, один из аспектов его функциональности --- метод выполнения). Такие --- центральные для теста --- объекты будем называть тестируемой системой. А для краткости будем использовать английскую аббревиатуру SUT (от System Under Test).

Другой объект --- склад --- нужен в первую очередь для того, чтобы выполнение заказа могло правильно отработать в данном тесте. Назовем такие объекты (их может быть несколько) сотрудниками (collaborators в англоязычной литературе). В данном случае мы используем сотрудника еще и на стадии проверки для того, чтобы проконтролировать результат выполнения заказа (произошло ли списание).

Что в этих тестах не так?


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

Еще одно замечание: класс Warehouse должен существовать и правильно работать (по крайней мере, в отношении хранения остатков по продуктам и их списания), для того, чтобы тест работал. То есть, тест может сломаться не только потому, что неправильно реализована тестируемая система, но и из-за ошибок в сотрудниках. Как следствие, данный тест не может точно показать, где именно кроется ошибка. И это не единственный недостаток зависимости тестов от используемых в них сотрудников. Но другой --- весьма немаловажный, надо заметить, --- аспект рассмотрим чуть позже...

Тесты с использованием суррогатных объектов



С использованием SMock то же поведение можно описать следующим образом. Рассмотрим сначала "неудачное" списание.

OrderInteractionTests >>
testFillingDoesNotRemoveIfNotEnoughInStock
| order warehouse mock |
order := Order on: 101 of: #article1.
mock := SMock.Mock new.
(mock expect: #has:of:)
with: 101;
with: #article1;
returns: false.
warehouse := mock proxy.
order fillIn: warehouse.
mock verify.
self deny: order isFilled

Теперь вместо настоящего склада мы используем "заместителя" (proxy), полученного из суррогатного объекта ("мок-объекта" или просто "мока" в дальнейшем). Моки позвволяют проверить взаимодействие тестируемой системы с сотрудниками. Для этого сначала моку указывается, какие сообщения он должен будет получить --- описываются ожидания (expectations). А после использования мока (через его представителя) в реальных вычислениях, можно проверить, были ли эти ожидания выполнены.

Ожидания добавляются в мок через посылку сообщения #expect:. В параметре передается селектор сообщения. В ответ мок возвращает специальный объект, представляющий информацию об ожидаемом сообщении. Ему можно указать, например, значения параметров (с помощью последовательных сообщений #with:), возвращаемый результат (#returns:) и др.

В рассмотренном примере: mock ожидает сообщение #has:of: с первым параметром равным 101, и вторым параметром равным #article. В ответ на получение этого сообщения мок должен вернуть значение false.

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

В ответ на сообщение #verify мок проверяет, были ли выполнены все ожидания. Если это не так, то генерируется исключительная ситуация, описывающая несоответствие.

Рассмотрим второй тест:

OrderInteractionTests >>
testFillingRemovesInventoryIfInStock
| order warehouse mock |
order := Order on: 100 of: #article1.
mock := SMock.Mock new.
(mock expect: #has:of:)
with: 100;
with: #article1;
returns: true.
(mock expect: #remove:of:)
with: 100;
with: #article1.
warehouse := mock proxy.
order fillIn: warehouse.
mock verify.
self assert: order isFilled

Здесь мок должен получить два сообщения: #has:of: и #remove:of:. В обоих случаях ожидаются два аргумента: 100 и #article, соответственно. На #has:of: мок должен ответить true.

среда, 28 октября 2009 г.

Метафора для Agile

Еще одна метафора, с помощью которой можно "на пальцах" объяснить суть Agile методологий. "Классический" подход похож на стрельбу: нужно уметь хорошо прицеливаться. Для этого нужно понимать механику полета снаряда, силы на него действующие, уметь брать всякие поправки (на ветер, к примеру) --- в общем, вычислять траекторию, чтобы обеспечить его попадание в нужную точку. При этом еще нужно учитывать, что цель тоже движется --- брать упреждение. Или уметь это делать интуитивно. Или, скорее, владеть некоторым искусством, представляющим собой синтез расчета и интуиции. А что такое Agile методологии? Это просто управляемый снаряд. Если обеспечить своевременное поступление информации о том, куда в данный момент снаряд летит, иметь возможность быстро на это реагировать и нужным образом воздействовать на направление его движения, то попасть в цель гораздо проще. Отсюда очевидны три основных ценности (задачи): готовность к изменениям, управляемость разрабатываемой программной системы, обратная связь с пользователем. Test-Driven Development, в частности, говорит о том, как обеспечить решение этих трех задач "на уровне программиста".

вторник, 13 октября 2009 г.

Задачка

Вывести в Transcript матрицу вида:

123456789
012345678
009123456
000789123
000045678
000009123
000000456
000000078
000000009

Реализовать это, разумеется, в цикле.

четверг, 8 октября 2009 г.

Store + SVN

Взаимодействие Smalltalk-овских систем управления кодом/версиями (в частности Store в VW) с традиционными файловыми (в частности, SVN)

вторник, 29 сентября 2009 г.

Test-Driven Development --- некоторые тезисы

0. "Предпосылка": программирование в стиле "сначала использование, потом реализация" в Smalltalk.
1. TDD = Test-First + Test Automation + Refactoring.
2.  По сути, TDD является научным открытием: написание тестов и анализ (+ частично проектирование) --- очень близкие виды деятельности.
3. TDD --- agile-методология для разработчиков (кстати, до сих пор единственная мне известная).
4. ... А "менеджерские" методологии не будут работать, если разработчики не будут agile-ными.
5. Итерации внутри итераций.
6. (Неформальная постановка задачи->) Создание теста (Формальная постановка задачи) -> Реализация -> Рефакторинг
7. В Smalltalk-е TDD в значительной степени реализуется как "программирование в отладчике"

понедельник, 21 сентября 2009 г.

Xerox PARC --- опередившая время

(взято с The Spirit of PARC)
Изобретено в Xerox Palo Alto Research Center:
- лазерный принтер
- современный графический интерфейс пользователя
- первый текстовый редактор "что видишь, то и получаешь" :) (WYSIWYG)
- предок языка PostScript
- Ethernet
- объектно-ориентированное программирование
- интегрированная среда разработки (и выполнения!)

Два последний пункта --- это Smalltalk.


вторник, 15 сентября 2009 г.

Самое начало: только объекты --- а что такое объект?

Весь Smalltalk базируется на нескольких простых принципах. Сейчас рассмотрим пару, и один --- подробно.


1. Все в Smalltalk-е является объектом.

Подробнее этот принцип обсудим в другой раз. Сейчас займемся другим пунктом... Ведь это первое утверждение, по сути, является "апофатическим": говорит о том, чего нет в Smalltalk-е --- "не-объектов". А вот что такое объект?


На самом деле, в этом вопросе содержится сразу два:
  1. Что такое объект "извне" --- как ими пользоваться?
  2. Что такое объект "изнутри" --- как он устроены и как создать "свой" объект?
(Объект как, соответственно, ноумен и феномен?)

Сейчас рассмотрим только первую часть.

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

2. Все вычисления выполняются через посылку сообщений.
Синтаксис Smalltalk отвечает этому принципу максимально близко к естественному языку. Для примера:
почтальон доставь: письмо по: адрес.
Примечание: Smalltalk, естественно, ориентирован на английский язык; в русской версии за счет наличия окончаний в различных падежах либо выглядит не столь "естественно" (лучше бы смотрелось "... по: адресу"), либо требует доработки компирятора (что, кстати, выглядит не очень сложной задачей). Но в любом случае далее будем использовать "нормальный" англоязычный синтаксис.

В обычном, англоязычном Smalltalk-е указанный пример будет выглядеть так:
postman deliver:  letter to: address.

Получили (почти) обычное предложение. Подлежащее в нем --- объект почтальон --- в терминах Smalltalk называется получателем (сообщения). Само сообщение включает в себя 
  • "имя" (в терминах Smalltalk --- селектор; почему селектор --- будет ясно позже, при рассмотрении внутреннего устройства объектов) --- в данном случае именем является доставь:по: --- (почти) сказуемое
  • аргументы (дополнения) --- объекты письмо и адрес
 
Продолжение следует...

понедельник, 14 сентября 2009 г.

Неделя 3: Коллекции

Обзор: Collection, Set, Bag, Array, OrderedCollection, Dictionary c примерами.
Смотрим (в Workspace), как работает очередной метод, разбираем его устройство (параллельно осваивая Debugger и Browser)...
За лекцию и практическое занятие можно успеть, не очень торопясь:
1. Collection: базовый класс
2. Set: добавление, удаление элементов; проверка вхождения; nil не работает; size
3. Bag: то же самое, но с количеством вхождений
4. Array: не изменяет размер; создание через #new:, #new:withAll; литерально; first
5. OrderedCollection: можно добавлять/удалять элементы
6. Печать элементов коллекции в Transcript: "С"-подобный вариант; через #do:
7. #select:, #collect:, #detect:, #detect:ifNone: --- с реализацией
8. #inject:into: --- принцип, пример использования (вычисление суммы); устройство и другие примеры --- на самостоятельное изучение

суббота, 12 сентября 2009 г.

Пример сайта на Seaside

Беззастенчиво пиарю сайт Дословно.Инфо. Сделан на Pharo Smalltalk + Seaside + Pier. Упоминается здесь и здесь.

четверг, 10 сентября 2009 г.

Web Development Using WebVelocity

... пока без комментариев

среда, 9 сентября 2009 г.

Классы и метаклассы --- часть 1

"Мета" = "о"
"Метакласс"= "класс о классе"


Smalltalk построен на нескольких базовых принципах. Два из них: (*) все является объектом и (**) каждый объект является экземпляром некоторого класса, описывающего поведение всех своих экземпляров.

Из этих принципов следует, что (*)=> каждый класс является объектом, и (**)=> является экземпляром некоторого класса --- назовем его метаклассом данного класса.

Рассмотрим некоторый класс C. Экземпляром какого класса он является?

Существует два способа ответить на этот вопрос: (А) все классы являются экземплярами некоторого единственного метакласса, и (Б) каждый класс (в общем случае) является экземпляром некоторого "своего собственного" метакласса.

Поскольку различные классы в Smalltalk обладают различным поведением (см. примечание 1), вариант (А) не подходит, т.к. поведение всех классов будет одинаковым --- оно описано в единственном метаклассе.

Таким образом, класс C является экземпляром своего некоторого собственного, "личного" метакласса.

Чтобы определить, экземпляром какого класса является некоторый объект, ему можно послать сообщение #class. Например:

(Point x: 1 y: 2) class "printIt -> Point"
Point class "printIt -> Point class"


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

Поскольку поведение у всех "личных" метаклассов одинаково, они имеют общий метакласс:

Point class class "printIt -> Metaclass"

Класс Metaclass имеет свое собственное поведение и, соответственно, собственный метакласс:

Metaclass class "printIt -> Metaclass class"

Метакласс класса Metaclass является обычным метаклассом и, соответственно, его метаклассом является Metaclass. "Метаклассом класса Metaclass class является класс Metaclass" :)

--- Примечание 1.
Например, класс Point должен иметь метод #x:y: для создания точки сзаданными координатами:

Point x: 1 y: 2.

Для большинства других классов (String, Integer, Boolean,...) этот метод смысла не имеет.
---

... продолжение следует...

вторник, 8 сентября 2009 г.

UI + Metadescriptions

Исследование возможностей использования мета-описаний для создания "универсального" UI Framework

Smalltalk + Java

Возможности использования Java-кода из Smalltalk (VW). JavaConnect и JNIPort

пятница, 3 апреля 2009 г.

Пример работы с mock-ами

Контекст


Сайт. Пользователь может задавать вопросы, заполняя и отправляя соответствующую форму. Вопросы представлены экземплярами класса Question. Когда вопрос изменяется (в результате редактирования пользователем) он получает сообщение #changed.

Задача


Когда изменяется вопрос, нужно отослать соответствующее оповещение администратору сайта.

Решение



(1) QuestionTests >> testNotifiesAdminOnChange
(2) | question |
(3) question := Question new.
(4) [ :notifier |
(5) question notifier: notifier.
(6) [ question changed ]
(7) should strictly satisfy:
(8) [ notifier notifyAdminAbout: question ]
(9) ] runScenario


Пояснение


В строках (4) -- (9) создается сценарий.
Параметры сценария (строка (4)) инициализируются mock-объектами.
В блоке (6) описываются действия, определяющие тестируемую ситуацию: question получает сообщение #changed.
В строке (7) задается условие: в результате этих (заданных выше в строке (6) действий должно происходить строго в заданной последовательности то, что записано в блоке строки (8).
В этом блоке записано, что объект notifier должен получить сообщение #notifyAdminAbout: с аргументом, идентичным question.

Обратите внимание! Не указывается, к какому классу будут принадлежать нотификаторы, с которыми будут работать вопросы.
Далее разработка будет включать шаги по созданию и описанию поведения такого класса.

Замечание


Также, возможно, потребуется рефакторинг по вынесению адресата оповещения в параметр и (в дальнейшем, возможно) изменению системы отсылки сообщения. На это указывает наличие указания на адресата (admin) в селекторе сообщения #notifyAdminAbout:.

пятница, 20 марта 2009 г.

Идея и некоторые тезисы курса по ГМ и Smalltalk

Логика примерно такая:

Гибкие методологии --- это хорошо. Поэтому их надо применять. Применять можно на разных уровнях. Базовый уровень --- программистский. Здесь работает Test-Driven Development.

Чтобы эффективно применять TDD, нужны соответствующие средства. Mainstream-овые языки на эту роль подходят не очень хорошо. С динамическими скриптовыми языками (которые тоже потихонечьку смываются в главный поток) дела, наверное, получше, но и у них многовато недостатков. В применении к ГМ на сегодняшний день Smalltalk --- (как минимум) одно из лучших решений. Поэтому будем его изучать.

Эти мысли излагаются на первых двух--трех лекциях. Далее "вперемжку" (!) небольшими кусками излагаются основы программирования на Smalltalk и Test-Driven Development.

Примечание 1. Про гибкие методологии



Гибкие методологии --- не очень адекватный перевод терамина Agile methodology.

Agile --- проворный, шустрый, сообразительный.
Под гибкостью же у нас издревле понимают некую универсальность, возможность приспособить (уже готовое) программную систему под любые нужды и сделать это без программирования, "играясь настройками". То есть, "гибкое" в данном понимании --- синоним сложного с целью универсальности.

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

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


Примечание 2. Про скриптовые динамические языки программирования



Тезис по поводу скриптовых языков требует проработки и обоснования. Однако отсутствие единой среды разработки и выполнения --- уже весьма существенный "минус" (по крайней мере для разработки более-менее крупных систем).


Примечание 3. Почему Smalltalk?



Собственно, цель курса и состоит в том, чтобы ответ на этот вопрос был бы понят и прочувствован.

В кратком виде аргументация такова: Smalltalk очень прост в своей основе но при этом его выразительные возможности огромны.
(Изначально хотел написать много тезисов. Но, похоже, все они лишь комментируют и обосновывают данный. С ними будем разбираться уже в лекциях непосредственно по Smalltalk.)

вторник, 10 марта 2009 г.

Отложенные ошибки

Коллега предложил очень интересную идею, которую тоже можно рассмотреть в рамках курсовой.

Все ошибки необработанные исключения в программе сохраняются как континуации. Тогда разработчик в (далеком светлом) будущем анализирует протокол и может посмотреть "живую" ошибку, исправить, а если повезет, то и завершить действия пользователя без потери данных.

Особенно актуально для Web...

MVC(P) для Seaside, Pier

Ссылки по теме: 

суббота, 7 марта 2009 г.

Content Management Systems

Цель --- разработка Web-сайтов на Smalltalk.
Задачи:
  • преимущества и недостатки Pier относительно других CMS
  • что хорошо и что не очень хорошо в устройстве Pier
  • перенос Pier в VisualWorks
  • разработка своей CMS

О чем и для кого этот блог

Здесь я буду писать о
  • языке программирования Smalltalk и соответствующих средах
  • методологии Test-Driven Development (TDD)
  • о том, как TDD работает в Smalltalk
  • о "смежных" вопросах
Для кого я буду это писать:
  • для себя --- чтобы не забыть и, надеюсь, когда-нибудь использовать эти записи
  • для студентов, которых мне доведется учить
  • для всех интересующихся указанными вопросами
Замечание: весьма вероятно, этот пост будет модифицироваться в будущем...