Тестирование#

Unit-тестирование#

При необходимости проверки работоспособности отдельных частей исходного кода имеется возможность реализации unit-тестирования на основе ScalaTest.

Для создания набора тестов необходимо создать класс, который будет наследоваться от LangFunSuite, ApiTest или других классов, в которые подмешаны трейты из ScalaTest.

Совет

Для дополнительной информации смотрите библиотеку unit тестирования: scalatest

Создание класса с тестами#

  1. Перейдите в окно проекта

  2. Выберете целевой модуль

  3. Перейдите в папку с исходными кодами
    [module_name]/src/test/scala

    Совет

    Создать недостающую папку можно из контекстного меню в idea New > Directory

  4. Создайте пакет ru.bitec.app.[module_name]

  5. Создайте тестовый класс

     class Lesson1Test extends LangFunSuite{
       test("HelloWorld"){
           println("hello world")
       }
     }
    

Совет

Запустить тест можно из контекстного меню, для этого:

  1. Переведите курсор на декларацию функции или класса, если нужно запустить все объявленные тесты класса

  2. В контекстном меню выполните операцию „Debug“ для запуска в режиме отладки или „Run“ для простого запуска

Подробнее смотрите выполнение тестов в руководстве idea.

Примечание

Существуют два специализированных базовых класса для тестовых случаев:

  • LangFunSuite
    Используется для тестов которые не нуждаются в контроллерах бизнес логики.

    Данные тесты не могут использовать Api, Pkg, и не имеют подключения к базе данных по умолчанию.

  • ApiTest
    В данном тексте доступен контекст автономной бизнес логики, тестовые случаи могут использовать Api,Pkg объекты. При этом запуск теста становится медленней из за необходимости инициализировать контекст.

Базовые Assert методы#

Трейт Assertions содержит основные методы для проверки предположений.

  1. assert(condition: Boolean)

Этот метод проверяет условие. Если переданное условие возвращает true, то метод завершается нормально, иначе выбрасывает ошибку TestFailedException.

Пример:

  test("создание объекта класса Btk_SomeClass") {
    try {
      val rop = Btk_SomeClassApi().insert()
      assert(Btk_SomeClassExtApi().findSomeClassExt(rop).isDefined)
    } finally {
      session.rollback()
    }
  }

Данный тест проверяет, что при создании объекта Btk_SomeClass для него создается расширение Btk_SomeClassExt. Если расширение не было создано, то тест не будет пройден. В консоль будет выведен текст ошибки

Btk_SomeClassExtApi.apply().findSomeClassExt(rop).isDefined was false

В assert можно передать дополнительный комментарий, который будет добавляться к тексту ошибки:

  test("создание объекта класса Btk_SomeClass") {
    try {
      val rop = Btk_SomeClassApi().insert()
      assert(rop.get(_.dBeginDate).isNotNull, "При создании не установилась дата начала.")
      assert(Btk_SomeClassExtApi().findSomeClassExt(rop).isDefined, "При создании не зарегистрировалось расширение.")
    } finally {
      session.rollback()
    }
  }
  1. assertResult(expected: Any)(actual: Any)

Сравнивает ожидаемое ожидаемое значение с переданным

  test("parseGtkSessionClientFullName") {
    val x = WorkSessionClientHelper.parseGtkSessionClientFullName("DESkTOP-23565:8080#E1@123-45")
    assert(x.isDefined)
    assertResult("DESkTOP-23565:8080")(x.get._1)
    assertResult("E1")(x.get._2)
    assertResult("123-45")(x.get._3)
  }
  1. assertThrows[T <: AnyRef](f: => Any)

Используется,когда нужно удостоверится, что при определенных методах тестируемый код выдаст ошибку.

  test("data.oaObjQty.isEmpty") {
    val data = new Bs_DistrData()
    assertThrows[AppException] {
      Bs_DistributionPkg().distribQty(data)
    }
  }
  1. assume(condition: Boolean)

Метод aналогичен методу assert, но в случае, когда не выполняется условие, не завершается ошибкой, а отменяет тест. Так же может содержать пояснительный комментарий.

assume(database.isAvailable, "База данных не доступна.")
assume(database.getAllUsers.count === 9)
  1. intercept[T <: AnyRef](f: => Any)

Метод aналогичен методу assertThrows, но позволяет получить ошибку ожидаемого типа для более детальной проверки.

        val vEx = intercept[AppException] {
          _api.validateOnBeforeManualInsert(ropDuplicate)
        }
        assert(vEx.getMessage.startsWith("Ошибка. Уже существует запись с классом типа объектов:"))

Matchers#

Трейт Matchers позволяет использовать в тестах комбинаторы вроде shouldBe вместо обычного assert, улучшая читаемость кода.

По умолчанию к ApiTest и LangFunSuite Matchers не подключен, поэтому чтобы использовать его методы, добавьте его в объявлении класса:

