pytest框架心得

参考地址

1.https://www.cnblogs.com/sundawei7/p/11956618.html#_label0_0

2.https://www.cnblogs.com/poloyy/tag/Pytest/

--------------------------------------------------------------------------------------------------------------------------------------------------------

pytest和unittest区别

 unittestpytest

用例编写规则

测试文件必须先import unittest

测试文件名必须以“test_”开头或者"_test"结尾(如:test_ab.py)

测试类必须继承unittest.TestCase

测试方法必须以“test_”开头

测试方法必须以“test_”开头

测试类命名以"Test"开头

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

 

用例前置和后置

unittest提供了setUp/tearDown,只能针对所有用例

pytest提供了模块级、函数级、类级、方法级的setup/teardown,比unittest的setUp/tearDown更灵活

模块级(setup_module/teardown_module)开始于模块始末,全局的

函数级(setup_function/teardown_function)只对函数用例生效(不在类中)

类级(setup_class/teardown_class)只在类中前后运行一次(在类中)

方法级(setup_method/teardown_method)开始于方法始末(在类中)

类里面的(setup/teardown)运行在调用方法的前后

断言

unittest提供了assertEqual、assertIn、assertTrue、assertFalse

pytest直接使用assert 表达式

测试报告

unittest使用HTMLTestRunnerNew库

pytest有pytest-HTML、allure插件

失败重跑

unittest无此功能

pytest支持用例执行失败重跑,pytest-rerunfailures插件

参数化

unittest需依赖ddt库

pytest直接使用@pytest.mark.parametrize装饰器

用例执行

unittest默认执行全部用例,也可以通过加载testsuit,执行部分用例

pytest可以通过@pytest.mark来标记类和方法,pytest.main加入参数("-m")可以只运行标记的类和方法

用到的第三方库

1.pip install pytest
2.pip install pytest-html
3.pip install allure-pytest
4.pip install pytest-ordering
5.pip install pytest-rerunfailures

整体框架

 1 E:\py_tests\
 2     ├─config
 3     │  ├─env.ini  #存放域名test_url、product_url与数据库链接所需的dbname、host、port、user、password
 4     ├─logs
 5 
 6    │  ├─log_error.log
 7 
 8     │  ├─log_info.log
 9 
10     ├─main
11 
12    │  ├─runAll.py  #执行此文件,可执行所有的测试用例(集成Jenkins)
13  
14     ├─report   #该目录下存放执行用例后自动生成的报告
15 
16     ├─test_case  #测试用例目录
17 
18    │  ├─project  #测试项目目录
19  
20     │  │  ├─interface  #测试接口目录
21 
22     │  │  │  ├─test_01_demo1.py  测试用例(注意命名)
23 
24     │  │  │  ├─test_02_demo2.py
25 
26     ├─utils  #文件下是框架用到的公共类方法
27 
28     ├─pytest.ini  #必须是GBK格式的文件,该文件是pytest的配置文件

pytest.ini配置文件

1.执行用例时,控制台输出的结果的级别、生成报告的路径、错误用例自动执行的设置

addopts = -s -v --alluredir ../report/result --reruns=3

2.测试用例的目录与命名规则

testpaths = ./test_case/rmcoupon/
python_files = test_*.py
python_classes = Test_*
python_functions = test_*

[pytest]
# pytest 执行的相关参数
addopts = -s -v --alluredir ../report/result --reruns=3
# 测试用例的目录
testpaths = ./test_case/project/
python_files = test_*.py
python_classes = Test_*
python_functions = test_*
# 对@pytest.mark.xfail中预期与实际不一致的,标记为失败
xfail_strict=true
测试用例设计
test_01_demo1.py
# coding=utf-8
"""
    @File: test_01_demo1.py
    @Desc: xxxx
    @Time: 2020/9/02 10:26
    @Author: mo
"""
import sys

import allure
import pytest

