Параметризация тестов на Robot Framework
Введение
В этой статье вы можете изучить основы параметризации тестов на Robot Framework.
Предполагается, что вы владеете материалом статьи
«Шаблоны в Robot Framework»
Официальные рекомендации по структуре проектов можно изучить
здесь
В этой статье будет использоваться сокращение КС - которое означает Ключевое Слово/ Ключевые Слова иначе говоря
Keyword / Keywords
Аналог этой статьи для
PyTest
можно изучить
здесь
Решение проблемы с видимостью модулей -
здесь
КС - Keyword
Трехзначные числа
Рассмотрим пример параметризации тестов с помощью шаблонов . Структура проекта имеет следующий оригинальный вид.
filter_three/ ├── three_digit.py └── tests └── robot └── test_app.py
Функция filter_three() и модуля app.py возвращает True если число трёхзначное.
def filter_three(x: int) -> bool: return x in range(100, 1000)
*** Settings *** Library ../../three_digit.py Test Template Validate Number *** Test Cases *** number expected_result Digit 8 ${False} Two Digit Number 99 ${False} Lower Border 100 ${True} Three Digit Number 101 ${True} Upper Border 999 ${True} Four Digit Number 1000 ${False} *** Keywords *** Validate Number [Arguments] ${number} ${expected} ${res}= Filter Three ${number} Should Be Equal ${res} ${expected}
python -m robot .\tests\robot\test_filter_three.robot
============================================================================== Test Filter Three ============================================================================== Digit | PASS | ------------------------------------------------------------------------------ Two Digit Number | PASS | ------------------------------------------------------------------------------ Lower Border | PASS | ------------------------------------------------------------------------------ Three Digit Number | PASS | ------------------------------------------------------------------------------ Upper Border | PASS | ------------------------------------------------------------------------------ Four Digit Number | PASS | ------------------------------------------------------------------------------ Test Filter Three | PASS | 6 tests, 6 passed, 0 failed ==============================================================================
Умножение двух чисел
Рассмотрим параметризацию простого умножения двух чиселю
Структура проекта:
parametrize/ ├── prod │ ├── prod.py │ └── tests │ └── robot │ └── test_prod.robot └── venv
# prod.py def prod(a, b): return a * b
Запускать тесты будем из директории prod командой
python -m robot .\tests\robot\test_prod.robot
*** Settings *** Library ../../prod.py Test Template Validate Prod *** Test Cases *** arg1 arg2 expected_result Zero Zero ${0} ${0} ${0} Positive Negative ${7} ${-8} ${-56} Float Positive Positive ${13.0} ${14} ${182} *** Keywords *** Validate Prod [Arguments] ${arg1} ${arg2} ${expected_result} ${product}= Prod ${arg1} ${arg2} Should Be Equal ${product} ${expected_result}
============================================================================== Test Prod ============================================================================== Zero Zero | PASS | ------------------------------------------------------------------------------ Positive Negative | PASS | ------------------------------------------------------------------------------ Float Positive Positive | PASS | ------------------------------------------------------------------------------ Test Prod | PASS | 3 tests, 3 passed, 0 failed ==============================================================================
Квадратное уравнение
Структура проекта
quad/ ├── quadratic_lists.py └── tests └── robot └── test_quadratic_lists.robot
Возьмём
отсюда скрипт
, который решает квадратное уравнение.
Изменим тип возвращаемого значения с
кортежа
на
список
# quadratic_lists.py from math import sqrt def quadratic_solve(a, b, c) -> list: if not all( map( lambda p: isinstance(p, (int, float)), (a, b, c) ) ): raise TypeError(TYPE_ERROR_TEXT) print("Types are OK") if a == 0: if b == 0: # a и b 0: решения нет return [None, None] return [-c / b, None] d = b ** 2 - 4 * a * c if d < 0: return [None, None] d_root = sqrt(d) divider = 2 * a x1 = (-b + d_root) / divider x2 = (-b - d_root) / divider if d == 0: x2 = None elif x2 > x1: x1, x2 = x2, x1 return [x1, x2]
Параметризация будет осуществляться с помощью шаблонов
# test_quadratic_lists.robot *** Settings *** Library ../../quadratic_lists.py Test Template Verify Solution *** Variables *** @{roots1}= ${4.0} ${-1.0} @{roots2}= ${1.0} ${None} @{roots3}= ${None} ${None} *** Test Cases *** a b c expected_result Two Roots ${1} ${-3} ${-4} ${roots1} Single Root ${1} ${-2} ${1} ${roots2} No Roots ${0} ${0} ${0} ${roots3} *** Keywords *** Verify Solution [Arguments] ${a} ${b} ${c} ${expected} ${res}= Quadratic Solve ${a} ${b} ${c} Should Be Equal ${res} ${expected}
python -m robot .\tests\robot\test_quadratic_lists.robot
============================================================================== Test Quadratic Lists ============================================================================== Two Roots | PASS | ------------------------------------------------------------------------------ Single Root | PASS | ------------------------------------------------------------------------------ No Roots | PASS | ------------------------------------------------------------------------------ Test Quadratic Lists | PASS | 3 tests, 3 passed, 0 failed ==============================================================================
Теперь вернём исходный тип возращаемого значения - кортеж в скрипте с решением квадратного уравнения
quad/ ├── quadratic.py └── tests └── robot └── test_quadratic.robot
Теперь роботу нужно приспособиться к кортежам
*** Settings *** Library ../../quadratic.py Test Template Verify Solution Suite Setup Prepare Variables *** Variables *** @{roots1}= ${4.0} ${-1.0} @{roots2}= ${1.0} ${None} @{roots3}= ${None} ${None} *** Test Cases *** a b c expected_result Two Roots ${1} ${-3} ${-4} ${ex_res1} Single Root ${1} ${-2} ${1} ${ex_res2} No Roots ${0} ${0} ${0} ${ex_res3} *** Keywords *** Verify Solution [Arguments] ${a} ${b} ${c} ${expected} ${res}= Quadratic Solve ${a} ${b} ${c} Should Be Equal ${res} ${expected} Convert List To Tuple [Arguments] ${list} ${tuple}= Evaluate tuple(${list}) RETURN ${tuple} Prepare Variables ${tup1}= Convert List To Tuple ${roots1} ${tup2}= Convert List To Tuple ${roots2} ${tup3}= Convert List To Tuple ${roots3} Set Suite Variable ${ex_res1} ${tup1} Set Suite Variable ${ex_res2} ${tup2} Set Suite Variable ${ex_res3} ${tup3}
Проверка списка неизвестной длины
Функция get_digits() из модуля list_of_digits.py
должна возвращать произвольный набор цифр.
Повторы разрешены.
Цифры это 0, 1, 2… 9.
Функция написана с ошибкой, но её нелегко поймать одиночным тестом.
Это самый важный пример данной статьи. До этого мы просто использовали
Template
с ограниченным количеством тестов.
Теперь мы параметризуем проверку списка заранее неизвестной длины. То есть мы не знаем сколько будет тестов. Это зависит от
входных данных, которые мы не контролируем.
Структура проекта, которую хотелось бы иметь
list_of_digits/ ├── list_of_digits.py └── tests └── robot └── test_list_of_digits.py
# list_of_digits.py import random def get_digits() -> list: n = random.randint(1, 10) result = [] for i in range(0, n): result.append(random.randint(4, 10)) return result
Как адекватно решить эту задачу на роботе пока непонятно. Ниже предлагаю решение с использованием
DataDriver
.
Суть в том, что полученный список сохраняется как .csv файл и затем с помощью DataDriver
прогоняются отдельные тесты на каждую строку.
Установить его нужно отдельно, так как в стандартную библиотеку робота он не входит.
python -m pip install robotframework-datadriver
Структура проекта теперь выглядит следующим оригинальным образом. После запуска теста рядом с list_of_digits.py будет создан файл UserData.csv
list_of_digits/ ├── list_of_digits.py └── tests └── robot ├── libraries │ └── save_to_csv.py └── test_list_of_digits.py
# save_to_csv.py def save_list_to_csv(list_of_digits): with open("UserData.csv", "w") as f: f.write("*** Test Cases ***;${var}") for digit in list_of_digits: with open("UserData.csv", "a") as f: f.writelines("\n") f.writelines(";") f.writelines(str(digit))
# test_list_of_digits.py *** Settings *** Library DataDriver ../../UserData.csv Library ../../list_of_digits.py Library libraries/save_to_csv.py Suite Setup Prepare Variables Test Template Variable Is Digit *** Test Cases *** Test ${var} *** Keywords *** Prepare Variables ${list_of_digits}= Get Digits Save List To Csv ${list_of_digits} Variable Is Digit [Arguments] ${var} ${num}= Convert To Integer ${var} Should Be True ${num} >= 0 Should Be True ${num} < 10
python -m robot .\tests\robot\test_list_of_digits.robot
============================================================================== Test List Of Digits ============================================================================== Test 4 | PASS | ------------------------------------------------------------------------------ Test 8 | PASS | ------------------------------------------------------------------------------ Test 1 | PASS | ------------------------------------------------------------------------------ Test 5 | PASS | ------------------------------------------------------------------------------ [ WARN ] Multiple tests with name 'Test 4' executed in suite 'Test List Of Digits'. Test 4 | PASS | ------------------------------------------------------------------------------ Test List Of Digits | PASS | 5 tests, 5 passed, 0 failed ==============================================================================
============================================================================== Test List Of Digits ============================================================================== Test 2 | PASS | ------------------------------------------------------------------------------ Test 10 | FAIL | '10 < 10' should be true. ------------------------------------------------------------------------------ [ WARN ] Multiple tests with name 'Test 2' executed in suite 'Test List Of Digits'. Test 2 | PASS | ------------------------------------------------------------------------------ Test 9 | PASS | ------------------------------------------------------------------------------ Test List Of Digits | FAIL | 3 tests, 2 passed, 1 failed ==============================================================================
Первый тест-ран состоял из пяти тестов и не нашёл ошибок. Повторный тест-ран хоть и состоял всего из трёх тестов, но был удачливее и выловил ошибку уже на втором тесте.
Уникальные имена тест-кейсов
В логах периодически появляются предупреждения вида
[ WARN ] Multiple tests with name 'Test X' executed in suite 'Test List Of Digits'.
Названия тест-кейса состоит только из слова Test и переменной ${var}, которая может повторяеться.
*** Test Cases *** Test ${var}
Поэтому если в списке будет две двойки - мы получим два тест-кейса
Test 2
Исправить это можно изменив код
save_to_csv.py
# save_to_csv.py def save_list_to_csv(list_of_digits): with open("UserData.csv", "w") as f: f.write("*** Test Cases ***;${var}") i = 0 for digit in list_of_digits: i += 1 with open("UserData.csv", "a") as f: f.writelines("\n") f.writelines("Test-" + str(i) + ". Digit: " + str(digit)) f.writelines(";") f.writelines(str(digit))
python -m robot .\tests\robot\test_list_of_digits.robot
============================================================================== Test List Of Digits ============================================================================== Test-1. Digit: 5 | PASS | ------------------------------------------------------------------------------ Test-2. Digit: 9 | PASS | ------------------------------------------------------------------------------ Test-3. Digit: 4 | PASS | ------------------------------------------------------------------------------ Test-4. Digit: 5 | PASS | ------------------------------------------------------------------------------ Test-5. Digit: 9 | PASS | ------------------------------------------------------------------------------ Test List Of Digits | PASS | 5 tests, 5 passed, 0 failed ==============================================================================
В этом тест-ране был повтор пятёрки и девятки, но так как теперь у имён тест-кейсов есть индекс,
повторы невозможны
Если вы хотите углубить знания о подготовке тестовых данных - изучите статью про
DataDriver
Проверка сайта
Практический пример применения параметризации для проверки ответов страниц сайта.
Задача проверить все ли страницы сайта heihei.ru
отвечают HTTP кодом 200.
Структура проекта:
from_site_map/ └── tests └── robot ├── libraries │ └── save_to_csv.py └── test_site_map.robot
Мой хостинг Beget.com бесплатно создаёт карту сайта. Я скачиваю её в родительскую директорию к tests
from_site_map/ ├── sitemap.xml └── tests └── robot ├── libraries │ └── save_to_csv.py └── test_site_map.robot
Выглядит карта сайта следующим оригинальным образом:
<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://heihei.ru/Spain/cities/malaga/</loc> <priority>0.8</priority> <changefreq>daily</changefreq> </url> <url> <loc>https://heihei.ru/Finland/destinations/</loc> <priority>0.6</priority> <changefreq>daily</changefreq> </url> </urlset>
Только страниц там гораздо больше.
Для парсинга карты сайта будем использовать бибилиотеку
XML
а для обращения к сайту - библиотеку
RequestsLibrary
Подготовим тестовое окружение
python -m venv venv
venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install robotframework robotframework-requests robotframework-datadriver
*** Settings *** Library XML Library RequestsLibrary Library Collections Library DataDriver ../../UserData.csv Library libraries/save_to_csv.py Suite Setup Prepare Variables Test Template Status Is OK *** Variables *** ${XML}= sitemap.xml @{urls} *** Test Cases *** Test ${url} *** Keywords *** Prepare Variables ${urls}= Get Urls Save List To Csv ${urls} Status Is OK [Arguments] ${url} ${response}= GET ${url} expected_status=200 allow_redirects=${False} Get Urls @{children} = Get Elements ${XML} */loc FOR ${child} IN @{children} Append To List ${urls} ${child.text} END RETURN ${urls}
# save_to_csv.py def save_list_to_csv(list_of_urls, file_name="Urls.csv"): with open(file_name, "w") as f: f.write("*** Test Cases ***;${url}") i = 0 for url in list_of_urls: i += 1 with open(file_name, "a") as f: f.writelines("\n") f.writelines("Test-" + str(i) + ". URL: " + str(url)) f.writelines(";") f.writelines(str(url))
python -m robot .\tests\robot\test_site_map.robot
============================================================================== Test Site Map ============================================================================== ------------------------------------------------------------------------------ Test-1. URL: https://heihei.ru/Spain/cities/cordoba/ | PASS | ------------------------------------------------------------------------------ Test-2. URL: https://heihei.ru/Finland/holidays/easter.php | PASS | …
Решение возможных проблем
Если Python не находит модуль с кодом, можно попробовать перейти в директорию
родительскую по отношению к tests
from_site_map и добавить
её в системный путь.
В
PowerShell
команда будет выглядеть так:
$Env:Path += ";$pwd"
В Bash немного по-другому.
export PATH=$PATH:$(pwd)
Автор статьи: Андрей Олегович
| Robot Framework | |
| Архитектура | |
| Логи | |
| __init__.robot | |
| Подключение своих .py библиотек | |
| Путь до библиотек и ресурсов | |
| Keyword как декоратор | |
| Параметризация | |
| Template | |
| Пример с pywinauto |