第一次接触Robot Framework时,我被它那"关键字驱动"的简洁语法惊艳到了。作为一个常年与Python打交道的测试工程师,我发现这个开源框架完美解决了测试脚本维护难、可读性差的问题。Robot Framework本质上是一个通用的自动化测试框架,但它最强大的特性是能够无缝集成Python代码,让测试人员既能享受RF的关键字便利性,又能发挥Python的灵活性。
在实际项目中,我经常遇到这样的场景:业务测试用例需要频繁调整,但传统的Python单元测试脚本修改成本高、可读性差。而Robot Framework的表格化用例编写方式,让非技术人员也能理解测试逻辑。更重要的是,通过它的Library机制,我们可以将复杂的Python测试代码封装成简单的关键字,实现技术细节与业务逻辑的分离。
在开始之前,我们需要准备Python环境(建议3.7+版本)。通过pip可以一键安装核心包:
bash复制pip install robotframework
验证安装是否成功:
bash复制robot --version
注意:如果系统中有多个Python版本,请确保使用正确的pip版本。在Windows上,可能需要使用
py -3 -m pip install robotframework来指定Python3。
Robot Framework的强大之处在于其丰富的扩展库生态系统。以下是几个必备库:
bash复制pip install robotframework-seleniumlibrary # Web自动化
pip install robotframework-requests # API测试
pip install robotframework-databaselibrary # 数据库测试
对于自定义Python测试脚本的集成,我们还需要:
bash复制pip install robotframework-pythonlibcore
这个库提供了Python和RF之间的桥梁,允许我们将任何Python代码转换为RF关键字。
假设我们有一个Python测试脚本calculator.py,包含以下功能:
python复制class Calculator:
def add(self, a, b):
return float(a) + float(b)
def subtract(self, a, b):
return float(a) - float(b)
要使其成为RF可调用的关键字,我们需要创建一个Library类:
python复制from robot.api.deco import keyword
class CalculatorLibrary:
def __init__(self):
self.calc = Calculator()
@keyword
def add_numbers(self, a, b):
return self.calc.add(a, b)
@keyword
def subtract_numbers(self, a, b):
return self.calc.subtract(a, b)
关键点:
@keyword装饰器将Python方法标记为RF关键字创建测试文件calculator_tests.robot:
robotframework复制*** Settings ***
Library CalculatorLibrary
*** Test Cases ***
Addition Test
${result}= Add Numbers 5 3
Should Be Equal As Numbers ${result} 8
Subtraction Test
${result}= Subtract Numbers 10 4
Should Be Equal As Numbers ${result} 6
执行测试:
bash复制robot calculator_tests.robot
运行后会生成三个文件:
output.xml:机器可读的详细结果log.html:可视化测试日志report.html:测试摘要报告技巧:使用
--outputdir参数指定输出目录,保持项目整洁
对于需要运行时控制的测试场景,可以使用RF的动态库API:
python复制from robot.libraries.BuiltIn import BuiltIn
class DynamicLibrary:
def get_keyword_names(self):
return ['dynamic_keyword']
def run_keyword(self, name, args):
if name == 'dynamic_keyword':
return self._handle_dynamic(*args)
def _handle_dynamic(self, arg):
# 获取当前测试用例名称
test_name = BuiltIn().get_variable_value("${TEST NAME}")
return f"处理 {arg} 在 {test_name}"
监听器接口则允许我们在测试生命周期中注入逻辑:
python复制from robot.api import ListenerV3
class MyListener(ListenerV3):
def start_test(self, data, result):
print(f"测试开始: {result.name}")
def end_test(self, data, result):
print(f"测试结束: {result.status}")
Robot Framework原生支持多种数据驱动方式:
robotframework复制*** Test Cases ***
Template Example
[Template] Add Numbers
1 1 2
2 3 5
robotframework复制*** Keywords ***
Add Two Numbers
[Arguments] ${a} ${b} ${expected}
${result}= Add Numbers ${a} ${b}
Should Be Equal As Numbers ${result} ${expected}
*** Test Cases ***
DDT Example
[Template] Add Two Numbers
1 1 2
2 3 5
robotframework复制*** Settings ***
Library DataDriver file=data.csv
*** Test Cases ***
CSV Data Test
Add Two Numbers ${a} ${b} ${expected}
对于已经使用pytest的项目,可以通过robotframework-pytest插件实现无缝集成:
python复制# conftest.py
import pytest
from robot.api import TestSuite
@pytest.fixture
def robot_suite():
suite = TestSuite('Pytest Integration')
suite.resource.imports.library('CalculatorLibrary')
test = suite.tests.create('Pytest Test')
test.keywords.create('Add Numbers', args=['2', '3'])
return suite
def test_robot_in_pytest(robot_suite):
result = robot_suite.run(output='pytest_output.xml')
assert result.return_code == 0
python复制class OptimizedLibrary:
ROBOT_LIBRARY_SCOPE = 'GLOBAL' # 避免重复初始化
def __init__(self):
self._heavy_resource = self._init_heavy_resource()
def _init_heavy_resource(self):
# 耗时的初始化操作
time.sleep(5)
return "Resource"
bash复制robot --prerunmodifier MergeSuites --output merged.xml *.robot
bash复制pabot --processes 4 tests/
@keyword装饰器robot.libdoc生成库文档验证${GLOBAL VAR}${SUITE VAR}${TEST VAR}python复制from robot.api import logger
@keyword
def type_demo(self, arg):
logger.info(f"原始类型: {type(arg)}") # RF传入的都是字符串
number = float(arg) if arg.isdigit() else arg
return number
code复制project/
├── resources/
│ ├── common_keywords.robot
│ └── variables.robot
├── libraries/
│ └── custom_lib.py
├── tests/
│ ├── smoke/
│ └── regression/
└── outputs/
yaml复制# .github/workflows/robot.yml
name: Robot Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests
run: robot --outputdir results tests/
- name: Upload results
uses: actions/upload-artifact@v2
with:
name: robot-results
path: results/
python复制from robot.api import ResultWriter
def generate_custom_report(output):
result = output.suite
stats = result.statistics
with open('custom_report.md', 'w') as f:
f.write(f"# 测试报告\n\n")
f.write(f"- 通过: {stats.passed}\n")
f.write(f"- 失败: {stats.failed}\n")
for test in result.tests:
f.write(f"### {test.name}: {test.status}\n")
robotframework复制*** Keywords ***
Login To System
[Arguments] ${user} ${pass}
Input Text id=username ${user}
Input Text id=password ${pass}
Click Button login
robotframework复制*** Keywords ***
User Checkout Flow
[Arguments] ${user} ${item}
Login To System ${user.name} ${user.pass}
Search Product ${item}
Add To Cart
Checkout
robotframework复制*** Test Cases ***
Valid Checkout Test
User Checkout Flow ${VALID_USER} iPhone
对于微服务架构,可以创建服务代理层:
python复制import requests
from robot.api.deco import keyword
class OrderService:
def __init__(self, base_url):
self.base_url = base_url
@keyword
def create_order(self, items):
response = requests.post(
f"{self.base_url}/orders",
json={"items": items}
)
return response.json()
结合Python实现高级等待逻辑:
python复制from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from robot.api import logger
class SmartWaits:
def __init__(self, timeout=30):
self.timeout = timeout
@keyword
def wait_for_element(self, locator):
driver = self._get_driver()
try:
element = WebDriverWait(driver, self.timeout).until(
EC.presence_of_element_located(locator)
)
logger.info(f"元素 {locator} 找到")
return element
except Exception as e:
self._capture_screenshot()
raise AssertionError(f"等待元素超时: {locator}")
robotframework复制*** Variables ***
# 全局变量
${BROWSER} chrome
*** Settings ***
Variables env/${ENV}.py # 环境特定变量
prod.py:
python复制BASE_URL = "https://prod.example.com"
stage.py:
python复制BASE_URL = "https://stage.example.com"
bash复制robot --variable BROWSER:firefox --variable ENV:stage tests/
robotframework复制*** Settings ***
Variables test_data.json
*** Test Cases ***
Data Driven Test
Login To System ${USERNAME} ${PASSWORD}
test_data.json:
json复制{
"USERNAME": "testuser",
"PASSWORD": "secure123"
}
python复制import sqlite3
from robot.api.deco import keyword
class DBDataProvider:
@keyword
def get_user_credentials(self, role):
conn = sqlite3.connect('test_data.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE role=?", (role,))
return cursor.fetchone()
bash复制#!/bin/bash
DATE=$(date +%Y%m%d)
OUTPUT_DIR="results/$DATE"
mkdir -p $OUTPUT_DIR
robot --outputdir $OUTPUT_DIR tests/
python复制import json
from robot.api import ExecutionResult
def compare_runs(current, baseline):
current_stats = ExecutionResult(current).statistics.total
baseline_stats = ExecutionResult(baseline).statistics.total
return {
'pass_diff': current_stats.passed - baseline_stats.passed,
'fail_diff': current_stats.failed - baseline_stats.failed
}
python复制from robot.parsing import ModelTransformer
from robot.parsing.model.blocks import TestCase
class MyTransformer(ModelTransformer):
def visit_TestCase(self, node):
if "smoke" in node.name.lower():
node.keywords.create(name="Log", args=["这是冒烟测试"])
return node
使用自定义解析器:
bash复制robot --prerunmodifier MyTransformer.py tests/
创建可通过HTTP访问的远程库:
python复制from robotremoteserver import RobotRemoteServer
from MyLibrary import MyLibrary
RobotRemoteServer(MyLibrary(), host='0.0.0.0', port=8270)
客户端连接:
robotframework复制*** Settings ***
Library Remote http://localhost:8270
json复制{
"robot.language-server.python": "/path/to/python",
"robot.pythonpath": ["${workspaceFolder}/libraries"],
"robot.variables": {
"ENV": "stage"
}
}
regexp复制# 自定义关键字匹配
\b(Add Numbers|Subtract Numbers)\b
测试架构:
code复制finance-test/
├── libraries/
│ ├── core/
│ │ ├── __init__.py
│ │ ├── security.py # 加解密操作
│ │ └── transaction.py # 交易流程
│ └── interfaces/
│ ├── payment_gateway.py
│ └── core_banking.py
├── resources/
│ ├── banking/
│ │ ├── transfer.robot
│ │ └── inquiry.robot
│ └── payment/
│ ├── credit_card.robot
│ └── ewallet.robot
└── tests/
├── regression/
└── smoke/
安全测试关键字示例:
python复制from cryptography.hazmat.primitives import hashes
class SecurityKeywords:
@keyword
def hash_transaction(self, data):
digest = hashes.Hash(hashes.SHA256())
digest.update(data.encode())
return digest.finalize().hex()
典型测试流程:
robotframework复制*** Test Cases ***
Guest Checkout Flow
Open Browser To Homepage
Search For Product Robot Framework Book
Add To Cart
Proceed To Checkout As Guest
Enter Shipping Information ${GUEST_SHIPPING}
Select Shipping Method Standard
Enter Payment Details ${TEST_CC}
Place Order
Order Should Be Successful
性能关键操作监控:
python复制import time
from robot.api.deco import keyword
class PerformanceMonitor:
def __init__(self):
self.metrics = {}
@keyword
def measure_performance(self, name, action):
start = time.perf_counter()
result = action()
elapsed = time.perf_counter() - start
self.metrics[name] = elapsed
return result
硬件集成方案:
python复制import serial
from robot.api import logger
class DeviceController:
def __init__(self, port):
self.ser = serial.Serial(port, 9600)
@keyword
def send_device_command(self, cmd):
self.ser.write(cmd.encode())
response = self.ser.readline().decode().strip()
logger.info(f"设备响应: {response}")
return response
RF测试用例:
robotframework复制*** Test Cases ***
Device Boot Test
${response}= Send Device Command STATUS
Should Contain ${response} READY
python复制import matplotlib.pyplot as plt
def generate_quality_trend(data):
dates = [d['date'] for d in data]
pass_rates = [d['passed']/d['total']*100 for d in data]
plt.plot(dates, pass_rates)
plt.title('测试通过率趋势')
plt.savefig('trend.png')
python复制from sklearn.feature_extraction.text import TfidfVectorizer
def analyze_failures(logs):
texts = [log.message for log in logs if log.status == 'FAIL']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(texts)
# 进行聚类分析...
python复制from selenium.webdriver.common.by import By
from robot.api import logger
class SmartLocator:
@keyword
def find_best_locator(self, text):
locators = [
(By.XPATH, f"//*[contains(text(),'{text}')]"),
(By.CSS_SELECTOR, f"[aria-label*='{text}']"),
(By.ID, text.lower().replace(' ', '_'))
]
for by, value in locators:
elements = self.driver.find_elements(by, value)
if elements:
logger.info(f"使用定位器: {by}={value}")
return elements[0]
python复制import openai
class TestGenerator:
def __init__(self, api_key):
self.client = openai.OpenAI(api_key=api_key)
def generate_from_user_story(self, story):
response = self.client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "生成Robot Framework测试用例"},
{"role": "user", "content": story}
]
)
return response.choices[0].message.content
python复制# base_shop.py
class BaseShop:
@keyword
def add_to_cart(self, item):
"""基础购物车操作"""
pass
# electronics_shop.py
class ElectronicsShop(BaseShop):
@keyword
def add_warranty(self, years):
"""电子产品特有操作"""
pass
code复制company-commons/
├── robot/
│ ├── libraries/
│ │ └── common_utils.py
│ └── resources/
│ └── generic_keywords.robot
└── package/
└── setup.py
安装共享包:
bash复制pip install -e /path/to/company-commons