# unittest+pytest

测试框架:

python unittest框架结构 pytest与unittest框架原理_测试用例

unittest单元测试框架

unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件。

unittest工作原理

unittest结构图:

python unittest框架结构 pytest与unittest框架原理_html_02

test case :一个完整的测试单元,执行该测试单元可以完成对某一个问题的验证,完整体现在:测试前环境准备(setUp),执行测试代码(run),及测试后环境还原(tearDown);
test suite :多个测试用例的集合,测试套件或测试计划;
testLoader :加载TestCase到TestSuite中的,其中loadTestsFrom__()方法用于寻找TestCase,并创建它们的实例,然后添加到TestSuite中,返回TestSuite实例;
test runner :执行测试用例,并将测试结果保存到TextTestResult实例中,包括运行了多少测试用例, 成功了多少,失败了多少等信息;
test fixture:一个测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;

例子:

#定义一个测试类(遵守驼峰命名法),继承unittest.TestCase
class TestCase(unittest.TestCase):
    #定义类,setUp、tearDown是unittest自带
    @classmethod
    def setUpClass(cls) -> None:
        print("测试开始1")
    def setUp(self) -> None:
        print("测试开始")

    #测试用例的命名规则为test_xxx,不以test_xxx命名的函数是方法,方法是不能被执行的
    def test_login(self):
        print("欢迎进入登录页面")

    def test_loginout(self):
        print("退出登录页面")

    def tearDown(self) -> None:
        print("测试结束")

    @classmethod
    def tearDownClass(cls) -> None:
        print("测试结束1")

if __name__ == '__main__':
    unittest.main
    
    
'''
测试开始1
测试开始
欢迎进入登录页面
测试结束
测试开始
退出登录页面
测试结束
测试结束1

'''
'''
setUpClass:整个测试开始后执行,只执行一次
tearDownClass:整个测试完成后执行,只执行一次
setUp:每运行一次用例前都会执行一次
tearDown:每运行一次用例后都会执行一次
测试用例的命名规则为test_xxx,不以test_xxx命名的函数是方法,方法是不能被执行的
'''

断言方法

测试中,我们会经常用到判断某个函数的返回结果是否等于某个值或者等于某个类型这种情况,非常适合使用断言来判断。在UnitTest中,TestCase 已经提供有封装好的断言方法进行断言校验。断言强调的是对于整个测试流程的结果进行判断,所以断言的内容是极为核心的。

assertEqual(a,b) a==b 值是否相等
aassertNotEqual(a,b) a!=b 值是否不相等
aasserIs(a,b) a is b 值是否相同
aassertIsNot(a,b) a is not b 值是否不同
assertIn(a,b) a in b a是否包含b
assertNotIn(a,b) a not in b a是否不包含b
ssertTrue(a) bool(a) is true 是否为真
assertFalse(a) bool(a)is false 是否为假
assertIsNone(a) a is None 是否为空
assertIsNotNone(a) a is None 是否不为空
assertIsInstance(a,b) Instance(a,b) a与b的数据类型一样
assertNotIsInstance(a) not Instance(a,b) a与b的数据类型不一样

@UnitTest.skip

有时候,测试用例很多,有些需要执行,有些不想执行,那就需要跳过一些测试用例,这时候就需要用到skip,skip用法:

无条件跳过,unittest.skip(“xxx”)
条件为True跳过,unittest.skipIf(1 < 2, ‘xxx’)
条件为False跳过,unittest.skipUnless(1 > 2, ‘xxx’)
执行失败不计入case总数中,unittest.expectedFailure

unittest.TestSuite

测试用例 是按照顺序执行,如果我们想自定义执行顺序怎么办,比如2可能依赖于1,在unittest中解决用例执行顺序的问题是使用TestSuite。

测试套件TestSuite的作用:

  • 用于给测试用例进行排序
  • 管理测试用例

unittest实战

步骤:

  1. 写一个类继承unittest.Testcase
  2. 导入unittest
  3. 编写测试用例(测试用例要以test开头)
