Pull to refresh

Обзор uniset2-testsuite — небольшого велосипеда для функционального тестирования

Reading time 10 min
Views 4.6K


Как естественное продолжение работы над libuniset2, возник проект uniset2-testsuite. Это свой небольшой велосипед для функционального тестирования. В итоге он развился до более-менее универсального решения с «плагинами». Написан на python. Если интересно почитать, то прошу… заходите.

Основная идея тестирования, заложенная в uniset2-testsuite проста: «Подали воздействие, проверили реакцию». Эта абстрактная идея материализовалась в итоге в следующие артефакты:

Тестовый сценарий


Тестовый сценарий — это xml-файл, в котором записывается последовательность тестов. Каждый тест в свою очередь делится на «действия» (action) и проверки (check). Тесты могут вызывать другие тесты, и, более того, можно вызывать тесты, находящиеся в других файлах. Всё это позволяет строить довольно сложные и развесистые последовательности тестов, общие части выносить в отдельные файлы, вызывать их «как процедуры» и т.п. Помимо этого поддерживается механизм замен (replace), позволяющий писать шаблоны тестов с некоторыми абстрактными названиями «переменных», которые подменяются на конкретные в момент вызова теста.

Действия (action)


Поддерживается три вида действий:

  • 'SET'. Выставление значения
  • 'MSLEEP'. Пауза в миллисекундах
  • 'SCRIPT'. Вызов внешнего скрипта

Тут особо пояснять нечего. На примерах ниже будет видно.

Проверки (check)


  • '='. Проверка условия эквивалентности значения
  • '!='. Проверка с отрицанием
  • '> или >='. Проверка условия, что значение больше (или равно) заданному
  • '< или <='. Проверка условия, что значение меньше (или равно) заданному
  • 'LINK'. Ссылка на другой тест в данном файле
  • 'OUTLINK'. Ссылка на другой файл
  • HOLD. Проверка постоянства условия в течение заданного времени

Тут, я думаю, тоже более менее всё очевидно.

В итоге простой тестовый сценарий имеет следующий вид:

<?xml version = '1.0' encoding = 'utf-8'?>
<TestScenario>
<Config>
...
</Config>
<TestList type="uniset">
 <test name="Processing" comment="Проверка работы процесса">
     <action set="OnControl_S=1" comment="Подаём команду 'начать работу'"/>
     <check test="CmdLoad_C=1" comment="Подана команда 'наполнение'"/>
     <check test="CmdUnload_C=0" comment="Снята команда 'опустошение'"/>
     <check test="Level_AS>=90" comment="Цистерна наполняется.." timeout="15000"/>
     <check test="CmdLoad_C=0" comment="Снята команда 'наполнение'"/>
     <check test="CmdUnload_C=1" comment="Подана команда 'опустошение'"/>
     <check test="Level_AS<=10" comment="Цистерна опустошается.." timeout="15000"/>
  </test>
  <test name="Stopped" comment="Проверка остановки процесса">
     <action set="OnControl_S=0" comment="Снимаем команду 'начать работу'"/>
     <check test="CmdLoad_C=0" comment="команда 'наполнить' не меняется" holdtime="3000"/>
     <check test="CmdUnload_C=0" comment="команда 'опустошить' не меняется" holdtime="3000"/>
     <check test="Level_AS<=80" comment="Уровень не меняется" holdtime="10000"/>
  </test>
</TestList>
</TestScenario>

Как видно из приведённого примера. Действия записываются в виде тега

<action set="..."/>

проверки записываются в виде

<check test=".."/>

Проигрыватель тестов


Раз есть сценарий, значит кто-то его должен исполнить. Поначалу было желание сделать систему, позволяющую написать свои «проигрыватели», у каждого из которых мог бы быть свой формат сценария. Но в итоге т. к. в качестве формата был выбран xml, то соответственно был написан uniset2-testsuite-xmlplayer. Консольный проигрыватель, который исполняет xml-сценарии. А дальше дело не пошло, потому-что xml-формат оказался достаточно удачным и xmlplayer-а хватает для решения всех текущих задач. Хотя в самом репозитории есть ещё uniset2-testsuite-gtkplayer это GUI-проигрыватель, написанный на python-gtk. Но его развитие в какой-то момент было заморожено за ненадобностью и он не поддерживает многих функций.

