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

  • Tutorial
Больше всего программисты любят программы, в которых не нужно исправлять баги. Шагом на пути к этой несбыточной мечте является написание юнит-тестов. В 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(surnamenamejob, .ec)
    
do $$$AssertStatusOK(ec)
    
    
set ##class(Tutorial.Person).%OpenId(id)
    
do $$$AssertTrue($IsObject(p))
    
    
do $$$AssertEquals(p.Surnamesurname)
    
do $$$AssertEquals(p.Namename)
    
do $$$AssertEquals(p.Jobjob)
}
}

Класс, содержащий тесты, должен наследовать от класса %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: Ошибка: <CLASS DOES NOT EXIST>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://<yourhost>:<yourport>/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 в справочнике классов.
  • +18
  • 7,2k
  • 9

InterSystems

95,41

Вендор: СУБД Caché, OLAP DeepSee, шина Ensemble

Поделиться публикацией

Похожие публикации

Комментарии 9
    +2
    >>Больше всего программисты любят программы, в которых не нужно исправлять баги. Шагом на пути к этой несбыточной мечте является написание юнит-тестов.

    Уж поверьте, исправлять баги гораздо интереснее и веселее, чем писать юнит-тесты :))
      +3
      Везёт вам.
        +1
        Я не говорю, что иметь баги правильнее, чем юнит-тесты, но лично для меня написание юнит-тестов — это самая худшая часть программирования :-0
          +1
          Если вас это устраивает, то ok.
          А если нет — то ещё рекламирую отличную книжку «Growing object-oriented software guided by tests», в которой отлично описана философия юнит-тестов и TDD.
        +5
        Весело исправлять баги без тестов?

        Как-то так?
        +1
        Добрый день! Очень подробно, хорошо написано. Мне кажется было бы плюсом описать способ автоматического запуска с помощью Jenkins, например. Не будут же разработчики, особенно если команда большая, запускать тесты каждый раз после коммита из терминала каше. Как считаете?
          +1
          Согласен. И вот тут есть рецепт doublefint, как приготовить Jenkins с Caché
            +2
            Можно использовать хуки в Continuous Integration проекте CacheGitHubCI для запуска юнит тестов. Преимуществом проекта является также то, что он полностью написан на COS. После каждого коммита тесты будут запущены автоматически.
              +1
              Можно включить аутентификацию на уровне ОС (http://docs.intersystems.com/cache20152/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_secmgmt#GCAS_secmgmt_autheopts) и запускать программы из командной строки Windows:
              ccontrol cterminal CACHE ^^test
              (http://docs.intersystems.com/cache20152/csp/docbook/DocBook.UI.Page.cls?KEY=GSA_using_instance#GSA_B150513)

              В программе ^test может быть всё что угодно, в том числе запуск тестов

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

            Самое читаемое