import time
import unittest
from selenium import webdriver

#定义一个测试类(遵守驼峰命名法),继承unittest.TestCase
class TestCase(unittest.TestCase):
    #定义类,setUp、tearDown是unittest自带
    @classmethod
    def setUpClass(cls) -> None:
        print("市政测试开始")
    def setUp(self) -> None:
        print("测试开始")

    #测试用例的命名规则为test_xxx,不以test_xxx命名的函数是方法,方法是不能被执行的
    def test_login(self):
        #将浏览器设置为全局变量,打开的浏览器不会关闭
        global driver
        print("欢迎进入登录页面")
        #打开谷歌浏览器
        driver = webdriver.Chrome();
        #加载管理员登录网页
        driver.get("https://municipal.test.shutoy.com/#/loginSecond?loginType=3")
        #浏览器加载最大
        driver.maximize_window()
        driver.find_element()
        time.sleep(5)

    def test_loginout(self):
        print("退出登录页面")

    def tearDown(self) -> None:
        print("测试结束")

    @classmethod
    def tearDownClass(cls) -> None:
        print("市政测试结束")

if __name__ == '__main__':
    unittest.main

unittest运行很大的坑:

  1. 命令行方式
python -m unittest 模块名.py
  1. main方式

需要配置环境(mian方法单独放在一个文件)

Pytest

pytest和unittest区别:

Pytest比起unittest来说比较自由,使用unittest首先,要继承TestCase的类,但是pytest是不需要的

python unittest框架结构 pytest与unittest框架原理_python unittest框架结构_03

一、用例设计规则

1.unittest

(1)测试类必须继承unittest.TestCase

(2)测试函数必须以”test_”开头

(3)测试类必须有unittest.main()方法

2.pytest

(1)测试文件的文件名必须以”test_”开头,或者以”_test”结尾

(2)测试类命名必须以”Test”开头

(3)测试函数名必须以”test”开头

(4)测试类里面不能使用”init”方法

总结:pytest是基于unittest衍生出来的新的测试框架,使用起来相对于unittest来说更简单、效率来说更高,pytest兼容unittest测试用例,但是反过来unittest不兼容pytest,所以说pytest的容错性更好一些!在使用交互逻辑上面pytest比unittest更全一些!

二、断言对比

1.unittest 断言

assertEqual(a, b) # 判断a和b是否相等

assertNotEqual(a, b) # 判断a不等于b

assertTrue(a) # 判断a是否为Ture

assertFalse(a) #判断a是否为False

assertIn(a, b) # a 包含在b里面

asserNotIn(a, b) # a 不包含在b里面

……

2.pytest 断言

pytest只需要用assert 来断言就行,assert 后面加需要断言的条件就可以了,例如:assert a = = b # 判断a是否等于b、

assert a != b # 判断a不等于b、assert a in b # 判断b包含a

总结:从断言上面来看,pytest的断言比unittest要简单些,unittest断言需要记很多断言格式,pytest只有assert一个表达式,用起来比较方便

三、用例前置和后置

1.unittest前置和后置

(1)通过setup每个用例执行前执行,teardown每个用例执行后执行

(2)通过setupclass类里面所有用例执行前执行,teardownclass类里面所有用例执行后执行

2.pytest前置和后置

(1)模块级别:setup_module/teardown_module,整个.py全部用例开始前执行/全部用例执行完后执行

(2)函数级别:setup_function/teardown_function,只对函数级别生效,每个用例开始前和结束后执行一次

(3)类级别:setup_class/teardown_function,只对类级别生效,类里面所有用例开始前执行一次,所有用例执行完执行一次

(4)方法级别:setup_method/teardown_method,只是类里面方法级别生效,方法开始前执行一致,方法结束后执行一次

(5)方法级别:setup/teardown,这个与setup_method/teardown_method用法很类似,但是级别比method级别要低,也就是说在同一个方法中会先执行setup_method再执行setup,方法结束后先执行teardown再执行teardown_method