Итого у нас получилось, что есть сценарии, состоящие из действий и проверок и есть проигрыватель который исполняет сценарии. Если всё это запустить, то на выходе можно увидеть примерно такую картинку



Немного предыстории…

В начале uniset2-testsuite рассматривался исключительно в рамках работы с libuniset, в которой основная концепция — «всё есть датчик». Поэтому формат записи подразумевает оперирование идентификаторами датчиков (числовыми или именами) и проверкой только числовых значений (целочисленных). Но постепенно развиваясь, добавилась возможность взаимодействия по протоколу modbus (type=«modbus»), недавно добавилась возможность работы через snmp (type=«snmp»). И в итоге стало более очевидно, что интерфейс для тестирования можно свести всего к двум основным функциям GET и SET. Т.е. для тестирования какой-либо системы достаточно реализовать всего две функции get_value(id) и set_value(id, value). И протокол взаимодействия с тестируемой системой не важен. Просто разработчик предоставляет свою реализацию этих двух функций для взаимодействия со своей системой. Эта идея легла в основу создания аналога системы плагинов. Т.е. для реализации взаимодействия с какой-то своей тестируемой системой, достаточно реализовать специальный python интерфейс и положить его (подключить) в нужное место. Как написать свой интерфейс для тестирования будет рассказано во второй части. Пока же я опишу кратко возможности. Поскольку их много всяких, я расскажу об основных, про остальные можно почитать в документации wiki.etersoft.ru/UniSet2/testsuite

Описание возможностей


Автоматический запуск программ


Конечно хотелось бы, чтобы не просто исполнялись тестовые сценарии, а чтобы ещё и при запуске теста, запускались все необходимые для теста программы. Для этого случая в тестовом сценарии предусмотрена секция RunList.

Псевдопример:

<?xml version = '1.0' encoding = 'utf-8'?>
<TestScenario>
<RunList after_run_pause="5000">
   <item after_run_pause="2000" script="./start_my_testprog1.sh" args="arg1 arg2" name="TestProg1"/>
   <item script="./start_my_testprog2.sh" args="arg1 arg2" chdir="../../TestPrograms/" name="TestProg2"/>
   <item script="./start_my_testprog3.sh" args="arg1 arg2" ignore_terminated="1" name="TestProg3"/>
</RunList>
<TestList>
...тесты..
</TestList>
<TestScenario>

В этой секции можно задать список того, что нужно запустить перед началом теста. Для каждой запускаемой программы указываются параметры запуска (args), программы могут лежать в других каталогах (chdir). Можно задать уникальное имя (name), которое в случае вылета программы будет выведено в логах. При этом как видно из примера, так же можно задать паузы после запуска той или иной программы или всех вместе. Можно задать параметр silent_mode=[0,1] , где 1 означает перенаправить весь вывод в /dev/null). А можно указать параметр logfilе=«filename», при котором весь вывод будет перенаправлен в указанный файл (отменяет действие silent_mode).

Все программы запускаются в фоновом режиме. По умолчанию, если какая-либо из них вдруг во время теста вылетает, тест прерывается с ошибкой. Но это можно отключить параметром ignore_terminated=«1». Все запущенные программы завершаются после прохождения теста (не важно успешного или нет). Т.е. в итоге RunList позволяет запускать необходимое окружение для тестирования, которое будет автоматически завершено после. Ну либо производить какие-то подготовительные действия перед началом теста.

Запуск скриптов при завершении


Ещё одной возможностью uniset2-testsuite является запуск указанных скриптов (программ) при успешном или провальном прохождении теста.

