Юнит-тесты в Caché – это просто

Больше всего программисты любят программы, в которых не нужно исправлять баги. Шагом на пути к этой несбыточной мечте является написание юнит-тестов. В Caché, как и в любой современной СУБД, есть реализация фреймворка для автоматического выполнения тестов.

d249e383b49540798cf8dd0fb2036ee1.jpg
За подробным описанием философии и методологии написания юнит-тестов я вас отсылаю в интернет — на Хабр и к замечательной книге «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 ##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 в справочнике классов.

© Habrahabr.ru