pytest自定义设置前置和后置:

通过firture可以自定义pytest的前置和后置,格式fixture(scope=”function”, params=None, autouse=False, ids=None, name=Noe)

scope:有四个级别,function(默认),class,module,session

params:参数列表

autouse:False为默认值,意思代表需要根据设置的条件(scope级别)来激活fixture,如果为Ture,则表示所有function级别的都被激活fixture

ids:每个字符串id的列表,感觉没啥实质性作用

name:fixture的名字

四、参数化

1.unittest参数化

可以通过nose_parameterized来实现,格式:@nose_parameterized.parameterized.expand(data), ‘data’为list格式的参数化的数据

2.pytest参数化

通过装饰器@pytest.mark.parametrize来实现

五、生成报告方式

1.unittest

通过HTMLTestRunner生成

2.pytest

(1)通过pytest-html生成html格式报告

(2)通过allure生成方案(很详细)

pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

简单灵活,容易上手
支持参数化
能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
测试用例的skip和xfail处理
可以很好的和jenkins集成
report框架----allure 也支持了pytest

在pytest框架中,有如下约束:
所有的单测文件名都需要满足test_.py格式或_ __test.py格式。
在单测文件中,测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)
在单测类中,可以包含一个或多个test_开头的函数。
此时,在执行pytest命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。

pytest

pytest运行方式:

第一种:命令行运行

pytest -s  name.py

第二种:主函数运行 -s:表示支持控制台打印,如果不加,print不会出现任何内容

if __name__ == '__main__':
    pytest.main(["-s","name.py"])

参数

描述

案例

-v

输出调试信息。如:打印信息

pytest -x ./testcase/test_one.py

-q

输出简单信息。

pyets -q ./testcase/test_one.py

-s

输出更详细的信息,如:文件名、用例名

pytest -s ./testcase/test_one.py

-n

多线程或分布式运行测试用例

-x

只要有一个用例执行失败,就停止执行测试

pytest -x ./testcase/test_one.py

– maxfail

出现N个测试用例失败,就停止测试

pytest --maxfail=2 ./testcase/test_one.py

–html=report.html

生成测试报告

pytest ./testcase/test_one.py --html=./report/report.html

-m

通过标记表达式执行

pytest -m slow ./testcase/test_one.py #@pytest.mark.slow装饰的所有用例

-k

根据测试用例的部分字符串指定测试用例,可以使用and,or

pytest -k “MyClass and not method”,这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple

注意:

如果只执行 pytest ,会查找当前目录及其子目录下以test_.py或_test.py文件,找到文件后,在文件中找到以 test 开头函数并执行

配置文件(pytest.ini):

应用场景:使用配置文件后可以快速的使用配置的项来选择执行哪些测试用例

注意:
1.在window下,pytest配置文件不允许写注释
2.一个工程内只需要一个pytest配置文件,并且保证文件名之前
3.一般情况下,只需要将pytest配置文件放到工程根目录下
4.配置有pytest配置文件的工程,只需要打开命令行,执行pytest命令,即可进行测试

使用方法:

  1. 项目下创建case模块
  2. 将测试脚本文件放到case中
  3. pytest的配置文件放在自动化项目目录下
  4. 名称为pytest.ini
  5. 命令行运行时会使用该配置文件中的配置
  6. 第一行内容为[pytest]
    示例
#告知系统该文件的类型
[pytest]
#指定测试用例存放位置
testpaths = ./case
#输出打印信息,添加pytest命令选项
addopts = -s
#执行的文件名
python_files = test*.py
#执行的测试类名
python_classes = Test*
#执行的方法名
python_functions = test*

python unittest框架结构 pytest与unittest框架原理_python unittest框架结构_04

默认配置:

说明:测试用例文件名/测试类名/测试方法名都要以Test/test开头
[pytest]
testpaths = ./case
addopts = -s
python_files = test*.py
python_classes = Test*
python_functions = test*