class Bs_AccApiTest extends ApiTest with Matchers {
  test("set level on insert") {
    val rop = api.insert()
    rop.get(_.nLevel) should ===(1.nn)
  }
}

Для дополнительной информации смотрите в руководстве пользователя: matchers

Некоторые примеры:

  1. Проверка размера и длины

Для проверки размера и длины используйте:

  • <проверяемый объект> should have length (<ожидаемое значение>)

  • <проверяемый объект> should have size (<ожидаемое значение>)

Пример:

  test("проверка размера массива") {
    val result = getArgs()
    result should have length (3)
  }
  1. Проверка строк

  • на наличие подстроки

    string should startWith ("Hello")
    string should endWith ("world")
    string should include ("seven")
  • с использованием регулярных выражений

    string should startWith regex ("Hel*o")
    string should endWith regex ("wo.ld")
    string should include regex ("wo.ld")
  • проверка всей строки на соответствие регулярному выражению

    string should fullyMatch regex ("""(-)?(\d+)(\.\d*)?""")
  1. Сравнения больше/меньше/равно Можно сравнивать объекты любого типа, кроме тех, которые могут быть преобразованы в Ordered[T]. Для сравнения списков, используйте greater than, less than, greater than or equal, less than or equal to a value of type T

    one should be < (7)
    one should be > (0)
    one should be <= (7)
    one should be >= (0)
  • с использованием регулярных выражений

    string should startWith regex ("Hel*o")
    string should endWith regex ("wo.ld")
    string should include regex ("wo.ld")
  • проверка всей строки на соответствие регулярному выражению

    string should fullyMatch regex ("""(-)?(\d+)(\.\d*)?""")

Массовое тестирование модулей#

Из консоли sbt#

  • Для запуска тестов по имени/модулю воспользуйтесь командой testOnly

testOnly {путь до класса}.{имя класса}

Например:

testOnly ru.bitec.app.pro.diagram.Pro_DiagramApiTest

Символ * заменяет любое количество символов в наименовании теста, таким образом можно запустить выполнение тестов не по полному имени, а по части наименования. Например, запустить тесты по модулю можно так:

testOnly ru.bitec.app.{ваш модуль}.*

Например:

 testOnly ru.bitec.app.tax.*
  • Для запуска тестов всего проекта воспользуйтесь командой test

 test

Примечание

Не рекомендуется использовать, запускайте этот сценарий только на тех проектах, где точно известно, что все тестовые классы не делают нежелательных изменений в файлах или в БД.

  • Для запуска только тех тестов, которые связаны с последними изменениями в коде, используйте команду testQuick, которую можно вызывать с такой же фильтрацией, как и testOnly.

Запуск сценариев тестирования с использованием JMeter#

  • Для запуска скачайте и установите JMeter

  • Скачайте пример, запускающего тестирование, и откройте его в JMeter:

    пример проекта

  • Данный проект содержит три примера сценария:

    • TestByModule - тестирование по модулю

    • TestByClass - тестирование по классу

    • TestAll - запуск всех тестов проекта

  • Выберете нужный тред и сделайте его активным (операция контекстного меню Enable. Disable - сделает выбранный элемент неактивным).

  • Развернув выбранный тред, отредактируйте параметры компонента run test:

    • При необходимости замените выбранную рабочую директорию, на ту, где лежит исполняемый файл sbt нужного вам проекта.

    • В параметрах замените исполняемую команду (Примеры см. выше), исправьте модуль или имя файла.

  • Запустите тестирование через операцию Start на тулбаре. Выполнение тестирования может занять продолжительное время. Дождитесь окончания.

  • Результаты выполнения будут отображены в View Results Tree. Для более детального отчета можно посмотреть логи на вкладке «Response data».

Выборочное исключение тестов из массового тестирования#

Иногда возникает необходимость временно отключить тест, для этого нужно вызов метода test заменить на ignore. В этом случае при тестировании в результат будет записано, что этот тест проигнорирован.

Jexl-тесты#

В меню Настройки и сервисы - Сервисы Jexl - Jexl-тесты можно писать и выполнять jexl-тесты.

Создание#

При создании jexl-теста ему необходимо задать план, который будет состоять из разных шагов, выполняемых друг за другом. Для каждого шага необходимо задать наименование и текст скрипта, который должен выполниться. Порядковый номер шага проставится автоматически.

На закладке «Глобальные переменные» можно задать переменные, к которым можно обращаться в тесте через методы tst.setGlobalVar и tst.getGlobalVar. Для их создания необходимо указать системное имя переменной, которая должна быть уникальна для теста, и ее значение в виде jexl-скрипта, который будет выполняться перед стартом теста.

Доступные методы#

tst.setVar#

Записывает в контекст выполнения jexl-теста переданное значение. Информация об изменении переменной записывается в лог выполнения теста с типом SETVAR

Пример:

tst.setVar("param_name", param_value); //param_name - имя параметра, param_value - значение параметра
tst.getVar#

Достает из контекста значение параметра по его имени.

