Юнит-тесты в Caché – это просто
Больше всего программисты любят программы, в которых не нужно исправлять баги. Шагом на пути к этой несбыточной мечте является написание юнит-тестов. В Caché, как и в любой современной СУБД, есть реализация фреймворка для автоматического выполнения тестов.
За подробным описанием философии и методологии написания юнит-тестов я вас отсылаю в интернет — на Хабр и к замечательной книге «Growing Object-Oriented Software Guided by Tests». На русский язык она не переведена, но английский там простой и понятный.
В качестве примера напишем тщательно оттестированный метод, который принимает на входе фамилию, имя и должность, сохраняет их в таблице и возвращает id созданного объекта. Таблица будет называться Tutorial.Person.
По всем канонам TDD сначала создадим тест:
Class Tutorial.Test.Person Extends %UnitTest.TestCase
{
Method TestNewPerson () {
set surname = «John»
set name = «Doe»
set job = «quality assurance developer»
set id = ##class(Tutorial.Person).AddPerson(surname, name, job, .ec)
do $$$AssertStatusOK(ec)
set p = ##class(Tutorial.Person).%OpenId(id)
do $$$AssertTrue($IsObject(p))
do $$$AssertEquals(p.Surname, surname)
do $$$AssertEquals(p.Name, name)
do $$$AssertEquals(p.Job, job)
}
}
Класс, содержащий тесты, должен наследовать от класса %UnitTest.TestCase. Все методы этого класса, которые начинаются со слова Test, считаются тестами. Макросы и методы, доступные наследникам %UnitTest.TestCase, описаны в документации.
Тесты запускает метод ##class (%UnitTest.Manager).RunTest (subdir, spec). Этот метод загружает и выполняет все классы с тестами из подпапки subdir каталога, который указан в глобале ^UnitTestRoot. Spec — дополнительные ключи для запуска. По умолчанию после выполнения классы с тестами удаляются. Мы этого, конечно, не допустим, добавив вторым аргументом к RunTest строку с ключом »/nodelete».
Итак, создайте папку c:\unittests\habr и экспортируйте в неё наш класс с тестом (понятно, что папку вы можете назвать как угодно и что подпапку лучше бы было назвать tutorial, обозначая пакет, который мы будет проверять; но, чтобы подчеркнуть, что эти названия не обязаны совпадать, я назвал подпапку habr). Экспортируйте тест в эту папку (я назвал файл tutorial.test.person.xml) и вперёд в терминал!
USER>set ^UnitTestRoot="c:\UnitTests"
USER>do ##class(%UnitTest.Manager).RunTest("habr","/nodelete/noload")
==================================================================
Directory: C:\UnitTests\habr\
==================================================================
habr begins ...
Перечислить стартовавшие элементы в каталоге 07/26/2015 00:59:23 '*.xml;*.XML'
Вывести файл C:\UnitTests\habr\tutorial.test.person.xml в xml
Вывод завершен успешно.
Tutorial.Test.Person begins ...
TestNewPerson() begins ...
LogStateStatus:0:TestNewPerson:ОШИБКА #5002: Ошибка: zTestNewPerson+4^Tutorial.Test.Person.1 *Tutorial.Person <<==== **FAILED**
habr:Tutorial.Test.Person:TestNewPerson:
TestNewPerson failed
Tutorial.Test.Person failed
Skipping deleting classes
habr failed
Use the following URL to view the result:
http://192.168.1.6:57772/csp/sys/%25UnitTest.Portal.Indices.cls?Index=2&$NAMESPACE=USER
Some tests FAILED in suites:
habr
Обратите внимание — я указал два ключа в методе RunTest: /nodelete — не удалять класс с тестами после запуска, /noload — не загружать и не компилировать сам класс.
Как видите, наш тест благополучно завершился с ошибкой. В конце вывода напечатан URL для веб-интерфейса результатов запуска этого теста и истории запусков. Обратите внимание, что этот URL ссылается на веб-приложение /csp/sys, указывая нужную область аргументом. Если вы в Портале управления смените область на USER и потом перейдёте по ссылкам Обозреватель системы > Инструменты > Портал UnitTest, то, вероятно, вы увидите ошибку Forbidden при попытке открыть следующий URL:
http://:/csp/user/%25UnitTest.Portal.Home.zen?$NAMESPACE=USER
Эта ошибка вызвана тем, что в Caché по умолчанию запрещено обращение к системным (процентным) страницам из веб-приложений не начинающихся на /csp/sys. Чтобы разрешить открытие страниц, относящихся к юнит-тестам в области USER, выполните следующую команду в терминале в области %SYS:
%SYS>Set ^SYS("Security","CSP","AllowPrefix","/csp/user/","%UnitTest.")=1
Так как классов с тестами почти всегда много, я создаю отдельный класс с методом, который сначала выгружает все классы с тестами в каталог, а затем вызывает RunTest:
Class Tutorial.Test.All [ Abstract ]
{
ClassMethod runall ()
{
do $system.OBJ.Export(«Tutorial.Test.Person.cls», ^UnitTestRoot_»\habr\»_«tutorial.test.person.xml»)
; здесь можно добавлять экспорт новых классов с тестами.
do ##class(%UnitTest.Manager).RunTest(«habr», »/nodelete/noload»)
}
}
Последнее, что осталось, это создать класс Tutorial.Person и метод AddPerson, запустить тесты и увидеть долгожданное »ALL PASSED»:
USER>do ##class(Tutorial.Test.All).runall()
Экспорт в XML начался в 07/26/2015 01:25:12
Экспортируемый класс: Tutorial.Test.Person
Экспорт успешно завершен.
==================================================================
Directory: C:\UnitTests\habr\
==================================================================
habr begins ...
Перечислить стартовавшие элементы в каталоге 07/26/2015 01:25:12 '*.xml;*.XML'
Вывести файл C:\UnitTests\habr\tutorial.test.person.xml в xml
Вывод завершен успешно.
Tutorial.Test.Person begins ...
TestNewPerson() begins ...
AssertStatusOK:ec (passed)
AssertTrue:$IsObject(p) (passed)
AssertEquals:p.Surname== surname (passed)
AssertEquals:p.Name== name (passed)
AssertEquals:p.Job== job (passed)
LogMessage:Duration of execution: .000503 sec.
TestNewPerson passed
Tutorial.Test.Person passed
Skipping deleting classes
habr passed
Use the following URL to view the result:
http://192.168.1.6:57772/csp/sys/%25UnitTest.Portal.Indices.cls?Index=7&$NAMESPACE=USER
All PASSED
На этом месте пытливый читатель (привет, tsafin) возмутится:»Зачем столько извращений, чтобы запустить простой тест?! Нельзя ли как-нибудь попроще? ». Конечно нельзя можно! Но не сильно проще. Если папка c:\UnitTests\habr есть и в ^UnitTestRoot прописан путь «c:\UnitTests», то отдельно взятый класс с тестами можно (без предварительной выгрузки) запустить так:
do ##class(%UnitTest.Manager).DebugRunTestCase("habr","Tutorial.Test.Person")
Не забывайте, Caché — это СУБД. Все результаты тестов сохраняются и доступны для SQL-запросов. Откройте окно Портала для ввода SQL. Поставьте галочку рядом со словом Система, чтобы показать системные классы. Выберите в выпадающем списке схему %UnitTest_Result. Например, в столбце Duration некоторых таблиц этой схемы хранится продолжительность отдельного теста (или метода внутри теста). Запуская тесты регулярно, вы можете следить не только за тем, чтобы они не падали, но и чтобы время их выполнения не увеличивалось.
В заключение рекомендую пройти небольшой туториал по юнит-тестам, который есть в документации по Caché, а также просмотреть описание классов пакета %UnitTest в справочнике классов.