自定义配置:

说明:测试用例文件名/测试类名/测试方法名,需要根据具体项目进行配置
修改默认配置的文件名/测试类名/测试方法名就可以
例如:
[pytest]
testpaths = ./case
addopts = -s
python_files = hm*.py
python_classes = Hm*
python_functions = hm*

单独指用一个文件执行:

[pytest]
testpaths = ./case
addopts = -s
python_files = 具体文件名称
python_classes = 具体类名
python_functions = 具体方法名

Pytest常用插件

1.能够生成pytest.html测试报告
2.能够控制pytest用例的执行顺序
3.能够掌握pytest失败重试

能够生成pytest.html测试报告

安装命令:

pip install pytest-html

使用:

1.在pytest配置文件中,增加命令选项, 在addopts = -s 后面增加 --html=用户路径/report.html
2.运行pytest命令,运行测试即可

--self-contained-html:代表直接生成html文件,无assert文件夹生成
[pytest]
testpaths = ./case
addopts = -s
          --html=./report/testReport.html  --self-contained-html
python_files = test*.py
python_classes = Test*
python_functions = test*

能够控制pytest用例的执行顺序

安装命令:

pip install pytest-ordering

使用:

1.标记于被测试的函数或者类。@pytest.mark.run(order=x)
2.根据order插入的指来指定运行顺序
3.order的值全部为正数或者负数时,值越小优先级越高
4.当正数负数同时存在,正数优先级高于负数

例子:

import pytest as pytest
class TestDemo:
    @pytest.mark.run(order=2)
    def test_001(self):
        print("djfijdiajfidjfifj")
        
    @pytest.mark.run(order=1)
    def test_002(self):
        print("请看下面一下啊")

能够掌握pytest失败重试

安装命令:

pip install pytest-rerunfailures

使用:

1.需要在pytest.ini增加命令选项,n代表次数
addopts = -s --reruns n
2.执行pytest测试即可

跳过测试函数

步骤:

1.在需要跳过的函数头上加上, @pytest.mark.skipif(条件,reason="跳过原因")
2.执行pytest即可
import pytest as pytest

a = 3
class TestDemo:
    @pytest.mark.run(order=2)
    def test_001(self):
        print("djfijdiajfidjfifj")

    @pytest.mark.skipif(a > 4 ,reason="A不对")
    @pytest.mark.run(order=1)
    def test_002(self):
        print("请看下面一下啊")

参数化功能

@pytest.mark.parametrize

1.在需要跳过的函数头上加上,@pytest.mark.parametrize(argnames,argvalues,indirect=False,ids=None,scope=None)
2.执行pytest即可
argnames:参数名
argvalues:参数对应的值,类型必须为可迭代类型,一般用list
一个参数使用方式:
1.argnames为字符串类型,根据需求决定何时的参数名
2.argvalues为列表类型,根据需求决定列表元素中的内容
3.在测试脚本中,参数,名字与argnames保持一致
4.在测试脚本中正常使用
import pytest as pytest

class TestDemo:
    # 单个参数
    # @pytest.mark.parametrize('参数变量',[数值1,数值2,数值3])
    @pytest.mark.parametrize("name", ["wang", "gui"])
    @pytest.mark.run(order=2)
    def test_001(self, name):
        print("姓名为", name)

    #多个参数
    #@pytest.mark.parametrize('参数1,参数2,..',[(数据1-1,数据1-2),(数据2-1,数据2-2),..])
    #注意
    #1.参数必须位于一个字符串内
    #2.数据格式为:[(),()]或者[[],[]]
    @pytest.mark.parametrize('name,age',[('小红','20'),('张三','18')])
    def test_002(self, name, age):
        print("姓名为:{},年龄为:{}".format(name,age))


if __name__ == '__main__':
    pytest.main('-s','test_case.py')
    
