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

Denis Goryachev: Morphic. Введение.

Denis Goryachev: Morphic. Введение.

среда, 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.