<?xml version="1.0" encoding="utf-8"?>
  <TestScenario>
    <Success>
        <item script="./success.sh param1 param2"/>
        <item script="./success.sh param3 param4"/>
    </Success>
    <Failure>
        <item script="./failure.sh param1 param2"/>
        <item script="./failure.sh param3 param4"/>
    </Failure>
  ....
  ...
  </TestScenatio>

Это можно использовать в разных случаях. Например (из очевидных):

  • послать уведомление в случае провала теста.
  • «Прибрать за тестом» после завершения работы
  • скопировать артефакты провалившегося (или успешного) теста «в сторонку»

Теги


Когда у Вас становится много развесистых тестов, которые содержат «полное покрытие», возможно Вам в некоторых случаях не хочется прогонять их все, для того, чтобы проверить какую-то одну «логическую ветку тестов». Для решения этой задачи на помощь приходят «теги». Вы можете помечать каждый тест одним или несколькими тегами.

...
<test name="Test3" tags="#tag1#tag2">
    <check test="outlink" file="Test4.xml" link="ALL"/>
</test>  
<test name="Test5" tags="#tag1">
   <check test="outlink" file="Test6.xml" link="ALL"/>
</test>  
<test name="Test7" tags="#tag2">
   <check test="outlink" file="Test8.xml" link="ALL"/>
</test>  
...

И при запуске сценария можно указать, что нужно исполнить только тесты с указанными тегами --play-tags "#tag1#tag2#".

Вывод дерева тестов


Т.к. существует механизм вызова тестов из других тестов и других файлов, то при достаточно сложной структуре тестов хочется видеть, как они будут вызваны, в какой последовательности и «кто кого вызывает». Для этого существует специальная команда, выводящая дерево тестов на экран --show-test-tree

Test1
  Test2
     Test3
  Test 4
  Test 5
     Test 6
         Test 7

