Тестирование устройств с помощью Robot Framework

от автора

Тестирование давно стало неотъемлемой частью разработки software-продуктов, но в мире hardware-решений, на моём опыте, дела обстоят несколько хуже: зачастую тестирование ограничивается ручной проверкой функционала. Современные устройства обычно содержат процессоры или микроконтроллеры, поэтому источником ошибок в embedded-девайсах является не только схемотехника, но и прошивки. Поскольку каждое обновление прошивки или внесение изменений в схему создаёт риск добавления новых багов, разумным решением было бы автоматизировать процесс тестирования. В данной статье рассмотрен один из инструментов, который можно использовать для автоматизации тестирования регрессий и новых функций в сфере embedded.

Robot Framework

Robot Framework (далее — Robot) — инструмент для автоматизированного тестирования, написан на Python. Robot используется для автоматического тестирования в разных областях разработки: от web-фронтенда и бекенда до embedded-устройств. По умолчанию Robot предоставляет базовый набор функций (например, работу со строками, возможность запуска внешних программ, подключение по Telnet), но с помощью внешних библиотек возможности фреймворка могут быть значительно расширены.

В данной статье речь пойдёт о применении Robot для тестирования простого embedded-устройства с применением встроенных функций, с использованием сторонней библиотеки, а также с помощью кастомной библиотеки на Python. В качестве среды для запуска Robot будет использоваться RaspberryPI 4B (далее — rPi) с установленной Raspberry Pi OS. Эта платформа была выбрана для демонстрации, т.к. rPi содержит встроенный Bluetooth-адаптер и внешние GPIO-пины, удобные для тестирования устройств. Тестируемое устройство — плата DOIT ESP32S Devkit V1 с прошивкой на Arduino.

Тестируемое устройство

В качестве примера рассмотрим простое устройство, реализованное на отладочной плате DOIT ESP32S Devkit V1 — она построена на SoC ESP32 со следующими характеристиками:

  • 32-разрядный CPU с тактовой частотой до 240 МГц

  • Bluetooth v4.2

  • UART

  • Внешние GPIO x 34

  • Wi-Fi 802.11b/g/n и много другой периферии (PWM, SPI, DAC, ADC, …), которая не используется в данной статье