Пример:

var p = tst.getVar("param_name"); //param_name - имя параметра
tst.setGlobalVar#

Записывает в глобальные переменные выполнения jexl-теста переданное значение. Доступ к этим переменным есть на любом шаге теста, без необходимости получать нужный контекст. Информация об изменении переменной записывается в лог выполнения теста с типом SETVAR

Пример:

tst.setGlobalVar("param_name", param_value); //param_name - имя параметра, param_value - значение параметра
tst.getGlobalVar#

Получает значение глобальной переменной по ее имени.

Пример:

var p = tst.getGlobalVar("param_name"); //param_name - имя параметра
tst.findParentContext#

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

Пример:

var parent = tst.findParentContext();
var parentParam = parent.getVar("param_name");
tst.info#

Записывает в лог выполнения теста переданный текст с типом INFO.

Пример:

tst.info("Текст с информацией");
tst.warning#

Записывает в лог выполнения теста переданный текст с типом WARNING.

Пример:

tst.warning("Текст с предупреждением");
tst.error#

Записывает в лог выполнения теста переданный текст с типом ERROR.

Пример:

tst.error("Текст с ошибкой");
tst.raise#

Выбрасывает ошибку с переданным текстом, а также записывает ее в лог выполнения с типом ERROR.

Пример:

tst.raise("Текст с ошибкой");
tst.assertTrue#

Проверят, что переданное условие истинно. Если оно ложно, то прекращает выполнение теста и записывает в лог выполнения ошибку. Если выполняется группа тестов, и проверка не пройдена то переходит к следующему тесту.

Пример:

tst.assertTrue(1 == 2); //запишет в лог "assertTrue не прошло проверку"

tst.assertTrue(1 == 2, "Число 1 не равно числу 2"); //запишет в лог переданный текст "Число 1 не равно числу 2"
tst.shouldBeTrue#

Проверят, что переданное условие истинно. Если оно ложно, то записывает в лог выполнения ошибку.

Пример:

tst.shouldBeTrue(1 == 2); //запишет в лог "shouldBeTrue не прошло проверку"

tst.shouldBeTrue(1 == 2, "Число 1 не равно числу 2"); //запишет в лог переданный текст "Число 1 не равно числу 2"
tst.sqlRows#

Возвращает количество строк, полученных в результате выполнения переданного запроса

Пример:

var count = tst.sqlRows(`select t.id from RplTst_AllDbTypes t where t.idObjectType = 120`);

Выполнение#

Выполненение теста осуществляется в помощью операции Выполнить тест в карточке теста или списке тестов. Для выполнения тестов целой группы необходимо в списке тестов выполнить операцию Выполнение всех тестов текущей группы.

Выполнение с помощью Rest сервиса#

Выполнение тестов также возможно с помощью Rest сервиса.

Путь для выполнения одиночного теста: http://{имя_сервера}/app/sys/rest/ss/pkg/Bts_TestPkg/runSingleTest

Путь для выполнения группы тестов: http://{имя_сервера}/app/sys/rest/ss/pkg/Bts_TestPkg/runTestForGroup

Тело обоих запросов является json-объектом, который состоит из ключа name и его значения, в виде системного имени теста или группы, соответственно.

Запрос выполнения одиночного теста возвращает json-объект, который содержит в себе следующие параметры: имя теста name, наличие ошибок hasErrors, наличие предупреждений hasWarnings, количество ошибок countErrors и количество предупреждений countWarnings.

Пример для одиночного теста:

//пример обращения
curl -H Database:pgDev -u admin:admin -d {\"name\":\"PRS_PurchReqTest\"} -H "Content-Type: application/json" -X POST http://localhost:8080/app/sys/rest/ss/pkg/Bts_TestPkg/runSingleTest

//пример результата
{"hasErrors":true,"hasWarnings":false,"countErrors":1,"countWarnings":0,"name":"Тест заявок на закупку услуг"}

Запрос выполнения группы тестов возвращает json-объект, который содержит в себе следующие параметры: наличие ошибок hasErrors, наличие предупреждений hasWarnings, количество ошибок countErrors, количество предупреждений countWarnings и результаты выполнения всех тестов tests в виде массива json-объектов таких же, как при выполнении одиночного теста.

//пример обращения
curl -H Database:pgDev -u admin:admin -d {\"name\":\"Test1\"} -H "Content-Type: application/json" -X POST http://localhost:8080/app/sys/rest/ss/pkg/Bts_TestPkg/runTestForGroup

//пример результата
{"hasErrors":true,"hasWarnings":false,"countErrors":2,"countWarnings":0,"tests":[{"hasErrors":false,"hasWarnings":false,"countErrors":0,"countWarnings":0,"name":"test_ok"},{"hasErrors":true,"hasWarnings":false,"countErrors":1,"countWarnings":0,"name":"test333"},{"hasErrors":true,"hasWarnings":false,"countErrors":1,"countWarnings":0,"name":"test22"}]}