Немного терминологии, которая будет использоваться в статье:
- Приложение — база данных IBM Notes/Domino или совокупность баз данных, если они представляют собой единую логическую единицу.
- Клиент — толстый клиент, ПО IBM (Lotus) Notes.
- Сервер — серверное ПО IBM Domino.
Начну с описания того, как выглядит разработка, чтобы каждый мог взглянуть на себя. Речь, в первую очередь, идет о написании приложений под толстый клиент с использованием @-формул и языка LotusScript. Оба языка скриптовые, а код можно писать практически везде: в событиях и действиях UI-объектов (таких, как формы и представления), в библиотеках (только для языка LotusScript), агентах и других элементах дизайна базы. К тому же, для каждого события и действия можно независимо выбрать язык, на котором писать. В итоге, если не иметь какой-то подход к организации кода, получается ужасная лапшеобразная каша из кода, разбросанного по всем мыслимым и немыслимым местам. А теперь добавим к этому большой зоопарк приложений, и, обычно, небольшой штат специалистов.
Мне был привит (за что очень благодарен) определенный подход к написанию кода, который я в дальнейшем развивал. В итоге получилась целая система, которая говорит не только о том, где писать, но и как писать. Если в кратце:
- вся бизнес-логика пишется на языке LotusScript, использование @-формул минимально;
- весь код находится в библиотеках, причем разделен на:
- код, который может использоваться как на сервере, так и на клиенте;
- код, который может использоваться исключительно на клиенте;
- в визуальных элементах (формы, представления) используются только вызовы библиотечного кода, причем, в подавляющем большинстве — однострочные;
- максимальное использование объектных и минимальное использование процедурных возможностей языка LotusScript.
Так при чем тут модульное тестирование? Чем более сложная, многозвенная система получается, тем сложнее удержать в голове все ее составляющие. Неизбежно правки в одном месте приводят к поломкам в другом. Зачастую, большое количество вариантов происходящего, количество параметров и зависимостей при ограниченных ресурсах, делают практически невозможным ручное тестирование всех вариаций. Нельзя отметать страх изменений, потому как не всегда можно помнить, а уж тем более знать особенности кода, который лежит в основе. Наличие тестов может как очертить границы взаимодействий, так и гарантировать проверку работоспособности. Да, это дополнительные усилия на покрытие тестами, их составление, но, как мне представляется, они того стоят.
Товарищ google, ассистировавший при поиске средств именно модульного (unit) тестирования для IBM Notes/Domino под язык LotusScript, привел только к одному очень старому варианту проекта на OpenNTF.org. В целом, идея, которая крутилась у меня голове, была похожей, но реализация обещала несколько больше. Поэтому было принято решение писать свой, не побоюсь этого слова, framework для unit-тестирования
За основу построения логики тестов взял обычную для любого (наверно) фреймворка для unit-тестирования: класс с методами-тестами. Эти тесты надо собрать в кучу и прогнать. Итак, в голове составился некоторый список того, что нужно для минимального набора:
- тесты будут писаться в виде методов класса;
- тесты будет запускать агент;
- при написании тестов понадобится набор проверок (ассертов, assert);
- в рамках запуска тестов должна вестись статистика успешности теста и выводиться строка в тесте, где произошла ошибка.
Некоторые аспекты и допущения, которые были сделаны при проектировании реализации:
- LotusScript не имеет в своем арсенале ни рефлексии, ни аннотаций, а значит необходимо будет явно указывать какие методы являются тестовыми. Варианты, например, с парсингом библиотеки с классами-тестами отмел, как минимум для первого этапа.
- Минимальный набор проверок:
- сравнение выражения с истиной;
- ложью;
- сравнение на равенство. Т.к. вопрос равенства — вещь контекстно-зависимая, в метод сравнения на равенство, будет передаваться объект. Класс этого объекта должен быть унаследован от класса-интерфейса (в языке LotusScript нет чистых интерфейсов) с одним публичными методом, возвращающим логическое значение;
- проверка на ошибку, а именно, на код ошибки.
При реализации встал вопрос: как фиксировать статистику? Опять же, средств рефлексии и аннотаций нет, значит:
- либо оборачивать каждый тестовый метод в агенте, который их запускает, кодом со статистикой;
- либо внести это в сам тестовый метод.
Принял решение пойти по второму пути. В итоге родился класс AbstractTest с двумя публичными методами: BeginTest(ИМЯ_ТЕСТА), EndTest(ИМЯ_ТЕСТА). Почему публичными? Это было сделано для удобства, из-за особенностей встроенной IDE на основе Eclipse: приватные методы суперкласса видны потомкам и доступны для переопределения, но в подсказки самой IDE не попадают. Шаблон каждого тестового метода для успешного сценария выглядит вот так:
Public Sub TestMethod()
On Error GoTo ErrorHandler
Const FuncName = "TestClassName.TestSubName ()"
Call Me.BeginTest(funcName)
'Код тестового метода располагается здесь
Call Me.EndTest(funcName)
GoTo endh
ErrorHandler:
Error Err, "(" & DESIGN & ") " & FuncName & ", line " & Erl & Chr(10) & Error$
endh:
End Sub
Для тестовых методов с ожидаемой ошибкой он немного отличается и выглядит так:
Public Sub TestError()
On Error GoTo ErrorHandler
Const FuncName = "DemoTest.TestAssertErrorExample ()"
Call Me.BeginTest(funcName)
'Код может быть как здесь
On Error ERROR_CODE_TO_TEST GoTo AssertErrorHandler
'Код, который завершается с ошибкой, обозначенной выше располагается здесь
GoTo endh
AssertErrorHandler:
Call Me.EndTest(funcName)
Resume endh
ErrorHandler:
Error Err, "(" & DESIGN & ") " & FuncName & ", line " & Erl & Chr(10) & Error$
endh:
End Sub
Из этих размышлений родился тестовый контекст, хранящий информацию времени выполнения тестов:
- данные о статусах прохождения тестов;
- возможность добавления тестов и изменения статусов;
- ведение логирования.
Шаблонизированию подвергся и класс, ответственный за запуск тестов. Если свести к минимуму код, то он состоит из основного метода Run() и метода, который следует переопределить в агенте, запускающем тесты DoRunTests(). Метод DoRunTests как раз должен содержать создание классов-тестов и вызов соответствуютщих тестовых методов:
Class AbstractTestRunner
Public Function Run()
On Error GoTo ErrorHandler
Const FuncName = "AbstractTestRunner.Run ()"
[...]
Call Me.DoRunTests()
[...]
GoTo endh
ErrorHandler:
Error Err, "(" & DESIGN & ") " & FuncName & ", line " & Erl & Chr(10) & Error$
endh:
End Function
Private Sub DoRunTests()
On Error GoTo ErrorHandler
Const FuncName = "AbstractTestRunner.RunTests ()"
'Override
GoTo endh
ErrorHandler:
Call Me.TestFailed(Error$)
Resume Next
endh:
End Sub
End Class
С проверками все просто. Небольшого отдельного внимания может заслуживать только метод сравнения на равенство:
Public Sub Equals(matcher As IMatcher)
On Error GoTo ErrorHandler
Const FuncName = "Assert.Equals ()"
If Not Matcher.Matches() Then Call Me.Throwerror(funcName)
GoTo endh
ErrorHandler:
Error Err, "(" & DESIGN & ") " & FuncName & ", line " & Erl & Chr(10) & Error$
endh:
End Sub
Как говорилось выше, он принимает объект, чей класс унаследован от класса-интерфейса (IMatcher), имеющего только один публичный метод Matches() as boolean.
Class IMatcher
Public Function Matches() As Boolean
End Function
End Class
Также было реализовано несколько простых матчеров, которые были вынесены в отдельную библиотеку:
- SimpleValueMatcher — простое сравнение 2ух значений. Значения принимаются с типом Variant.
- DocumentItemsMatcher — сравнение заданных полей в двух заданных документах. Сравнение происходит на наличие поля, на тип и значение.
- DatabaseMatcher — сравнение двух баз: id реплики, пути и названия. Суть — мы имеем дело с одной и той же базой. Целесообразно сюда еще сравнение на сервер добавить.
- DocumentMatcher — сравнение 2х документов на идентичность: лежат в одной базе, имеют один и тот же UniversalId, совпадают по всем полям.
Проект размещен в открытом доступе на GitHub, распространяется по лицензии Apache 2.0 и снабжен подробной документацией по установке и использованию. Также проект был размещен на самой популярной для domino-водов площадке — Openntf.org.
В качестве развития вижу:
- упростить написание тестов, чтобы можно было избавится от ряда условностей при их создании;
- избавиться от необходимости вручную создавать тестовые классы и вызывать тестовые методы;
- создать централизованное приложение для запуска и сбора результатов тестирования.
Так получилось, что та система, которая показала всю необходимость создания unit-тестирования для Domino и сподвигла меня на написание представленного инструмента, на текущий момент так и не покрыта тестами. Однако готовлю еще один проект, который изначально должен стать публичными. Первая версия уже готова и на 100% покрыта тестами, созданными на описанном фреймворке. Статистику прохождения тестов собираюсь прикладывать к каждому релизу.
Конструктивные идеи, предложения и замечания всегда приветствуются!
Комментарии (9)
rezdm
16.01.2018 15:34Кто его использует ещё?
В эээ 2002 году у меня однокашница делала какой-то зарплатный репорт на лотусе, так он считал зарплату небольшого предприятия (человек 30-40) часы. Уже тогда это было дикостью.k3NGuru
16.01.2018 15:57Использует налоговая, используем мы ( свыше 1000 сотрудников). Здоровая Java махина. Удобно, что в нем почта, IM (sametime), плюс СЭД.
artemaa Автор
17.01.2018 09:57Банковская и гос сфера — много. На самом деле инструмент довольно удобный и приписываемая монструозность — это скорее наличие толстого клиента и убогого подхода к процессу разработки под этот клиент. Во многом платформа используется для документооборота (в основе — NoSQL хранилище). На самом деле, на рынке крупных СЭД далеко не все имеют полнофукциональный тонкий клиент.
BigD
18.01.2018 23:54ну да, там даже локальный почтовый ящик (Nsf) — это по сути БД. Но я как вспомню эти глюки, когда что-то слетало в формате, и у тебя оставались все письма, но признаки папок, в которых они лежали, терялись напрочь — жесть жесткая была.
BigD
Lotus жив?
artemaa Автор
В целом да, даже старается где-то прогрессировать. У нас платформа в общих кругах почему-то считается совсем загнивающей, тогда как «там» делается довольно много интересных околоплатформенных проектов. Большинство, конечно, уже с использованием Web возможностей.
BigD
Ну не знаю… Все, кто на нём сидят (включая русские офисы IBM, PwC и т.д.) жутко плюются и ждут миграции на O365 (IBM'у это вряд ли светит..). BAT перешел недавно полностью на O365, и все туда идут на самом деле. Сам писал на 7 версии приложения в 2008-2011 годах. Монстр был еще тогда.