При этом если для этой команды указать--play-tags "#tag1#tag2#", то будет выведено дерево тестов с учётом тегов.

  Test1 [#tag1]
     Test2 [#tag2]
        Test3 [#tag1]

Проверка корректности сценария


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

  • --check-scenario — Проверка сценария, завершающаяся при первой же ошибке
  • --check-scenario-ignore-failed — Проверка сценария, c игнорированием ошибок...

В этих режимах фактического «исполнения» проверок не происходит, нет пауз и timeout-ов. Т.е. происходит просто «синтаксическая» проверка теста и параметров на корректность.

Вот так выглядит результат:



Дерево вызовов при вылете теста


Если при вылете теста хочется увидеть trace вызовов, есть специальный параметр --print-calltrace, выводящий на экран дерево вызовов от места вылета к началу. При этом есть специальный параметр, ограничивающий глубину --print-calltrace-limit N

Примерно так это выглядит:



Шаблоны тестов


Безусловно, «шаблоны» это удобная вещь. И uniset2-testsuite тоже есть простая реализация этого механизма. В данном случае, речь идёт о простом способе автоматической замены «одного» на «другое». Например, пишем:

...
    <test name="Check [MyVariable]" lname="tname">
        ....
        <check test="[MyVariable]=[MyValue]"/>
    </test>
...

А потом хотим вызывать этот тест с различными параметрами в качестве MyVariable, MyValue. Легко…

...
    <test name="Check Name1=100">
        <check test="outlink" file="my-template-test.xml" link="lname=tname" replace="[MyVariable]:Name1,[MyValue]:100"/>
    </test>
    <test name="Check Name2=200">
        <check test="outlink" file="my-template-test.xml" link="lname=tname" replace="[MyVariable]:Name2,[MyValue]:200"/>
    </test>
...

Правила replace имеют три зоны видимости:

  • глобальная — действует на протяжении всего тестирования
  • уровень теста — действует на уровне конкретного теста
  • уровень проверки (link или outlink) — действует на уровне вызова link или outlink

Глобальные replace прописываются в секции TestList

<TestList replace="[MyVariable]:Name2,[MyValue]:200"

Уровень теста прописывается в секции test и действует только на данный тест и все link, outlink которые используются внутри него.

<test name="MySuperTes  replace="[MyVariable]:Name2,[MyValue]:200">
    <check test="[MyVariable]=11"/>
    <check test="Othrer=[MyValue]"/>
    <check test="outlink" file="my-template-test.xml" link="lname=tname"/>
</test>

Уровень конкретной проверки прописывается в секции check и действует только на конкретный вызов link или outlink теста

    <test name="Check Name2=200">
        <check test="outlink" file="my-template-test.xml" link="lname=tname" replace="[MyVariable]:Name2,[MyValue]:200" />
    </test>

Хочется заметить, что данный механизм замен (replace) действует не только на теги action, check, set, test, но так же и на названия тестов, их параметры и даже свойства replace во вложенных тестах.

Ну и под конец то, о чём не рассказал:

  • отчёты в формате junit
  • сохранение отчёта в файл
  • использование нескольких типов интерфейсов в одном сценарии
  • «compare» — сравнение параметров между собой, а не с числом
  • игнорирование тестов при исполнении
  • Как это ни странно, вместо тысячи слов, приведу ещё и help программы
    help
    Usage: uniset2-testsuite-xmlplayer [--confile [configure.xml|alias@conf1.xml,alias2@conf2.xml,..]  --testfile scenario.xml [..other options..]
    
    --testfile tests.xml                     - Test scenarion file.
    
    --test-name test1,prop2=test2,prop3=test3,...  - Run tests from list. By default prop=name
    --ignore-run-list                - Ignore <RunList>
    --ignore-nodes                   - Do not use '@node' or do not check node available for check scenario mode
    --default-timeout msec           - Default <check timeout='..' ../>.'
    --default-check-pause msec       - Default <check check_pause='..' ../>.'
    --print-calltrace                - Display test call trace with test file name. If test-suite FAILED.
    --print-calltrace-limit N        - How many recent calls to print. Default: 20.
    --supplier-name name             - ObjectName for testsuite under which the value is stored in the SM. Default: AdminID.
    
    --check-scenario                 - Enable 'check scenario mode'. Ignore for all tests result. Only check parameters
    --check-scenario-ignore-failed   - Enable 'check scenario mode'. Ignore for all tests result and checks
    --play-tags '#tag1#tag2#tag3..'  - Play tests only with the specified tag
    --show-test-tree                 - Show tree of tests
    --hide-result-report             - Hide result report
    
    --confile [conf.xml,alias1@conf.xml,..]  - Configuration file for uniset test scenario.
    
    TestSuiteConsoleReporter (--log)
    --------------------------------------------
    --log-show-tests              - Show tests log
    --log-show-actions            - Show actions log
    --log-show-result-only        - Show only result report (Ignore [show-actions,show-tests])
    --log-show-comments           - Display all comments (test,check,action)
    --log-show-numline            - Display line numbers
    --log-show-timestamp          - Display the time
    --log-show-test-filename      - Display test filename in test tree
    --log-show-test-comment       - Display test comment
    --log-show-test-type          - Display the test test type
    --log-hide-time               - Hide elasped time
    --log-hide-msec               - Hide milliseconds
    --log-col-comment-width val   - Width for column "comment"
    --log-no-coloring-output      - Disable colorization output
    --log-calltrace-disable-extended-info - Disable show calltrace extended information
    
    TestSuiteLogFileReporter (--logfile)
    --------------------------------------------
    --logfile-name filename  - Save log to file
    --logfile-trunc          - Truncate logile
    --logfile-flush          - flush every write
    
    TestSuiteJUnitReporter (--junit)
    --------------------------------------------
    --junit-filename name  - Save junit report to file
    --junit-deep val       - Deep tree of test for report. '-1' - all tests
    

Во второй части рассмотрим как написать свой интерфейс для проверки.

Tags:
Hubs:
+10
Comments 4
Comments Comments 4

Articles