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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Тестирование давно стало неотъемлемой частью разработки 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/


Интересные статьи

Интересные статьи

Канальный уровень OpenUNB сильно отличается от всего, что мы видели. Так получилось из-за заложенной в OpenUNB возможности только односторонней передачи: от оконечного устройства к сети. ...
Возможность интеграции с «1С» — это ключевое преимущество «1С-Битрикс» для всех, кто профессионально занимается продажами в интернете, особенно для масштабных интернет-магазинов.
Доброго времени суток, Хабровчане! Хочу рассказать о том, как я недавно узнал о неких "хуках" в React. Появились они относительно недавно, в версии [16.8.0] от 6 февраля 2019 года (что по скор...
На мой взгляд, одним из главных минусов homebridge является отсутсвие возможности создавать продвинутые сценарии. Вся автоматизация возлагается на домашний центр, которым может быть Ipad (подключ...
Рис. 1. – Внешний вид 4-х ступенчатого термоакустического двигателя с бегущей волной В предыдущих статьях я писал о том, как построить двигатель Стирлинга без поршней, то есть о том, как пос...