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



    Как естественное продолжение работы над 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
      

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

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 4

      +2
      А почему не YAML (ну на крайняк JSON)? Какой кайф от XML то?
        0
        Частично ответил ниже…
        Но в целом, XML был выбран по историческим причинам (с ним уже давно велась работа во всех проектах), поэтому тянуть в проект ещё один формат было излишне. А поскольку велосипед «внутренний» то и «заказа» на другой формат пока не поступало :)
        +1

        Статья, безусловно, заслуживает своего плюса. Но насчёт самого фреймворка уже не уверен, взял ли бы я его в дело. Требовать от пользователя писать код тестов на XML — это довольно-таки жестоко. Тем более, что существуют хорошие альтернативные движки тестов. Например, тот же Robot Framework. Опыт нескольких лет работы с ним оставил приятные воспоминания. Шустрый, стабильный, развивающийся, качественно документированный, достаточно удобный.


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

          0
          >> Требовать от пользователя писать код тестов на XML — это довольно-таки жестоко.
          :) На самом деле (на практике) писать xml не составляет труда, особенно когда у Вас уже будут наработанные шаблоны. Но и писать сценарий в виде xml не обязательно. Это вопрос решаемый.
          Либо пишется свой «плеер», либо вариант конвертера из своего формата в xml-ку.
          У меня даже была проба второго варианта Упрощённый синтаксис

          Думаю после прочтения второй части статьи будет более полная картина.

          P.S. Robot Framework обязательно поизучаю. Интересно…

        Only users with full accounts can post comments. Log in, please.