Для примера был создан Arduino-скетч, работающий следующим образом: ESP32 принимает данные по UART, при получении символа «1» контроллер зажигает светодиод (пин D2); при получении символа «0» светодиод отключается. Также в прошивке реализован BLE GATT-сервис с одной read-only характеристикой, которая содержит текущее состояние светодиода (подробнее про BLE GATT-сервисы можно прочитать здесь. Таким образом, управление светодиодом осуществляется через UART, а с помощью BLE можно узнать текущее состояние светодиода.

Исходный код скетча
/**  * The sketch cotains two main parts:  * - Serial console for controlling LED  * - BLE GATT service  */ #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h>  #define SERVICE_UUID        "0fc04e02-c8f4-4d51-aad6-35c5db47c27e" #define CHARACTERISTIC_UUID "ece27bad-3d4b-4072-8494-76a551f0b6cc"  const uint8_t blue_led = 2; //LED pin on ESP32 DEVKIT V1 DOIT board uint8_t led_status = '0'; uint8_t char_value = '0';  BLEServer* pServer = NULL; BLEService *pService = NULL; BLECharacteristic* pCharacteristic = NULL; bool deviceConnected = false; bool oldDeviceConnected = false;  class MyServerCallbacks: public BLEServerCallbacks {     void onConnect(BLEServer* pServer) {       deviceConnected = true;     };      void onDisconnect(BLEServer* pServer) {       deviceConnected = false;     } };  void console_routine(void) {   if (Serial.available() > 0) {     char inByte = Serial.read();     if (inByte == '0') {       digitalWrite(blue_led, LOW); //turn LED off       led_status = '0';     }     if (inByte == '1') {       digitalWrite(blue_led, HIGH); //turn LED on       led_status = '1';     }   } }  void ble_connection_routine(void) {     // notify changed value     if (deviceConnected) {         if (char_value != led_status) {           char_value = led_status;                     pCharacteristic->setValue((uint8_t*)&char_value, 1);         }         delay(3);     }     // disconnecting     if (!deviceConnected && oldDeviceConnected) {         delay(500); // give the bluetooth stack the chance to get things ready         pServer->startAdvertising(); // restart advertising         oldDeviceConnected = deviceConnected;     }     // connecting     if (deviceConnected && !oldDeviceConnected) {         oldDeviceConnected = deviceConnected;     } }  void setup() {   pinMode(blue_led, OUTPUT);   Serial.begin(115200);   while (!Serial) {   }    BLEDevice::init("ESP32_LED_STATUS");   pServer = BLEDevice::createServer();   pServer->setCallbacks(new MyServerCallbacks());      pService = pServer->createService(SERVICE_UUID);      pCharacteristic = pService->createCharacteristic(                                          CHARACTERISTIC_UUID,                                          BLECharacteristic::PROPERTY_READ);    pCharacteristic->setValue((uint8_t*)&char_value, 1);   pService->start();   BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();   pAdvertising->addServiceUUID(SERVICE_UUID);   pAdvertising->setScanResponse(false);   pAdvertising->setMinPreferred(0x0);   BLEDevice::startAdvertising(); }  void loop() {   console_routine();   ble_connection_routine(); }

Схема подключения плат в тестовом стенде:

Установка Robot

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

pip3 install robotframework robotframework-seriallibrary pygatt

Также для работы с внешними пинами понадобится утилита wiringpi, которую можно установить из репозитория Raspberry Pi OS:

sudo apt install wiringpi

Пример теста в Robot

Перед тем, как перейти к написанию тестов для нашего девайса, рассмотрим как выглядят тест-кейсы в Robot в целом. Все тесты содержатся в файлах с расширением .robot, причём в одном файле можно написать сразу несколько тест-кейсов, объединённых в один тест-сьют. В файле .robot есть несколько секций, названия секций выделяются тремя знаками *. В наших примерах будут использоваться секции: *** Settings *** (в ней подключаются внешние библиотеки и задаются настройки тестов), *** Variables *** (для объявления переменных), *** Keywords *** (для создания собственных ключевых слов, которые затем можно использовать в тестах) и *** Test Cases *** (для написания тест-кейсов). Более подробное описание секций и другую информацию об Robot можно найти в официальном User Guide.

Каждый тест-кейс состоит из набора ключевых слов, которые запускаются последовательно друг за другом. Некоторые ключевые слова принимают на вход аргументы — например, ключевое слово Log принимает один обязательный аргумент — message, который сохранится в log-файле по завершении теста. В качестве разделителя имени ключевого слова и аргумента в Robot могут использоваться:

  • несколько пробелов подряд (от двух и больше). В примерах в данной статье в качестве разделителя используются 4 пробела подряд

  • символ табуляции

  • вертикальная черта

Некоторые ключевые слова возвращают значение, которое можно записать в переменную. Например, ключевое слово Set Variable используется для записи аргумента в указанную переменную ${hello_world}=    Set Variable    Hello.

Ключевые слова могут завершаться успешно (Success) и неуспешно (Fail). В случае, если ключевое слово завершилось неуспешно, тест-кейс, в котором произошёл вызов ключевого слова, так же завершается с результатом Fail. Например, ключевое слово Should Be Equal As Strings сравнивает строки, и возвращает Fail, если они не равны.

В Robot присутствует большое количество встроенных ключевых слов, также существует возможность создавать собственные ключевые слова в разделе *** Keywords ***.

Рассмотрим следующий тест-сьют в качестве примера:

*** Settings *** Suite Setup    Log    Suite setup           # Запускается перед тест-сьютом Suite Teardown    Log    Suite teardown     # Запускается после тест-сьюта Test Setup    Log    Test setup             # Запускается перед тест-кейсом Test Teardown    Log    Test teardown       # Запускается после тест-кейса  *** Test Cases *** Test Case Pass Example    # Пример успешного тест-кейса     ${hello_world}=    Set Variable    Hello    # Встроенное ключевое слово Set Variable для создания переменной     ${hello_world}=    Add Word To String  ${hello_world}  world    # Кастомное ключевое слово для добавления слова к строке     Should Be Equal As Strings  ${hello_world}    Hello world    # Сравнение строк, в данном случае возвращает Pass  Test Case Fail Example    # Пример зафейленного тест-кейса     ${hello_world}=    Set Variable    Goodbye     ${hello_world}=    Add Word To String  ${hello_world}  world     Should Be Equal As Strings  ${hello_world}    Hello world    # Сравнение строк, в данном случае возвращает Fail  *** Keywords *** Add Word To String    # Новое ключевое слово     [Arguments]    ${string}    ${word}    # Принимает на входе два аргумента     ${string}=  Catenate    ${string}   ${word}    # Встроенное ключевое слово для соединения строк     [Return]    ${string}    # Возвращает строку с добавленным словом

Для запуска данного тест-сьюта надо сохранить его в отдельный файл и запустить следующей командой:

robot simple_test.robot

Как можно увидеть, Test Case Pass Example завершился успешно, а Test Case Fail Example завершился неуспешно. Узнать подробнее, на каком ключевом слове тест-кейс завершился неудачей, можно, посмотрев лог-файл simple_test.log:

Как видно из лог-файла, ключевое слово Should Be Equal As Strings во втором тест-кейсе завершилось с результатом Fail, т.к. переменная ${hello_world} не равна Hello world.

Также в лог-файле можно увидеть, что при запуске тест-сьюта помимо исполнения ключевых слов, описанных в секции *** Test Cases *** вызывались ключевые слова из секции *** Settings ***: Suite Setup и Suite Teardown вызываются в начале и в конце тест-сьюта соответственно, а Test Setup и Test Teardown в начале и в конце каждого тест-кейса соответственно. Секция Setup используется для предварительных настроек необходимых для каждого тест-кейса, а Teardown для операций, которые необходимо выполнять в конце теста независимо от его результата (например, для закрытия программ запущенных внутри теста, или для удаления временных файлов).

Пишем тесты для embedded-устройства

Для проверки функций, реализованных в описанном выше скетче, напишем два тест-кейса: Test LED Switch On и Test LED Switch Off. Каждый из данных тест-кейсов будет содержать следующий сценарий:

  • отправка команды для включения/отключения LED («1» в первом случае, и «0» во втором) по UART (через USB)

  • проверка состояния светодиода с помощью чтения линии GPIO.0 на rPi

  • проверка значения GATT-характеристики с помощью BLE

Используем готовые библиотеки

Для начала напишем основу теста с использованием сторонних (будем использовать стороннюю библиотеку SerialLibrary для отправки команды по UART) и встроенных библиотек (будем использовать встроенную библиотеку Process для запуска утилиты gpio, которая позволяет читать состояние пина rPi).

Для включения/отключения светодиода нам необходимо создать ключевые слова, которые отправляют «1» либо «0» по UART. Для этого сначала необходимо в секции *** Settings *** подключить библиотеку SerialLibrary (Library    SerialLibrary), а также вызвать на этапе Test Setup ключевое слово для конфигурирования последовательного порта (Open Serial Port). Непосредственно включение и отключение светодиода будет производиться ключевыми словами Turn LED On и Turn LED Off, внутри которых происходит вызов Write Data, реализованного в библиотеке SerialLibrary.

Для проверки состояния светодиода создадим ключевое слово, внутри которого происходит вызов утилиты gpio. Для запуска внешних программ в Robot есть встроенная библиотека Process. Для нашей задачи подойдёт ключевое слово Run Process, которое принимает на вход название вызываемой программы и передаваемые аргументы, а возвращает результат выполнения команды, в т.ч. вывод программы в stdout. Для чтения состояния пина GPIO.0 нужно вызвать gpio read 0, после чего проверить, вывела программа «1» или «0». Для сравнения состояния пина с ожидаемым будем использовать ключевое слово Should Be Equal As Integers.

В итоге у нас получился следующий тест-сьют:

*** Settings *** Library    Process    #built-in library Library    SerialLibrary    #external 3rd-party library  Test Setup    Run Keywords ...           Open Serial Port  Test Teardown    Delete All Ports  *** Variables *** ${esp32_dev_path}    /dev/ttyUSB0    #dev path for serial communication  *** Test Cases ***  Test LED Switch Off     Turn LED Off     ${led_state_gpio}=    Get LED State     Should Be Equal As Integers    ${led_state_gpio}    0  Test LED Switch On     Turn LED On     ${led_state_gpio}=    Get LED State     Should Be Equal As Integers    ${led_state_gpio}    1  *** Keywords *** Turn LED On     Write Data    0x31    #0x31 is ascii '1'  Turn LED Off     Write Data    0x30    #0x31 is ascii '0'  Get LED State     ${result}=    Run Process    gpio    read    0     Log    all output: ${result.stdout}     [Return]    ${result.stdout}  Open Serial Port     Add Port   ${esp32_dev_path}     ...        baudrate=115200     ...        bytesize=8     ...        parity=N     ...        stopbits=1     ...        timeout=999

Пишем собственную библиотеку

В скетче реализован BLE GATT-сервис с одной характеристикой, доступной для чтения. Данная характеристика содержит текущее состояние светодиода: «1» если светодиод включен и «0» если он выключен. UUID характеристики был сгенерирован случайным образом и захардкожен в скетче — ece27bad-3d4b-4072-8494-76a551f0b6cc.

Дополним тест чтением характеристики через BLE. В Robot существует возможность создавать собственные библиотеки для расширения функционала. Чтобы не писать весь функционал BLE с нуля, воспользуемся готовым модулем pygatt и напишем к нему небольшую надстройку на Python, в которой реализуем класс с единственной функцией — чтение характеристики BLE-устройства. В качестве аргументов будем передавать данной функции адрес устройства и UUID характеристики. Сама по себе функция достаточно примитивна, так как является надстройкой над pygatt, и производит следующую последовательность операций: подключается в BLE-адаптеру через последовательный порт (в rPi 4B есть встроенный Bluetooth-адаптер), открывает Bluetooth-соединение с удалённым устройством, читает значение характеристики и возвращает его наружу.

Исходный код библиотеки:

import pygatt.backends  class BluetoothTesting:          def read_char_value(self, mac, char_uuid):         adapter = pygatt.backends.GATTToolBackend()         try:             adapter.start()    #open adapter             device = adapter.connect(mac)    #connect to device via gatt             value = device.char_read(char_uuid)    #read characteristic with the given uuid             return value    #return characteristic         finally:             adapter.stop()

После подключения данной библиотеки (с помощью Library    BluetoothTesting.py) Robot автоматически создаст ключевое слово Read Char Value (при его вызове фактически вызывается функция read_char_value()). При этом данное ключевое слово принимает на входе два аргумента: ${mac} и ${char_uuid} и возвращает значение, прочитанное из GATT-характеристики. Теперь можно дополнить созданный ранее тест-сьют проверкой BLE-характеристики:

*** Settings *** Library    Process    #built-in library Library    SerialLibrary    #external 3rd-party library Library    BluetoothTesting.py    #custom library  Test Setup    Run Keywords ...           Open Serial Port  Test Teardown    Delete All Ports  *** Variables *** ${char_uuid}    ece27bad-3d4b-4072-8494-76a551f0b6cc        #BLE characteristic UUID ${ble_mac}    f0:08:d1:d5:0c:ae      #BLE device access address ${esp32_dev_path}    /dev/ttyUSB0    #dev path for serial communication  *** Test Cases ***  Test LED Switch Off     Turn LED Off     ${led_state_gpio}=    Get LED State     Should Be Equal As Integers    ${led_state_gpio}    0     ${led_state_ble}=    Read Char Value    ${ble_mac}    ${char_uuid}     Should Be Equal As Integers    ${led_state_ble}    0  Test LED Switch On     Turn LED On     ${led_state_gpio}=    Get LED State     Should Be Equal As Integers    ${led_state_gpio}    1     ${led_state_ble}=    Read Char Value    ${ble_mac}    ${char_uuid}     Should Be Equal As Integers    ${led_state_ble}    1  *** Keywords *** Turn LED On     Write Data    0x31    #0x31 is ascii '1'  Turn LED Off     Write Data    0x30    #0x31 is ascii '0'  Get LED State     ${result}=    Run Process    gpio    read    0     Log    all output: ${result.stdout}     [Return]    ${result.stdout}  Open Serial Port     Add Port   ${esp32_dev_path}     ...        baudrate=115200     ...        bytesize=8     ...        parity=N     ...        stopbits=1     ...        timeout=999

Итоговый тест проверяет корректность работы устройства, а именно: отправляет по UART команду на включение, либо отключение светодиода, проверяет корректность состояния светодиода в BLE GATT-сервисе и проверяет фактическое состояние светодиода с помощью чтения состояния GPIO-пина. Оба написанных тест-кейса проходят успешно:

Заключение

Тестирование в сфере embedded-устройств несколько сложнее, чем тестирование чисто программных продуктов, т.к. требует создания отдельного тестового стенда для подключения к внешним интерфейсам тестируемого устройства. Но решение этих сложностей окупается уменьшением ошибок в выпускаемом продукте и возможностью контролировать качество в процессе разработки и поддержки устройства. За рамками данной статьи осталось множество вопросов, связанных с тестированием в embedded, например: автоматическое тестирование железа без цифровых интерфейсов, интеграция Robot Framework в Jenkins, подключение измерительных приборов к тестовому стенду и т. д. Но, надеюсь, что этот короткий обзор возможностей Robot-а поможет embedded-командам по крайней мере начать применять практики автоматизированного тестирования в своих разработках.

Ссылки

1. Robot Framework Documentation

2. Robot Framework User Guide

3. Pygatt library

4. SerialLibrary

5. Интересный доклад на тему тестирования Embedded с помощью Robot [eng]

ссылка на оригинал статьи https://habr.com/ru/post/566740/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *