三、安装pytest依赖库
pip install pytest
pip install pytest-html
pip install paramiko
pip install rpyc
pip install request
四、使用pytest
1.配置文件
pytest.ini是pytest的主配置文件,可以改变pytest的默认行为,按指定的方式去运行。Pytest.ini的基本格式:
# 保存为pytest.ini文件
[pytest]
addopts = -rsxX
pytest.ini可以放在测试脚本的目录中或者通过pytest调用时的命令行参数指定:
pytest -c "E:\Desktop\pytest_local.ini"
不推荐使用命令行参数指定的方式,因为使用-c参数后,原来在测试脚本根目录中的pytest.ini就默认不使能了,pytest就会使用当前文件夹作为rootdir,破坏了一般意义上我们测试脚本的根目录是pytest.ini所在目录的固定观念。当然,我们可以通过制定命令行参数---rootdir来指定运行根目录:
testcase\dir_level1\dir_level2>pytest -c "E:\Desktop\pytest_local.ini" --rootdir="E:\Desktop\Sublime Text Build_x64\test_ini"
pytest确定rootdir的方法见手册:
https://docs.pytest.org/en/latest/customize.html#initialization-determining-rootdir-and-inifile
2.配置项
本节会介绍我们常用的配置项,对于其他的配置项,请在官方手册中查询。
adopts
addopts参数可以更改默认命令行选项,等同于我们在cmd输入指令去执行用例的时候加的参数,比如指定测试床信息和生成的日志文件目录:
[pytest]
addopts = --testbed="E:\Desktop\topology_template.yaml" --reportpath="E:\Desktop"
--testbed
用于指定测试床的命令行参数,可以写入addopts中或者在调用的时候手动输出,该参数必选。
--reportpath
用于指定生成日志文件的根目录,如果不选,日志文件会默认选择pytest的rootdir作为日志的根目录
python_functions
匹配测试用例编号,用例编号一般都是有固定开头的,比如以TestCase_开头,因此配置为TestCase_
python_files
匹配测试脚本文件名,TestCase的脚本名都是TestCase_开头的,因此配置为TestCase_*
综上,我们在子系统根目录下放置的pytest.ini样例如下,脚本运行时可以根据自己的需要修改。
# pytest.ini
[pytest]
addopts = -s --testbed="E:\Desktop\topology_template.yaml" --reportpath="E:\Desktop"
python_files = TestCase_*
python_functions = TestCase_*
下面介绍部分常用配置项
markers
为了避免自定义标记的拼写错误,可以通过makers字段在pytest.ini文件中注册标记,如有不在markers字段中的标记,pytest运行时会报错。
[pytest]
markers =
smoke: run smoke testcase
func: run functionarity testcase
标记注册好之后,可以通过pytest --makers来查看
五、脚本写作
1.执行单个测试用例
创建一个.py文件(test_001.py),对个简单的功能进行测试。
#coding=utf-8
# 方法
def func(x):
return x + 1
# 测试用例
def test_answer():
assert func(3) == 5
注意:测试用例函数命名应全部小写,并以test_开头,如test_answer
切换到测试文件所在的目录,通过“pytest”命令运行测试
c:\python_ts>pytest
2.执行多个测试用例
在一个测试类中可以创建多个测试用例:
#coding=utf-8
class TestClass:
def test_one(self):
x = "this"
assert "h" in x
def test_two(self):
x = "hello"
assert x == "hi"
注意:测试类命名规范为以Test开头
pytest指定文件运行:
>pytest -q test_002.py
-q 为quiet。表示在安静的模式输出报告。-q 不会输出pytest的版本信息。
如果没有参数,pytest会查看当前目录和所有子目录的测试文件(test_开头或者
_test结尾)并运行。 也可以指定文件名,目录名称或这些名称的列表。
发现规则小结
• 测试文件应该命名为test_.py 或_test.py
• 测试方法和函数应该被命名为test_。
• 测试类应该被命名为Test
结果类型:
以下是测试功能的几种常见的结果:
• PASSED (.):测试成功。
• FAILED (F): 测试失败。
• ERROR (E):错误
3.从python代码中调用pytest
通过python代码执行pytest
(1)直接执行pytest.main() 【自动查找当前目录下,以test_开头的文件或者以_test结尾的py文件】
(2)设置pytest的执行参数 pytest.main(['--html=./report.html','test_xxx.py'])【执行test_login.py文件,并生成html格式的报告】
方式2中,pytest.main()的参数其实是个list,执行参数和插件参数,多个参数时[]内的多个参数通过‘逗号,’进行分割
比如,在C:\python_testscript目录下有三个py文件
python_testscript/
├── test_001.py
├── test_002.py
└── test_003.py
其中test_003.py的代码如下:
import pytest
def test_main():
assert 5 != 5
if __name__ == '__main__':
pytest.main()
pytest.main不带参数,直接运行该程序,sublime 中按Ctrl+B 运行。结果如下:
============================= test session starts =============================
platform win32 -- Python 3.7.1, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\python_testscript, inifile:
plugins: metadata-1.7.0, html-1.19.0
collected 4 items
test_001.py F [ 25%]
test_002.py .F [ 75%]
test_003.py F [100%]
================================== FAILURES ===================================
_________________________________ test_answer _________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_001.py:9: AssertionError
_____________________________ TestClass.test_two ______________________________
self = <test_002.TestClass object at 0x0000000003791A90>
def test_two(self):
x = "hello"
> assert x == "hi"
E AssertionError: assert 'hello' == 'hi'
E - hello
E + hi
test_002.py:11: AssertionError
__________________________________ test_main __________________________________
def test_main():
> assert 5 != 5
E assert 5 != 5
test_003.py:4: AssertionError
===================== 3 failed, 1 passed in 0.22 seconds ======================
[Finished in 1.4s]
从执行结果看到,main() 默认执行了当前文件所在的目录下的所有测试文件。
那么,如果我们只想运行某个测试文件呢?可以向main()中添加参数:
import pytest
def test_main():
assert 5 != 5
if __name__ == '__main__':
pytest.main(['test_001.py'])
============================= test session starts =============================
platform win32 -- Python 3.7.1, pytest-4.0.2, py-1.7.0, pluggy-0.8.0
rootdir: C:\python_testscript, inifile:
plugins: metadata-1.7.0, html-1.19.0
collected 1 item
test_001.py F [100%]
================================== FAILURES ===================================
_________________________________ test_answer _________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_001.py:9: AssertionError
========================== 1 failed in 0.06 seconds ===========================
[Finished in 1.2s]
4.常用参数说明
(1)执行失败N条用例后停止运行
pytest -x # 第一条用例失败后停止运行
pytest --maxfail=2 # 失败两条用例后停止运行
(2) 指定执行用例
pytest test_abc.py # 执行test_abc.py中的所有用例
pytest testing/ # 执行testing目录下的所有用例
pytest test_abc.py::TestClassA::test_sample # 执行test_abc.py文件中的TestClassA测试类中的test_sample方法
pytest -v -m AR161 # 按标签执行当前目录下,fixture为AR1610的用例
(3)循环执行用例
pytest --count 30 # 带--count参数指定循环次数(需要安装pytest-repeat包)
(4) 生成测试报告
pytest --junitxml=path # 生成junit类型测试报告
pytest -v --html=testresult.html # 生成html类型测试报告
(5) cmd命令行执行过程打印信息
pytest --capture=no
(6)其他参数
pytest –v #输出详细信息
pytest –q #静默模式,不输出python的版本信息等
5.常用断言和异常处理
(1)常用断言:
assert a==b,”添加断言备注信息”
assert a!=b, ”添加断言备注信息”
assert a, ”添加断言备注信息”
assert not a, ”添加断言备注信息”
assert “abc” in “123abc” , ”添加断言备注信息”
assert “ef” not in “123abc” , ”添加断言备注信息
(2)python的标准异常
异常名称 描述
BaseException 所有异常的基类
SystemExit 解释器请求退出
KeyboardInterrupt 用户中断执行(通常是输入^C)
Exception 常规错误的基类
StopIteration 迭代器没有更多的值
GeneratorExit 生成器(generator)发生异常来通知退出
StandardError 所有的内建标准异常的基类
ArithmeticError 所有数值计算错误的基类
FloatingPointError 浮点计算错误
OverflowError 数值运算超出最大限制
ZeroDivisionError 除(或取模)零 (所有数据类型)
AssertionError 断言语句失败
AttributeError 对象没有这个属性
EOFError 没有内建输入,到达EOF 标记
EnvironmentError 操作系统错误的基类
IOError 输入/输出操作失败
OSError 操作系统错误
WindowsError 系统调用失败
ImportError 导入模块/对象失败
LookupError 无效数据查询的基类
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
MemoryError 内存溢出错误(对于Python 解释器不是致命的)
NameError 未声明/初始化对象 (没有属性)
UnboundLocalError 访问未初始化的本地变量
ReferenceError 弱引用(Weak reference)试图访问已经垃圾回收了的对象
RuntimeError 一般的运行时错误
NotImplementedError 尚未实现的方法
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab 和空格混用
SystemError 一般的解释器系统错误
TypeError 对类型无效的操作
ValueError 传入无效的参数
UnicodeError Unicode 相关的错误
UnicodeDecodeError Unicode 解码时的错误
UnicodeEncodeError Unicode 编码时错误
UnicodeTranslateError Unicode 转换时错误
Warning 警告的基类
DeprecationWarning 关于被弃用的特征的警告
FutureWarning 关于构造将来语义会有改变的警告
OverflowWarning 旧的关于自动提升为长整型(long)的警告
PendingDeprecationWarning 关于特性将会被废弃的警告
RuntimeWarning 可疑的运行时行为(runtime behavior)的警告
SyntaxWarning 可疑的语法的警告
UserWarning 用户代码生成的警告
(3)异常处理
1)try...except
a=10
b=0
try:
c=a/b
except:
print("error")
print("done")
2)try ....except...else 语句,当没有异常发生时,else中的语句将会被执行
a=10
b=0
try:
c = b/ a
except:
print("error")
else:
print("no error")
print("done")
3)raise 引发一个异常
inputValue=input("please input a int data :")
if type(inputValue)!=type(1):
raise ValueError
else:
print(inputValue)
4)try ...finally ,无论异常是否发生,在程序结束前,finally中的语句都会被执行
a=10
b=0
try:
c = a/ b
finally:
print("always excute")
finally语句也可以和except语句一起使用
a=10
b=0
try:
c = a/ b
except:
print("error")
finally:
print("always excute")
6.正则表达式
必须要import re
1) re.match函数
re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
如:
import re
print(re.match('www', 'www.123.com') .group()) # 在起始位置匹配
print(re.match('com', 'www.123.com')) # 不在起始位置匹配
输出结果:
www
None
2)re.search方法
re.search 扫描整个字符串并返回第一个成功的匹配。
如:
import re
print(re.search('www', 'www.123.com').group())
print(re.search('com', 'www.123.com').group())
输出结果:
www
com
re.match与re.search的区别:
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
3) re.findall函数
在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。
注意: match 和 search 是匹配一次 ,findall 匹配所有。
import re
print(re.finditer(r"\d+","12a32bc43de3"))
输出结果:
['12', '32', '43', '3']
4) re. finditer函数
和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。
import re
it = re.finditer(r"\d+","12a32bc43de3")
for match in it:
print (match.group() )
输出结果:
12
32
43
3
六、测试套特性目录内容举例
1.测试套即为特性目录名,如testcase/startup,特性目录下存放__init__.py、conftest.py以及requirements.py及测试用例脚本文件
1)__init__.py :为空文件即可,标识该目录为一个package
2)contest.py: 定义脚本的setup和teardown,结构如下:
# --------------------------------------------------------
#
#注释部分
#
# --------------------------------------------------------#依赖的模块
import os,sys
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
from requirements import *@pytest.fixture(scope='package', autouse=True)
def testuite_setup_teardown():#准备工作:每个特性的所有用例执行前执行一次
Log.log('\n------------testsuite setup begin--------------')
dta.login()yield
#环境清理:每个特性所有用例执行完执行一次
Log.log('\n------------testsuite teardown begin--------------')
@pytest.fixture(scope='function', autouse=True)
def testcase_setup_teardown():#准备工作:每个用例运行前执行一次
Log.log('\n------------testcase setup begin--------------')
yield#环境清理:每个用例运行完执行一次
Log.log('\n------------testcase teardown begin--------------')3)requirements.py :存放所有需要依赖的模块
######################系统模块##########################
import time
import datetime
import re
import pytest
import sys
import os
from ctypes import *######################pytest_testlib_base模块##########################
import Log
import rpyc_client
import rpyc_server######################本地自定义common模块##########################
sys.path.append("%s/../../common/"%(os.path.split(os.path.realpath(__file__))[0]))
sys.path.append("%s/../../common/STARTUP"%(os.path.split(os.path.realpath(__file__))[0]))
import py_base
import file_method4)测试用例脚本文件:
# --------------------------------------------------------
#
#--
# 用例编号 : TESTCASE_1001
# 用例名称 :测试用例demo
# --------------------------------------------------------from requirements import *
@pytest.fixture(scope='function', autouse=True)
def prepare_cleanup():
Log.log('\n------------prepare begin--------------')
yield
Log.log('\n------------cleanup begin--------------')def TESTCASE_1001():
Log.log("操作步骤1、设备启动,有预期结果1")
#py_base.py文件及check_startup方法定义在整个工程目录中common文件夹中,通过requirements.py 的本地自定义common模块声明引入
py_base.check_startup(dev_a)
Log.log("预期结果1、设备正常启动")