'''
test_case.py::TestDemo::test_001[wang] 姓名为 wang
PASSED
test_case.py::TestDemo::test_001[gui] 姓名为 gui
PASSED
test_case.py::TestDemo::test_002[\u5c0f\u7ea2-20] 姓名为:小红,年龄为:20
PASSED
test_case.py::TestDemo::test_002[\u5f20\u4e09-18] 姓名为:张三,年龄为:18
PASSED
'''
通过方法引用数据
import pytest as pytest


def list_a():
    return [('小红','20'),('张三','18')]

class TestDemo:

    #多个参数
    #@pytest.mark.parametrize('参数1,参数2,..',[(数据1-1,数据1-2),(数据2-1,数据2-2),..])
    #注意
    #1.参数必须位于一个字符串内
    #2.数据格式为:[(),()]或者[[],[]]
    @pytest.mark.parametrize('name,age',list_a())
    def test_002(self, name, age):
        print("姓名为:{},年龄为:{}".format(name,age))


if __name__ == '__main__':
    pytest.main('-s','test_case.py')

'''
test_case.py::TestDemo::test_002[\u5c0f\u7ea2-20] 姓名为:小红,年龄为:20
PASSED
test_case.py::TestDemo::test_002[\u5f20\u4e09-18] 姓名为:张三,年龄为:18
PASSED
'''

断言

assert xx:判断xx为真
assert not xx:判断xx不为真
assert a in b:判断b包含a
assert a not in b:判断b不包含a
assert a == b:判断a等于b
assert a != b:判断a不等于b
assert 表达式
assert 1==1

夹具-fixture

@pytest.fixture(scope="范围")

注意:夹具可用套用夹具

一般用conftest.py文件用于服务所有测试用例,成为全局夹具,conftest.py在测试用例之前启动,与setup、teardown类似

全局夹具使用步骤:
1.创建文件conftest.py,一般在文件根目录下创建
2.在conftest.py写夹具内容
eg:yield:用于区分用例之前运行和用例结束运行
@pytest.fixture()
def driver(self):
 driver = webdriver.Chrome()
 driver.get()
 yield driver   #返回driver
 driver.quit()
 3.夹具使用:夹具名称作为测试用例的参数放进函数里
 eg: def test_login(driver):

夹具一般用于启动浏览器

#例如
yield:用于区分用例之前运行和用例结束运行
@pytest.fixture()
def driver(self):
 driver = webdriver.Chrome()
 driver.get()
 yield driver   #返回driver
 driver.quit()

日志

日志可用在控制台或者文件中进行打印

1.控制台中打印

log_cli = true
log_cli_level=DEBUG
log_cli_format= %(asctime)s %(levelname)s %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
'''
log_cli:表示开启控制台日志,默认是关闭的
log_cli_level:日志等级
log_cli_forma:日志格式
log_cli_date_format:日志的时间格式
'''

1.日志要写在pytest.ini文件里

log_cli = 0
log_file = ./logs/test.log
log_file_level = info
log_file_format = %(levelname)-8s %(asctime)s [%(name)s:%(lineno)s] : %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S
'''
log_cli = 0 控制台实时输出日志 log_cli=True 或 log_cli=False(默认),或者 log_cli=1 或 log_cli=0
log_file = logs/pytest-logs.txt,将实时日志写入文件中
log_file_level = INFO,写入文件的日志级别
log_file_date_format = %Y-%m-%d %H:%M:%S,写入文件的日期格式
log_file_format = %(asctime)s %(levelname)s %(message)s,写入文件的日志消息格式
'''

2.创建一个钩子,在conftest.py中创建(定义函数的名称不可以修改)

logger = logging.getLogger(__name__)
def pytest_runtest_setup(item):
    # .center(60,"-"):代表日志加上左右60个横杠进行分隔
    logger.info(f'开始执行{item.nodeid}'.center(60, "-"))

def pytest_runtest_teardown(item):
    # .center(60,"-"):代表日志加上左右60个横杠进行分隔
    logger.info(f'结束执行{item.nodeid}'.center(60, "-"))

3.运行测试用例,就会在./logs/test.log生成日志信息

csv数据驱动测试

数据驱动测试(DDT),只需要修改数据文件,就可以自动的实现添加、修改、删除测试用例

步骤:

1.加载csv测试数据

在目录下创建一个文件夹data,在该文件夹下创建一个csv文件(ddt_test_login.csv),在csv中创建数据信息

python unittest框架结构 pytest与unittest框架原理_html_05

2.创建一个data.py文件,去读取ddt_test_login.csv里的内容

python unittest框架结构 pytest与unittest框架原理_html_06

# 为框架加载数据
import csv
from pathlib import Path

# 获取数据的存放目录
# Path(__file__)表示当前路径,parent:表示上一级
data_dir = Path(__file__).parent.parent / "data"


def read_csv(filename):
    path = data_dir / filename
    with open(path, encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for i in reader:
            yield list(i.values())


if __name__ == '__main__':
    for d in read_csv('ddt_test_login.csv'):
        print(d)

3.数据驱动测试

pytst框架自带支持数据驱动测试

@pytest.mark.parametrize('username,password',read_csv('ddt_test_login.csv'))
    def test_01_login_error(self, username, password):
        lp = LoginPage()
        lp.login_ecshop(username, password)
        time.sleep(2)
        message = lp.login_message()
        print("message的值为:" + message)
        assert message == "请输入验证码"

openpyxl+excel数据驱动

1.加载excel测试数据

pip install openpyxl

2.在data目录下导入excel文件

python unittest框架结构 pytest与unittest框架原理_html_07

3.读取excel文件内容

python unittest框架结构 pytest与unittest框架原理_python unittest框架结构_08

# 读取excel文件内容
def read_excel(filename):
    path = data_dir / filename
    # 加载excel
    wb = load_workbook(path)
    # 激活excel
    ws = wb.active
    # 读取文件内容
    # yield from ws.iter_rows(min_row=2, values_only=True)
    for i in ws.iter_rows(min_row=2, values_only=True):
        a = list(i)
        b = str(a[1])
        print(b)
        a[1] = b    
        print(a)
        yield a

4.使用excel内容

@pytest.mark.parametrize('projcetname, declarationtime, quallevel, projectmanager',
                             read_excel('ddt_test_jbgc.xlsx'))
    # @pytest.mark.parametrize('projcetname, declarationtime, quallevel, projectmanager',[('金杯工程项目001','2023-04-07','3级','王sir')])
    def test_add_jbgc(self, projcetname, declarationtime, quallevel, projectmanager):
        try:
            time.sleep(1)
            jbgc = JBGCPage()
            # 填写金杯工程信息
            jbgc.jbgc_add_message(projcetname, declarationtime, quallevel, projectmanager)
            # 点击添加按钮
            time.sleep(2)
            jbgc.add_button()
        except Exception:
            print("异常错误")

生成测试报告--allure

步骤一:生成结果

1.先在网上下载allure,https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/,然后进行环境配置

2.然后安装:allure-pytest

pip install allure-pytest

步骤二:生成报告

在pytest.ini文件添加

#--clean 清空文件
addopts = --alluredir=./allure_results --clean-alluredir

步骤三:报告中附加内容

allure使用方法:

使用方法

参数值

参数说明

@allure.epic()

epic描述

敏捷里面的定义,往下是feature

@allure.feature()

模块名称

功能点的描述,往下是story

@allure.story()

用户故事

用户故事,往下是title

@allure.title(用例标题)

用例的标题

重命名测试用例标题

@allure.testcase()

测试用例的链接地址

对应功能测试用例系统里面的case

@allure.issue()

缺陷

对应缺陷管理系统里面的链接

@allure.description()

用例描述

测试用例的描述

@allure.step()

操作步骤

测试用例的步骤

@allure.severity()

用例等级

@allure.link()

链接

定义一个链接,在测试报告展现

@allure.attachment()

附件

报告添加附件