@allure.feature('获取菜单列表接口')
class Test_Demo1(object):

    # 类级,只调用 1 次
    def setup_class(self):
        log_info.info('begin')
        self.url = "XXXXXXXX"

    # 类级,只调用 1 次
    def teardown_class(self):
        log_info.info('end')

    # 函数级,每个函数(即每条用例)执行前调用 1 次
    def setup_method(self):
        log_info.info('{} begin'.format(self.__class__.__name__))

    # 函数级,每个函数(即每条用例)执行后调用 1 次
    def teardown_method(self):
        log_info.info('{} end'.format(self.__class__.__name__))

    @allure.story('菜单列表')
    @allure.title('验证菜单列表接口正常')
    @allure.description('输入正常值,验证 xxx接口 返回是否正确')
    @allure.severity(allure.severity_level.BLOCKER)
    @pytest.mark.parametrize('clientVersion, tabType', [('', ''), ('', '0')])
    def test_01_demo1(self, clientVersion, tabType):
        log_info.info('{} begin'.format(sys._getframe().f_code.co_name))

        body = {
            "clientVersion": clientVersion,
            "tabType": tabType
        }
        res = req_get(url=self.url, param=body) #此处为个人分装的方法
        self.assert_.assert_code(res[2], 200)
        log_info.info('{} end'.format(sys._getframe().f_code.co_name))

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

   


skipif使用
condition字段的取值,必须是从导入的内容中拿到,比如,不同的py文件(测试用例文件)有依赖关系,A.py中接口返回的字段,需要在B.py中使用,首先我们需先将A中获取到的字段存储在map中,然后B文件引用map,从map中获取使用的字段
B.py

from utils import GlobalMap
#如果未获取到id,则跳过此用例
@pytest.mark.skipif(condition='GlobalMap().get(\'id\') == "Null_"', reason='XXXXXXX')
def test_01_getinteractivedetal(self):
log_info.info('{} begin'.format(sys._getframe().f_code.co_name))
self.url = 'XXXXXXXXXXXXXXXXXXX'
res = req_get(url=self.url)
self.assert_.assert_code(res[2], 200)
self.assert_.assert_code(res[0]['code'], 0)
log_info.info('{} end'.format(sys._getframe().f_code.co_name))

GlobalMap.py代码如下:
import json
import logging

"""
    @File: GlobalMap.py
    @Desc: 实现全局变量
    @Time: 2020/7/21 16:42
    @Author: 
"""


class GlobalMap:
    map = {}

    def set_map(self, key, value):
        if isinstance(value, dict):
            value = json.dumps(value)
        self.map[key] = value

    def set(self, **keys):
        try:
            for key_, value_ in keys.items():
                self.map[key_] = str(value_)
                logging.debug(key_ + ":" + str(value_))
        except BaseException as msg:
            logging.error(msg)
            raise msg

    def del_map(self, key):
        try:
            del self.map[key]
            return self.map
        except KeyError:
            logging.error("key:'" + str(key) + "'  不存在")

    def get(self, *args):
        key = None
        try:
            dic = {}
            for key in args:
                if len(args) == 1:
                    dic = self.map[key]
                    logging.debug(key + ":" + str(dic))
                elif len(args) == 1 and args[0] == 'all':
                    dic = self.map
                else:
                    dic[key] = self.map[key]
            return dic
        except KeyError:
            logging.warning("key:'" + str(key) + "'  不存在")
            return 'Null_'

 

runAll.py
# coding=utf-8
import datetime
import os
import pytest
import sys
from utils.log import log_info, log_error
from utils.shellUtil import Shell
from utils.DeleteDir import setDir
sys.path.append(r'../')


# 待执行的用例地址
# project_name = sys.argv[1]
project_name = 'project'

# 执行接口用的网络环境
# execute_env = sys.argv[2]
execute_env = 'test_url'



if __name__ == '__main__':
    # 1、设置待执行用例的目录
    current_directory = os.path.dirname(os.path.abspath(__file__))
    root_path = os.path.abspath(os.path.dirname(current_directory) + os.path.sep + ".")
    # 定义执行用例所在的目录
    cases = root_path + '/test_case/' + project_name
    try:
        log_info.info('自动化用例执行开始...')
     #执行命令前,先删除之前执行后的测试报告 #setDir(root_path
+'/report/') pytest.main(['-s', cases, '-v', '--alluredir', '../report/result']) log_info.info('自动化用例执行完毕!') except Exception as e: log_error.error('自动化用例执行失败:{}'.format(e)) # 生成 allure 报告 shell = Shell() cmd = 'allure generate %s -o %s --clean' % ('../report/result', '../report/allure_html') try: log_info.info("开始生成测试报告...") shell.invoke(cmd) log_info.info("结束生成测试报告!") except Exception as e: log_error.error("测试报告生成失败:{}".format(e)) raise

 

utils中的shellUtil.py是用来实现在cmd中执行pytest命令的

import subprocess


class Shell:
    @staticmethod
    def invoke(cmd):
        output, errors = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
        o = output.decode("unicode_escape")
        return o

 

 

 

posted @ 2020-09-11 18:34  亿逍遥  阅读(264)  评论(0编辑  收藏  举报