背景

入职了一家小公司,需要从0开始搭建一个自动化测试环境,因为是测试板卡项目,所以需要使用串口连接工具pyserial,测试用例批量化执行工具pytest,测试报告自动生成工具allure,大家根据这三个关键词选择是否需要继续阅读

1. 技术实现

1.1 技术说明

使用pyserial作为串口连接工具,发送读写命令;使用pytest作为用例批量管理工具;使用allure对测试结果进行可视化处理,自动生成测试报告

1.2 后续拓展

持续集成:构建冒烟和全量用例,支撑每次版本发布结果呈现:自动处理测试结果,生成表格并邮件自动抄送相关人员

2. 目录结构

2.1 testcases(文件夹)

用来存放测试脚本和测试夹具,例如:test_demo.py、conftest.py等

2.1.1 子文件夹分类

同时存在两种分类的维度,第一种按照功能性能可靠性等进行分类,支撑单轮验证

function:功能测试performance:性能测试reliability:可靠性测试

第二种按照冒烟和全量进行分类,支撑版本迭代

smoke:冒烟测试(少量基本功能用例,支撑版本可用性验证)full:全量测试(所有测试用例,支撑版本迭代验证)

2.1.2 test_demo.py

需要实现的测试脚本,原则上该脚本内不定义任何功能函数,全部从function文件夹下导入。内部定义详细的测试类和测试方法,脚本的实现步骤与实际测试用例的描述步骤必须保证完全一致!!!

#导入function文件夹下定义的函数

from function.serial port,link serial port import open com

#此处为测试类的定义,例如:网口自协商类,板卡复位类

class TestCommunicateserialPort:

#此处为测试方法的定义,例如:分别配置两个网口,检查自协商结果与预期是否一致

def test_case0l_a_connect_b(self):

# step 1

do something1()

# step 2

do something2()

# 判断结果

assert expected result == actual result

2.1.3 conftest.py

需要实现的测试夹具,定义部分/全部用例的前后置执行内容,例如:连接串口(前置),关闭串口(后置)

# 装饰器,参数为该装饰器的作用范围

@pytest.fixture(scope='function', autouse=True)

def communicate_serial_port():

do something()

yield

time.sleep(5)

scope:被装饰器标记的作用域,可选参数有function(默认),class,module,package/sessionparams:参数化,可往函数内传参,支持数据类型列表[],元组(),字典列表[{},{}],字典元组({}),{})autouse:是否使用该装饰器,默认为False(不使用)

2.2 function(文件夹)

用来存放通用的功能函数,例如:connect_serial_port.py等,在testcases目录下的测试脚本可直接import调用

2.2.1 子文件夹分类

按照实际功能模块来划分,以下示例仅为参考

network:网口配置相关serial_port:串口连接,串口配置相关reset:网口复位,板卡复位相关

2.2.2 connect serial port.py

该函数仅用于打开串口并判断串口是否打开

def open serial port(n: int):

ports = list(serial.tools.list ports.comports(include_links=False))

ser = serial.serial(port=ans[0], baudrate=230400, parity=serial.PARITY_ NONE, stopbits=sertimeout=1, rtscts=False)

if ser.isopen():

print('\nserial port connection is successful!')

else:

print("\nserial port connection is failure!')

assert ser.isopen()

return ser

2.3 reports(文件夹)

用来存放json格式的用例执行结果+allure生成的htmI报告

2.3.1 xml

用来存放每个用例的独立执行结果(json格式),通过pytest.ini中 addopts =--alluredir ./reports/xml/来控制

--alluredir:后接需要存放的报告缓存地址

2.3.2 html

用来存放所有用例的整体执行结果(html格式),通过main.py中os.system('allure generate ./reports/xml -0/reports/html/ --clean')来控制

generate:后接取缓存报告的地址(源地址)-o:后接报告生成后的存放地址(目的地址)--clean:覆盖原来的htmI报告,默认不覆盖

2.4 pytest.ini(配置文件)

main.py执行时,会读取该文件下的配置,解析出需要测试的用例范围,例如:只执行功能测试用例,失败用例rerun,是否随机执行等

[pytest]

addopts = -vs --random-order --alluredir ./reports/xml/ --reruns=2

testpaths = ./testcases/test_communicate_serial_port.py

python_files = test_*.py

python_classes = Test*

python_functions = test

addopts:配置基本的执行参数,例如:显示详细信息,是否随机执行,allure生成报告等testpaths:需要执行的用例范围,可执行范围包含文件夹/文件/类/方法python_files:需要执行的python文件命名规则,默认为test_开头python_classes:需要执行的python类命名规则,默认为Test开头python_functions:需要执行的python函数命名规则,默认为test开头

2.5 main.py(主文件)

pytest执行的主入口,pytest.main()调用pytest.ini的配置后执行相关测试用例,也可以发送其他命令按照顺序执行

import os

import pytest

if __main__ == '__name__':

pytest.main()

os.system('allure generate ./reports/xml -o ./reports/html/ --clean')

3.数据规范说明

3.1 命名规则

3.1.1 变量

变量命名遵循Python命名规则,使用小驼峰+下划线的命名规则,例如:query_port_status(查询网口状态)

3.1.2 文件/类/函数

除了下面的命名规则之外,还需做到“见名知意”,最好使用英文翻译作为名称

python_files:默认为test_开头python_classes:默认为Test开头python_functions:默认为test开头

3.1.3 注释

为了代码的可读性,应该在文件和函数头添加相应的注释说明,一些数据转换规则最好也能体现在注释中,而不是每次重读代码或查数据对应表

3.1.3.1 文件注释

文件注释包含文件名,坐着,迭代版本,创建/更新时间,文件实现的功能,有无特殊用法(如:外部传参等python_port_config.py 【参数1】~/log_path  【参数2】~/cache_path)

"""

File Name: port config.py

Author: KeYou

Version: v1.1

Created: 2022/12/20

Description: Converts instructions between hexadecimal and ASCII.

Usage: N/A

"""

3.1.3.2 函数注释

函数注释包括函数实现的功能,内部传参(分别应该传入什么参数),返回值(返回值的类型,作用)

def hex to ascii(s: str):

"""

-function:Converts an instruction from hexadecimal to ASCII.

-param s:Hexadecimal command to be converted, such as: '4F440006710A'

-return:s:str,such as:b'oDlxe0\x06g\n'

"""

return base64.b16decode(s.upper())

3.1.3.3 函数内注释

函数内的注释,阐明一些特殊情况即可,例如bit位的对应关系,无需任何函数都详细注释。

例如上述代码中的注释,直接反馈了config变量中4个bit的组成方式,包括每个bit的对应关系,增加可读性

# config(bit0-3) = force(bit3) + speed(bit1-2) + autoneg(bit0)

config = “0000"

# 1) autoneg(bit0): on:1, off:0

if autoneg == "on":

config = config[:-1]+'1'

elif autoneg == "off":

pass

else:

print("Invalid input, please re-enter autoneg!")

return False

# 2) speed(bit1-2): F:01,G:10,FG:11

…………………………………………………………

# 3) XXXXX: XXXXX

…………………………………………………………

3.2 函数定义

测试方法应该更具有通用性,方便重构,并且尽可能考虑所有情况,在异常case时给出相应的报错信息,便于出现问题时快速定位

3.2.1 可重构

测试方法应该更具有通用性,方便重构

3.2.2 全面性

基本的异常路径都有考虑到,并给出相应的提示信息,方便问题定位

例如以下示例中,考虑了连接串口时可能出现的两种异常case

插入了串口但未识别到任何串口设备插入了多个串口但我们需要的串口设备并未被识别到

def open_com(n: int):

ports = list(serial.tools.list_ports.comports(include_links=False))

ports_list = []

for port in ports:

s = str(port)

port_list.append(s[:s.find(' ')])

# 异常case1: 串口列表是空的,提示检查串口连接是否正常

if not port_list:

print('No serial port is available, please check the connection!')

return False

# 异常case2: 其他的串口设备,不是COMXX开头

for port in port_list:

if port[:3] != 'COM':

print('Wrong serial port, Please check serial ports connection!')

return False

3.2.3 一致性

方法的实现步骤要和测试用例的步骤保持完全一致,部分执行步骤可按照实际逻辑进行调整

# 测试用例描述如下:

# 测试用例编号&名称

def test_case0l on_g ms to_on_g_ms(self):

# step1:打开串口

ser = open_com(1)

# step2:配置网口T1-1为自协商打开, 1000M速率,SM都支持

ser.write(hex_to_ascii(port_config("T1","on","G”,“MS”)))

# step2的预期结果:网口配置成功

read_msg1 = ser.read(100)

assert ascii_to_hex(str(read_msg1)) == result_query_success

# step3:配置网口T1-2为自协商打开1000M速率,SM都支持

ser.write(hex to ascii(port config("T2","on",“G”,“MS")))

# step3的预期结果:网口配置成功

read msg2 = ser.read(100)

assert ascii_to_hex(str(read_msg2)) == result_query_success

# step4:查询网口状态

ser.write(hex to ascii(command query port status))

port_status =ser.read(100)

ans = analyse_result(ascii_to_hex(str(port_status)))

step5:关闭串口(该步骤在代码中被提前,防止用例失败后串口无法关闭)

ser.close()

# step4的预期结果:网口状态符合预期

assert ans[0][:3] == ['T1','on','up']

assert ans[1][:3] == ['T2','on','up']

3.2.4 独立性

用例之间互不干扰,都可以分别执行,也可整体一起随机执行

【例】如3.2.3中测试用例

单个测试用例完成了串口的打开和关闭,无其他资源等待,并及时完成释放,与其他测试用例无任何关联,互不影响,整个测试集可以按照任意顺序执行

总结

本文只是基于某个特定的项目,写了一个较为简单的自动化测试框架说明,并非企业级规范标准,甚至一些英文翻译也是机翻的,不要太在意这些细节。整篇文章可以用作个人的项目搭建练手或者面试项目叙述,仅供参考!

【注】文中有一些英文字母之间的空格可能无法理解其含义,其实是复制到博客时_字符缺失,该字符空缺变成了空格,例如:port connection.py其实是port_connection.py

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: