学习笔记之软件测试2:基于Python的单元测试方法(Testcase+Pytest)

前言

  • 文章比较详细,建议先收藏
  • 下一步:项目的具体测试
  • 有任何问题,评论区留言

单元测试的目的

单元测试的由来

在软件测试的发展过程中衍生的一种测试方法,能够更高效率、更简洁地实现软件系统的模块化的测试。

单元测试的优势

  • 极大程度上节约测试时间
    复杂的交互场景中自动化地帮助自己测试组件,更快速的定位错误代码位置。
  • 测试本身不仅是发现错误,更能预防错误
    测试能够协助开发以更清晰的思路认识自己的代码问题,只有认识到代码问题才能够让代码优化,带给客户更优质的体验。
  • 能够让代码更加简洁
    测试后的代码,逻辑更加清晰,能够减少代码冗余。

Request模块

Request模块简介

介绍

一、说明

​ Request模块是一个模拟用户在客户端发送各种请求的基于HTTP协议的Python库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8V9rGXa-1602593196197)(C:\Users\Pine\Desktop\03.png)]

二、作用

  • 软件测试使用:脚本化发送请求并分析返回数据是否异常
  • 爬虫使用:模拟用户脚本化自动采集网络资源

安装

# 切换虚拟环境!!!  安装镜像源为清华园
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple 

请求方式以及响应

HTTP接口动词说明

在这里插入图片描述

查询参数对应的request模块中的请求方式

查询参数 所有请求方式->requests.get(params={})
请求体 json参数 非get请求方式(post、put、delete)->requests.post(json={})
请求体 form参数 非get请求方式(post、put、delete)->requests.post(data={})
参数传递方式 requests 接收的关键参数名字
请求头参数 所有请求方式->requests.get(headers={})

Request模块使用(Httpbin.org)

httpbin.org说明:之后的测试案例都采用对这个网站进行请求的方案

  1. 作用:测试HTTP的Request请求并返回对应的Response信息,适用于GET、POST各种请求接口
  2. 项目架构:基于Python的Flask框架 开源
  3. Github地址:https://github.com/Runscope/httpbin
  4. 官网:http://httpbin.org/
  • Get 请求

    import requests
    from requests import Request, Session
    
    # 1. 定义URL对象
    # url = 'http://www.meiduo.site:8080/'
    url = 'http://httpbin.org/get'
    # 2. 新建request对象 注意+请求方式
    response = requests.get(url)
    # 3. 检查获取的数据格式以及内容打印到一个文件中
    with open('test1.html', 'w') as f:
        f.write(response.text)
        
    # 4. 可以打印response观察对象类型
    print(type(response))
    # text方法默认打印的返回数据的json格式
    print(response1.text)
    
    # 默认打印的是返回数据二进制数据
    print(response1.content)
    # b'{\n  "args": {}, \n  "headers": {\n    "Accept": "*/*", \n    "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org", \n    "User-Agent": "python-requests/2.23.0", \n    "X-Amzn-Trace-Id": "Root=1-5f818280-636d615a13bf7f173724951b"\n  }, \n  "origin": "106.121.5.147", \n  "url": "http://httpbin.org/get"\n}\n'
    
    • 返回结果展示
      在这里插入图片描述
  • POST请求

    # 1. URL
    URL = 'http://httpbin.org/post'
    # 2. request对象
    json_dict = {
          
          
        'user': '吴亦凡'
    }
    # 传入数据 data:表单数据  json:json数据
    # response = requests.post(URL, data=json_dict)
    # response = requests.get(url,params=json_dict)
    response = requests.post(URL, json=json_dict)
    # 3. 写入文件
    with open('test1.html', 'w', encoding='utf-8') as f:
        f.write(response.text)
    
    • 返回结果
      在这里插入图片描述
  • 响应对象相关属性说明

    # 1.获取响应对象的数据 text(requests 会使用其推测的文本编码)->string类型
    response.text
    # 2.获取响应对象的数据 content->bytes类型
    # decode() 将 bytes-> str
    response.content.decode()
    # 3.前端返回状态码(无论数据是否验证正确,都会返回200状态码,测试判断不能用状态码)
    response.status_code
    # 4.前后端分离开发模式下,存在一个问题无论数据是否验证正确,都会返回200状态码,测试判断不能用状态码  
    # 测试网站登录功能,接口规定登录成功返回json->{'code':0 ,'msg':oK}
    # 4.1获取响应对象的 json数据---json()
    response.json()
    # 5.获取请求头信息
    response.requests.headers
    # 6.获取响应头信息
    response.headers
    # =====防止被反爬添加请求头的信息====
    # 添加请求头信息
    headers = {
          
          
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
    }
    request.get(url, headers=headers)
    
  • Cookie请求

    cookie作用:1. 常用与用户登录后,登录状态信息的保存 2. 缓存一些其他信息

    # ===方法1:request对象中携带cookies====
    # 1. url
    url = 'http://httpbin.org/cookies'
    # 2. 定义cookie值  dict:强制类型转换
    cookies = dict(cokie_is='Richerd')
    # 3. response加上cookie值
    # response = requests.post(URL, data=cookies)
    response = requests.get(url, cookies=cookies)
    # print(response.text)
    # 4. 写入文件
    with open('test1.html', 'w', encoding='utf-8') as f:
        f.write(response.text)
        
    # ===方法2: 直接使用cookie中的RequestsCookieJar()===
    # 这里引入 request.cookie
    # 1.实例化cookie_jar对象
    jar = requests.cookies.RequestsCookieJar()
    # 2.设置cookie
    # jar.set(请求头key值, value值, 域名, 路径)
    jar.set('coo', 'zhenxiang', domain='httpbin.org', path='/cookies')
    # 3.url
    url = 'http://httpbin.org/cookies'
    # 4.获取对象
    response = requests.get(url, cookies=jar)
    print(response.text)
    
  • Session请求

    seesion作用:和cookie类似用于保存一些信息,但浏览器中往往存的是seesionid值,真正的session信息存储在数据库中

    request模块中Session和Cookie的请求登录的区分

    • request.get()

    requests.get() 就可以发送请求, 但是 遇到涉及登录的时候, 应该先发送登录请求->登录成功获取cookies->可以去请求需要登录认证的页面

    * 1.login请求: 携带账户和密码 response = requests.post(url, json=xxx)
    * 2.解析登录成功的:cookie   = response.request._cookies
    * 3.登录用户中心请求 '/mytaobao...':requests.get('<https://i.taobao.com/my_taobao.htm?spm=xxxx', cookies=cookies)
    
    • session.get()
      • 1.先访问的是login的请求-- 携带账户和密码 response = session.post()
      • 2.再发送 info 请求 – session.get(‘info’)
    from requests import Session
    
    # 1.创建session对象
    s = Session()
    # 2.发送带cookie的请求  用url设置一个cookie值 之后的cookie将会一直和session对象绑定
    s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    # 3.获取cookie值 检查是否获取成功
    r = s.get("http://httpbin.org/cookies")
    print(r.text)
    
    
    # 1. 设置 用户名和密码  即设置session对象的auth属性默认值
    s.auth = ('user', 'pass')
    # 2. 可以直接增加请求头中信息(在原有请求头字段中添加)
    s.headers.update({
          
          'test1': 'wangyibo'})
    # 3. 请求头中再次以参数形式添加另一个请求头信息  并检查请求头中是否已经添加成功
    # 注意这里或默认auth之前添加的信息也会一块合并到请求头中
    r = s.get('http://httpbin.org/headers', headers={
          
          'test2': 'yibowangzha'})
    print(r.text)
    
    • 返回结果
      在这里插入图片描述

Django单元测试

注意!:Django每次测试每次测试之前会自动创建一个和项目默认数据库名称相同的default临时数据库,而且临时数据库是复制了项目所有的表但是没有数据,测试用例测试结束后,会把临时创建的default数据库删除

说明

  • Django的单元测试模块继承自Python自带的测试基类unittest
  • postman和unitest的区别
    • Postman适用于接口测试 —>黑盒测试
    • unitest适用于代码逻辑测试 —>白盒测试

数据库环境准备

在Django的settings配置文件中进行对测试数据库配置

DATABASES = {
    
    
    'default': {
    
    
        'ENGINE': 'django.db.backends.mysql',  
        'HOST': '127.0.0.1', 
        'PORT': 3306,  
        'USER': 'root',  
        'PASSWORD': 'mysql',  
        'NAME': 'mysql',
        'OPTION': {
    
    'charset': 'utf8'},
      
       # 配置测试的数据库
       # 1.单元测试需要使用的数据库,测试执行时,会自动创建新的default数据库包含TEST库 
       # 2.每次自动创建的数据库中没有测试数据,而且测试执行完会自动删除测试数据库
       # 3.如果和default配置相同,使用默认配置
        'TEST':{
    
    
          'NAME':'test',
          'CHARSET': 'utf8',  # 设置测试数据库的字符集
          'COLLATION': 'utf8_general_ci',  # 测试数据表中的行的编码 
        }, 
    },
}

Testcase

注意事项!!!

  1. 由于使用的是Django自带的测试模块,所以所有的测试代码都必须在项目的目录下,且为Python的package
  2. 创建的测试函数函数名组成格式 test_xxx
  3. 执行方式为终端执行 python manage.py test 默认一键执行所有的测试代码
  • 执行摸个应用的测试 python manage.py 应用名
  • 执行应用下的某个测试文件 python manage.py 应用名.test_xxx
  • 执行应用下测试文件中的具体测试类 python manage.py 应用名.test_xxx.Demotest
  • 具体到函数 执行应用下测试文件中的具体测试类 python manage.py 应用名.test_xxx.Demotest.test_xxx

setUp和tearDown函数

  • setUp tearDown

    setUp 和 tearDown函数作用就是在测试用例代码执行前/后 做一些准备动作 !!!每个测试用例执行前后都会执行

    使用场景:测试之前打开文件 测试之后关闭文件

    class TestDemo(TestCase):
    
        def setUp(self):
            print('---setup---')
    
        def tearDown(self) -> None:
            print('---tearDown---\n')
    
        def test_two(self):
            print('---222---')
    
        def test_three(self):
            print('---3333---')
    
        def test_four(self):
            print('---4444---')
    
    
    class TestDemo2(TestCase):
    
        def test_two(self):
            print('\n---TestDemo---')
    

    测试结果:

    ---setup---
    ---4444---
    ---tearDown---
    
    ---setup---
    ---3333---
    ---tearDown---
    
    ---setup---
    ---222---
    ---tearDown---
    
    
    ---TestDemo---
    
    
  • @classmthod修饰

    setUpclass 和 tearDownclass 函数在这个类中的所有测试用例执行前后只会执行一次

    使用场景:测试用例测试之前数据库中添加测试数据

    class ThreeTest(TestCase):
    
        @classmethod
        def setUpClass(cls):
            print('setUpClass--每个测试用例类---调用一次')
    
        @classmethod
        def tearDownClass(cls):
            print('tearDownClass---每个测试用例类---调用一次')
    
        def test_baidu(self):
            url = 'http://www.baidu.com'
            response = requests.get(url)
            print(response.status_code)
    
        def test_tencent(self):
            url = 'http://www.tencent.com'
            response = requests.get(url)
            print(response.status_code)
    

    返回结果:

    setUpClass--每个测试用例类---调用一次
    200
    200
    tearDownClass---每个测试用例类---调用一次
    
  • 登录测试

    class LoginTest(TestCase):
    
        def test_login(self):
            url = 'http://www.heiheihei.site:8000/login/'
            json_dict = {
          
          'username': 'wangyibo', 'password': '12345678'}
            response = requests.post(url, json=json_dict)
            print(response.content.decode())
            
        
    # 登录成功返回json字符串
    {
          
          "code": 0, "errmsg": "ok"}
         
    

assert断言捕获异常的方法

# assert 是一个python的关键字  作用是捕获异常  详细说明查看 https://blog.csdn.net/fengtian12345/article/details/80529510

# 正常使用assert函数
assert (1+1==2)
assert (1+1==3)  # 条件为 false时触发  AssertionError 异常


# 测试中使用到的 assert语法   self
self.assert(用于判断的变量,判断值,'如果不符合条件的提示')

Django的TestCase中封装的assert方法:
在这里插入图片描述

Client

说明

  1. client 自带login方法 和session类似可以在client对象在被销毁之前一直携带登录的cookie

  2. client的login不用url地址

  3. client和session发送请求的区别

    session发送的真请求 client发送的伪请求(项目不用运行,直接测试代码)

登录测试两种实现方法

unicode-escape编码集 长这样b``'\\u4e2d\\u56fd'

# ===================方法1:不使用login方法==================
class LoginTestCase(TestCase):
    def setUp(self):
        # 准备数据库数据  加一个用户
        User.objects.create_user(username='liudehua', password='11111111')
        self.client = Client()

    def test_login(self):
        # 1.接口url
        url = '/login/'
        # 2.设置登录用户
        json_dict = {
    
    'username': 'liudehua', 'password': '11111111'}
        # 3.用client对象进行 post登录访问测试  添加content_type='application/json' 确保向后台返回json字符串数据
        response = self.client.post(url, data=json_dict, content_type='application/json')
        # 4.检查返回数据  是否登陆成功
        print(response.content.decode())
        # 5.由于返回json数据可能含有其他数据 我们这里只取后台返回的状态码code
        # 为断言判断取值
        code = json.loads(response.content.decode()).get('code')
        # 6.断言函数 如果code为0则登录成功  否则输出‘登录失败'
        self.assertEqual(code, 0, '登录失败')
        
# =================方法2:使用login函数====================
class LoginTestCase(TestCase):
    def setUp(self):
        # 准备数据库数据  加一个用户
        User.objects.create_user(username='wangyibo', password='11111111')
        self.client = Client()

    def test_login(self):
        # 1.使用login函数直接发送请求
        login_result = self.client.login(request='post', username='wangyibo', password='11111111')
        # 登录成功返回True  失败False
        print(login_result)
        # 2.使用断言函数直接判断是否登录成功
        self.assertEqual(login_result, True, '登录失败')
        # 3.登录成功访问个人中心(检查cookie携带)
        response = self.client.get('/info/')
        # 4.再使用断言判断是否访问成功
        code = json.loads(response.content.decode())['code']
        print(response.content.decode())
        # 检查cookie信息
        print('登录成功之后的cookie:', self.client.cookies)
        # 判断
        self.assertEqual(code, 0, '个人中心访问失败!')

    def tearDown(self) -> None:
        # 退出登录
        self.client.logout()
        # 检查退出登录之后的cookie值是否还在
        print('退出之后的cookie', self.client.cookies)

FactoryRequest

简介

  • 注意事项

    • 只接受get post put delete head options 和trace 方法,这些方法接受的参数大部分相同, 因为它们只是用来生成请求
    • 它不支持中间件。会话和身份验证属性必须由测试本身提供(如果需要)才能使视图正常工作
  • 作用

    模拟一个请求对象,直接可以把请求对象传给指定的视图函数(作为参数),不需要手动指定url地址。

  • 原理

    # 类视图:类名称.as_view()(请求对象)
    # 视图函数:视图函数(请求对象)
    
    python manage.py shell
    # shell中导入
    from django.http import HttpRequest
    from apps.users.views import LoginView
    # 发送请求  登录接口实现
    request = HttpRequest()
    request.method='post'
    request.url = '/login/'
    
    # response = LoginView().post(request)
    response = LoginView.as_view()(request)
    print(response.content)
    

用户中心登录测试

class LoginInfoTestCase(TestCase):

    def setUp(self) -> None:
        # 实例化对象属性
        self.factory_request = RequestFactory()
        # 创建用户
        self.user = User.objects.create_user(
            username='dilireba',
            password='12345678',
        )

    def test_info(self):
        # get方式请求用户中心 创建factory对象
        factory_request = self.factory_request.get('/info/')
        # 携带用户属性 为登录成功
        factory_request.user = self.user
        # 用factory模拟对象访问用户中心视图函数
        response = UserInfoView.as_view()(factory_request)
        print(response.content.decode())
        # 断言检查是否进入成功
        code = json.loads(response.content.decode()).get('code')
        self.assertEqual(code, 0, '访问个人中心失败!')

mock

简介

  • 概念

    注意:老django版本中有这个模块 后来直接封装到python库了

    导包方式

    django < 2.0版本 from django.test import mock
    django > 2.0 版本 from unnitest import mock

    Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的用于发送请求的对象,以达到模拟对象的行为。

  • 实现效果

    import requests
    
    def c(url):
        response = requests.get(url)
    

    mock实现过程:

    使用一个mock对象替换掉上面的requests.get函数,然后执行函数c时,c调用requests.get的返回值就能够由我们的mock对象来决定(若C函数执行成功<不用管具体的函数执行逻辑>接收函数返回值,若不成功mock对象赋予默认值),而不需要服务器的参与

  • 适用场景

    • 在项目开发过程中,尽早介入测试->先把基本功能的输入输出测试。
    • 项目开发差不多,但是服务器架设进度缓慢,可以代替与服务器交互过程,提早测试

mock实操

  • mock函数常用两个参数说明

    • side_effect 表示被调用的函数名
      • 指向一个可调用的对象,一般是函数.当mock对象被调用时,如果该函数返回值不是DEFAULT时,那么以该函数的返回值作为mock对象调用的返回值。
      • 是异常类或者对象时,当mock被调用就会被抛出。
      • 若是可迭代对象,mock每次调用就会返回下一个值。若可迭代对象的元素是异常,就会抛出。
    • return_value 表示被调用的函数失效情况下的返回值
    • 如果被调用的函数,功能暂未实现,可以通过return_value指定默认的返回值,用来在测试用例中进行断言判断
  • mock 小案例

    1. mock基础使用

    # ==============被测试的功能模块===========
    
    mport requests
    # side_effect 表示被调用的函数名
    
    # return_value 表示被调用的函数的返回值
    
    # B功能->发请求
    def send_req(url):
        print('B 功能....调用!')
        resp = requests.get(url)
        return resp.status_code
        pass
    
    
    # A功能->调用B
    def post_func():
        return send_req('http://www.baidu.com')
        pass
    
    # =============测试上述模块:两种情况======================
    
    from . import utils
    from unittest import mock
    from django.test import TestCase
    
    class MockTestCase(TestCase):
        
        def test_B_None(self):
            """ 如果B模块功能未实现"""
            # 1. 实例化mock对象 由于B功能未实现side_effect=None
            mock_obj = mock.Mock(side_effect=None, return_value=200)
            # 2. 替换send_req模块 假设被post_func调用
            utils.send_req = mock_obj
            # 3. 获取返回结果  因为设置了 return_value=200 所以返回结果应该是200
            results = utils.post_func()
            # 4. 断言检查是否为200
            self.assertEqual(results, 200, 'B模块测试失败!')
        
        def test_B_function(self):
            """如果B模块实现"""
            
            # 1.实例化  mock对象  由于B功能实现side_effect=utils.send_req
            mock_obj = mock.Mock(side_effect=utils.send_req, return_value=300)
            # 2.替换 send_req 模块
            utils.send_req = mock_obj
            # 3. 获取返回结果  如果A功能调用B模块成功 则返回访问baidu成功的状态码200
            results = utils.post_func()
            # 4. 断言
            self.assertEqual(results, 200, 'B模块测试失败!')
    

    2. mock 检查是否被调用方法

    class TestMockCalled(TestCase):
        def test_func(self):
            # 1.新建mock对象
            mock_obj = mock.Mock(side_effect=utils.send_req, return_value=400)
            # 2.使用mock对象替换功能B函数
            utils.send_req = mock_obj
            # 3.测试功能B函数访问百度是否成功
            utils.visit_func()
            # 4.断言mock对象是否被调用
            self.assertEqual(mock_obj.called, True)
            #  call('http://www.itcast.cn')
            self.assertEqual(mock_obj.call_args, True)
            #  [call('http://www.itcast.cn')]
            self.assertEqual(mock_obj.call_args_list, True)
    

    3. mock 上下文管理器用法

    # ======测试一个登陆功能的接口视图函数是否可以正常被调用=======
    
    class LoginMockTest(TestCase):
        def test_class(self):
            """模拟类视图1"""
            # 实例化mock对象
            mock_obj = mock.Mock(return_value=200)
            # mock替换
            LoginView.post = mock_obj
            print('>>>test_class run>>>')
            self.assertEqual(LoginView.post(), 200)
    
        def test_context_mock_use(self):
            # 1.实例化 模拟对象
            mock_obj = mock.Mock(return_value=200)
            # 2.使用上下文管理器实现视图具体函数功能测试
            # 参数1表示被测试的类,参数2表示测试类中的方法,参数3表示mock对象
            with mock.patch.object(LoginView, 'post', mock_obj):
                # 3.调用视图函数  此时已经被mock对象替换
                results = LoginView.post()
                print(results)
                # 4.断言判断 post方法是否可以调用
                self.assertEqual(results, 200, '登录失败!')
    

    4. mock的装饰器用法

    • reverse反向解析函数在django中的使用

      # 路由正向
      客户端发请求->url->总路由->子路由->视图函数
      # 路由反向
      视图函数->路由
      
      # 使用reverse的前提
      # 1. 总路由需要起名称  在使用的时候直接使用路由名称即可
       path('', include(('apps.xxx.urls'),namespace="这个总路由的名"))
      # 2. 子路由起名
      path('login/', views.LoginView.as_view(), name='子路由的名'),
      # 3. 调用方法
      reverse('总路由名称:子路由明名称')
      # 4. 后端重定向用法
      redirect(reverse())
      
    • 装饰器形式的mock测试用户登录

      class ReverseTest(TestCase):
      
          def setUp(self) -> None:
              self.client = Client()
              self.url = reverse('User:Login')
              User.objects.create_user(username='wangyibo', password='11111111')
      
          # mock 装饰器 写法
          # @mock.patch('精确模块的路径')
          @mock.patch('apps.users.views.LoginView')
          def test_mock_decorator_order(self, mock_obj):
              # 1.接收 mock对象
              results = mock_obj
              # 2.指定 mock对象添加return_value属性值
              results.post.return_value = 200
              # 3.添加测试用户
              data_dict = {
              
              'username': 'wangyibo', 'password': '11111111'}
              # 4. 测试用 client 发送请求 测试  注意测试的测试对象是被mock替代了的视图函数 side_effect=登录的视图函数
              response = self.client.post(self.url, data=data_dict, content_type='application/json')
              print(response)
              # 4. 断言
              self.assertEqual(response.status_code, 200, '测试失败!')
      
          def test_reverse_url(self):
              # 反向解析输出检查
              order_url = reverse('User:Login')
              print('反向解析的路由:', order_url)
      

Pytestest单元测试

Pytest介绍

pytest是什么

  • 是python的一个用于测试的框架
  • 适用于任何级别、任何代码的测试

pytest的优点

  • 命令行模式工具,编写测试用例简单,丰富的文档参考
  • 支持单元测试和功能测试还有assert断言 支持运行由Unittest编写的测试Case
  • 支持跳过某些测试,对某些预期失败的case标记为失败
  • 第三方插件丰富,可自定义
  • 支持参数化

Pytest环境准备

安装

# !!!最好切换到虚拟环境
workon xxx
pip install pytest

配置文件

pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令运行时会使用该配置文件中的配置

[pytest]

# 1.给 pytest  传参数  三个插件的配置文件中写写法如下
# -s 输出测试用例中的print内容  
# --html 插件 将测试结果输出为报告网页  
# --rerun碰见测试报错重新测试几遍
addopts = -s --html=./a.html --reruns 3

# 2.指定 测试脚本的 路径
testpaths = ./

# 3.指定 测试文件的 名字
python_files = test_a*.py

# 4.指定 测试文件类的 名字  当前的目录下有指定名称文件才会执行
python_classes = Test*

# 5.指定 测试文件函数的 名字
python_functions = test_*

demo

# 文件名    test_demo.py  

import pytest
import requests


def test_one():
    r = requests.get('http://baidu.com')
    assert r.status_code == 200


def test_two():
    r = requests.get('http://baidu.com')
    assert r.encoding == 'utf'

# ======运行结果======
# 第一个测试无报错   第二个报错 结果为 ISO-8859-1 != utf

运行方式

都是在终端环境下运行如下命令

  • pytest -s 运行当前目录下的 所有测试用例

    • -s 作用 就是 如果测试用例通过, 测试函数中的print输出打印内容
  • pytest test_xx.py 执行 test_xx.py文件 中 所有测试用例

  • pytest test_xx.py::类::函数 执行具体的某个测试用例

  • pytest.main([’-s’]) 脚本执行

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

Pytest相关函数使用

Pytest-assert+warning

  • 原生断言

    import requests
    
    
    def test_assert():
        age = 20
    
        # 判断 年龄是不是大于18岁
    
        assert age >= 18, '未成年!'
    
    
    def test_jingdong():
        response = requests.get('http://www.jd.com')
    
        print(response.content.decode())
    
        assert response.status_code == 202, '没成功'
    

- 异常测试

  # ====================测试案例:判断平年闰年================
  def is_leap_year(year):
  
      # 先判断year是不是整型
      if isinstance(year, int) is not True:
          raise TypeError("传入的参数不是整数")
  	# year 不能为0 的取值异常
      elif year == 0:
          raise ValueError("公元元年是从公元一年开始!!")
  	# year 必须为正整数的  类型异常
      elif abs(year) != year:
          raise ValueError("传入的参数不是正整数")
  	# year的平润年判断
      # 以下代码  和 异常类型 无关
      elif (year % 4 ==0 and year % 100 != 0) or year % 400 == 0:
          print("%d年是闰年" % year)
          return True
      else:
          print("%d年不是闰年" % year)
          return False
  # 一定要注意的是:站在测试的角度去写代码   
  # 我们测试报错是否在业务逻辑中实现  而不是要看到测试的报错信息  如果测试OK不会有报错的
  
  # =================raise()方法的使用================
  class TestException():
  
      def test_ValueError1(self):
          with pytest.raises(ValueError):
              utils.is_leap_year(-5)
  
      def test_TypeError(self):
          with pytest.raises(TypeError) as info:
              utils.is_leap_year(6.6)
          # 这里也可以搭配 assert方法提示报错信息
          assert info.type == TypeError
  
      def test_ValueError2(self):
          # match对应的错误信息 因为数值错误有两个 可能会匹配到其中任意一个
          # 如果 异常类型 相同 对比不出来, 需要借助异常中的提示信息 二次
          # match的实现底层借用 re.search() 方法  寻找符合条件的字符串提示信息进行具体的错误类型匹配
          with pytest.raises(ValueError, match="公元元年"):
              utils.is_leap_year(0)
  
      def test_ValueError3(self):
          with pytest.raises(ValueError, match="正整数"):
              utils.is_leap_year(-9)
  
      def test_leap_year(self):
          assert utils.is_leap_year(2001) == True, "不是闰年"
  

- 警告测试

  # =================测试案例:几个警告的提示函数=============
  class MakeWarns():
      # 1. 弃用警告 函数
      def make_decotat_warns(self):
          warnings.warn('deprecate', DeprecationWarning)
  
      # 2. 用户警告 函数
      def make_user_warns(self):
          warnings.warn('user', UserWarning)
  
      # 3. 语法警告 函数
      def make_syntax_warns(self):
          warnings.warn('syntax', SyntaxWarning)
  
      # 4. 字节警告 函数
      def make_bytes_warns(self):
          warnings.warn('bytes', BytesWarning)
  
      # 5. 运行时 runtime警告 函数
      def make_runtime_warns(self):
          warnings.warn('runtime', RuntimeWarning)
  
      # 6. 无警告 函数
      def not_warns(self):
          pass
  	# 7. 很多警告
      def many_warns(self):
          warnings.warn('runtime', RuntimeWarning)
          warnings.warn('user', UserWarning)
  
  # ===================测试用例============
  import warning
  
  class TestWarnsType():
  
      def test_deprecate(self):
          # 测试弃用警告
          with pytest.warns(DeprecationWarning):
              MakeWarns().make_decotat_warns()
  
      def test_bytes(self):
          # 测试字节警告
          with pytest.warns(BytesWarning):
              MakeWarns().make_bytes_warns()
  
      def test_sync(self):
          # 测试语法警告
          with pytest.warns(SyntaxWarning, match='syntax'):
              MakeWarns().make_syntax_warns()
  
      def test_many_warning(self):
          # 测试 很多警告 中Runtime是否生效
          with pytest.warns(RuntimeWarning) as record:
              MakeWarns().many_warns()
                
      def test_as(self):
          """warning的几个信息查看用法"""
          with pytest.warns(UserWarning) as warninfo:

            make_warning.warn_message()

          print('\n', warninfo)
          print(len(warninfo))
          print(warninfo[0].message)
          print(warninfo[1].message)

          assert len(warninfo) == 2
          assert str(warninfo[0].message) == "user"
          assert str(warninfo[1].message) == "runtime"

Pytest-setup/teardown

类似于django的Teatcase中的setUp和tearDown函数

一、函数分类及介绍

  • 函数级别
    • setup_function和teardown_function
      • 只针对测试类 外面的 测试函数 每个函数执行前后都会执行
    • setup_method和teardown_method
      • 只针对测试类 里面的 测试函数 类中每个函数执行前后都会执行
    • setup_class和teardown_class
      • 一个测试用例类 只调用一次
  • 模块
    • setup_module和teardown_module
      • 一个py测试文件只执行一次

二、优先级顺序

  • 测试代码中不建议同时全部使用
  • 先后顺序
    • setup执行先后顺序
      • module>class>method 类里面的测试函数
      • module>function 单独测试函数的
    • teardown执行先后顺序
      • method>class>module 类里面的测试函数
      • function>moudle 单独测试函数的

三、Demo演示

# =============优先级查看代码=============

def setup_module():
    print('\n>> setup_module  setup_module ...')


def teardown_module():
    print('\n>> teardown_module  teardown_module ...')


def test_one():
    print('\n>>> test_one 111....')


def test_two():
    print('\n>>> test_two 222....')


class TestSetUpMethod():

    def setup_class(self):
        print('\n>> setup_class ...')

    def teardown_class(self):
        print('\n>> teardown_class ...')

    def setup_method(self):
        print('\n>>> setup_method ...')

    def teardown_method(self):
        print('\n>>> teardown_method ...')

    def test_three(self):
        print('\n>>> test_three 333....')

    def test_four(self):
        print('\n>>> test_four 444....')


class TestSetUpMethodTwo():

    def test_three(self):
        print('\n>>> test_three 555....')

    def test_four(self):
        print('\n>>> test_four 666....')

# =================输出结果==============
setup_module  setup_module ...

>>> test_one 111....
.
>>> test_two 222....
.
>> setup_class ...

>>> setup_method ...

>>> test_three 333....
.
>>> teardown_method ...

>>> setup_method ...

>>> test_four 444....
.
>>> teardown_method ...

>> teardown_class ...

>>> test_three 555....
.
>>> test_four 666....
.
>> teardown_module  teardown_module ...

Pytest-fixture

一 、fixture是什么

  • fixture是pytest特有的功能,它用pytest.fixture标识,以装饰器形式定义在函数上面,在编写测试函数的时候,可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数.

二、 场景举例

  • 如果测试类中 5个测试用例 , 其中 有 3个测试用例需要登录, 2个不需要
    • setup_method 可以实现 但是不够灵活
    • 直接使用fixture就可以实现哪个需要给哪个

三、 使用

@pytest.fixture()
def user_login():
    # 实现用户登录
    print('用户登录成功')

# 测试用例:有些要求用户必须登录,有些要求可以不登录访问
def test_user_orders(user_login):
    print('>>>test_user_orders run >>>')

def test_payment_status(user_login):
    print('>>>test_payment_status run >>>')

# 项目首页,任何人都可以访问
def test_index():
    print('>>>test_index run >>>')
# ============不同级别情况下使用fixture的执行次数==========

# function级别共执行3次
# class级别共执行2次
# module级别共执行1次
# session级别共执行1次

# 定义fixture
@pytest.fixture(scope='function')
def user_login():
    # 实现用户登录
    print('用户登录成功')
# 在类外面使用fixture,在类外面依然可以使用
def test_1(user_login):
    print('>>>test_1 run>>>')

# 在类外面使用fixture,在类里面依然可以使用
class TestLogin(object):
    # 测试用例:有些要求用户必须登录,有些要求可以不登录访问
    def test_user_orders(self, user_login):
        print('>>>test_user_orders run >>>')

    def test_payment_status(self, user_login):
        print('>>>test_payment_status run >>>')

四、fixture函数共享

  • 作用
    • 给所有的测试用例添加可以直接调用的功能
  • 实现方式
    • 在测试文件目录下新建 conftest.py 文件 (文件名不能修改!!!)
    • 在这个文件中定义 通用的 可能需要被测试类调用的函数 pytest执行测试时会自动调用
  • demo举例
# ==========================文件名:conftest.py======================
import pytest

"""
function:对所有的测试函数生效,不同的测试文件中执行多次;
class:对所有的测试类和测试函数生效,不同的测试文件中执行多次
module:对所有的测试类和测试函数生效,一个测试文件,只执行一次

session:对所有的测试类和测试函数生效,多个测试文件,只执行一次

"""


# 添加功能: 登录成功输出信息
@pytest.fixture(scope='function')
# @pytest.fixture(scope='class')
# @pytest.fixture(scope='module')
# @pytest.fixture(scope='session')
def user_login():
    print('\n用户登录成功\n')

# ========================文件名:fixture_test.py===============
import pytest


# 使用共享文件中的fixture装饰的的user_login的功能函数
def test_1(user_login):
    print('\n>>> test_1 run >>>\n')


def test_2():
    print('\n>>> test_2 run >>>\n')


# fixture标记函数共享文件的使用
@pytest.mark.usefixtures('user_login')
class TestOrders(object):
    """
    fixture标记函数共享文件的使用:
    1.可以直接传给测试类中的指定测试函数,只对测试函数生效
    2.也可以直接把测试类使用fixture进行标记,对该类中的所有测试函数都生效
    """

    def test_get_orders(self):
        print('\n>>> test_get_orders run >>>\n')

    def test_get_goods(self):
        print('\n>>> test_get_goods run >>>\n')


五、fixture的参数化

  • 三个参数说明

    • scope 作用域:即 function、class、module、session 这四个级别
      • function:即测试 类外的测试函数、类中的测试用例函数 运行多次
      • class:即作用测试用例类 一个类一次
      • Module:作用整个py模块 一个模块一次
      • Sessioon:作用整个一次的测试包含所有的测试用例 pytest一次
    • param:List类型,默认None, 接收参数值,对于param里面的每个值,fixture都会去遍历执行一次.
    • autouse:Bool类型,默认Ture 为true时此session中的所有测试函数都会调用fixture
  • demo

    import pytest
    from _pytest.fixtures import SubRequest
    
    # 1.多个参数列表
    user_list = ['dlireba', 'oyangnanan']
    pwd_list = ['111111', '222222']
    
    
    # 2.多个fixture
    # params参数的用法
    @pytest.fixture(params=user_list)
    def user_name(request):
        return request.param
    
    
    @pytest.fixture(params=pwd_list)
    def pwd_content(request):
        return request.param
    
    # scope参数的用法
    # @pytest.fixture(scope='function')
    @pytest.fixture(scope='class')
    # @pytest.fixture(scope='module')
    # @pytest.fixture(scope='session')
    def pwd_content(request):
        return 'scope测试'
    
    
    # 3.给测试函数 多个fixture
    class TestOrders(object):
    
        # 调用  fixture装饰的函数   会自动遍历匹配   一共会生成4个测试用例
        def test_get_orders(self, user_name, pwd_content):
            print('\n从fixture传过来的name数据', user_name)
            print('从fixture传过来的pwd数据', pwd_content)
            print('>>> test_fixture2 run >>>\n')
    
    # ===   autouse参数  一般情况下不用,影响测试灵活性======
    # True  一旦执行pytest后的所有的测试用例将会自动被传递hell()函数
    # False  默认关闭此功能
    @pytest.fixture(autouse=True)
    def hello():
        print('\nhello world 2020\n')
    
    

Pytest-mark

一、作用以及常用的5个标记函数

  • 作用

    标记测试用例,以满足在某些特定场景下测试用例的特殊要求实现的功能

  • 6个标记函数

    • pytest.mark.usefixtures 给测试用例 添加fixture
    • pytest.mark.xfail 标识测试用例预期失败
    • pytest.mark.skip 没有限制条件情况下跳过测试用例
    • pytest.mark.skipif 有限制条件情况下跳过测试用例
    • pytest.fixture(params=[]) 直接给fixture函数传递参数
    • pytest.mark.parametrize() 直接给测试用例函数传递参数

二、6个标记函数的使用

import pytest


# fixture标记函数共享文件的使用
@pytest.mark.usefixtures('user_login')
class TestMark():

    def test_one(self):
        print('\n test_one .....')
        assert 1

    """
        pytest.mark.xfail() 
        如果不写参数  默认预期失败
        如果有参数   condition reason 必须都写
    """

    @pytest.mark.xfail(condition=False, reason="测试 登录失败! ")
    def test_two(self):
        print('\n test_two .....')
        assert 0, '测试失败了!'

    """
        无条件 跳过
    """

    @pytest.mark.skip()
    def test_three(self):
        print('\n test_three .....')
        assert 1

    """
        有条件 跳过
        pytest.mark.skipif() 
        如果 不写参数 --默认就跳过
        写了参数   必须两个一起写 condintion  reason
    """

    @pytest.mark.skipif(condition=True, reason='跳过的原因')
    def test_four(self):
        print('\n test_four .....')
        assert 1

    username = "user,pwd"
    values = [('reba', '12345678'), ('oyangnanan', '99999999')]
    """
        key   -argnames     参数类型-string  -- "k1,k2,k3"
        value -argvalues    参数类型-list    -- [v1,v2,v3]
    """

    @pytest.mark.parametrize(username, values)
    def test_five(self, user, pwd):
        print('\n test_five .....')
        print('\n 测试函数接收外界的参数:', user)
        print('\n 测试函数接收外界的参数:', pwd)
        print('\n parametrize测试用例 .....')
        assert 1

    """
        之前的fixture参数传递
    """

    @pytest.fixture(params='fixture_param_test')
    def test_six(self, request):
        print('\n test_six .....')
        return request.param

    # 传递后使用
    def test_seven(self, test_six):
        print('\n test_seven .....')
        print('\nfixture的param测试:', test_six)
        assert 1

Pytest的插件

三个常用插件: pytest-html pytest-orderingrun pytest-rerunfailures

安装:

pip install pytest-html -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pytest-ordering -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install pytest-rerunfailures -i https://pypi.tuna.tsinghua.edu.cn/simple

Pytest-html-生成测试报告

两种使用方式

  • 终端命令行:pytest 测试文件.py --html=存储路径/report.html
  • 配置文件中修改:appopts= -s --html=./report.html

Pytest-orderingrun-控制函数执行顺序

import pytest
  
# 通过插件,指定测试函数执行的顺序
"""
  正整数:值越小,先执行
  负数:值越小,先执行
  正负数:优先执行正数,最后执行负数,值越小,先执行
  0:优先执行0,然后正数、负数
"""
  
class TestOrdering(object):
    # 默认从上往下执行  !!直接使用标记函数的方式进行调用!!
    @pytest.mark.run(order=1)
    def test_login(self):
        print('\n>>>登录执行>>>\n')
  
    @pytest.mark.run(order=0)
    def test_register(self):
        print('\n>>>注册执行>>>\n')
  
    @pytest.mark.run(order=2)
    def test_user_info(self):
        print('\n>>>获取用户信息执行>>>\n')

Pytest-rerunfailures-重测试失败的用例

**作用 **

  • 就只在某种情况下(网络阻塞、软件卡顿),测试用例测试过程异常测试失败,进行自动重新测试

两种使用方式

  • 终端命令行:pytest 测试文件.py --reruns 重试次数
  • 配置文件中修改:appopts= -s --reruns 重试次数

Yaml

Yaml是什么

一、介绍

​ 一种支持所有编程语言的序列化标准,用来存储数据,类似于json

二、语法规则

  • 大小写敏感
  • 缩进表示层级关系, 不建议使用tab键 缩进空格数量不重要,重要的是同层级对齐即可

三、种数据类型

  • object(对象):

    键值对的集合,又称为映射/hash/字典 —> python中dict

    dilireba: 18
    	oyangnana: kavayi
    		wangyibo: cool
    
  • array(数组):

    一组按照次序排列的值,又称序列(sequence)、列表 —> python中list

    dilireba: [18, 18, 18]
    nazha:
    	- 18
    	- 18
    	- 18
    
  • scalars(纯量):

    单个的、不可再分的值, 包括 字符串、布尔值、整数、浮点数、null、日期 —>python中的不可拆分的数据类型

    animal: pets # !!!!!注意冒号后面必须要有一个空格!!!
    	dog: beibei
    # 字符串
    value: "hello"
    转换为python代码
    {
          
          "value":"hello"}
    
    # 布尔值
    value1: true
    value2: false
    转换为python代码
    {
          
          'value1': True, 'value2': False}
    
    # 整数,浮点数
    value1: 12
    value2: 12.102
    # 转换为python代码
    {
          
          'value1': 12, 'value2': 12.102}
    
    # 空(Null)
    value1: ~ # ~ 表示为空
    转换为python代码
    {
          
          'value1': None}
    
    # 日期
    value1: 2017-10-11 15:12:12
    转换为python代码
    {
          
          'languages': {
          
          'value1': datetime.datetime(2017, 10, 11, 15, 12, 12)}}
    
  • 锚点的使用

    锚点类似于一个变量,只不过其中只存储着value供别人调用

    data: &ok   # & 是锚点标示符号
        value: 456
    name:
        value1: 123
        <<: *ok # "<<:" 合并到当前位置,"*imp" 引用锚点imp
    
    # 转换为python代码  注意字典是无序的  
    # 锚点本身字典也会被pyYAML转换(下节)
        {
          
          'data': {
          
          'value': 456}, 'name': {
          
          'value': 456, 'value1': 123}}
    

PyYAML解析

一、作用

  • 一个python库用于对yaml文件进行解析,解析为字典格式的数据
  • yaml->json->dict

二、安装

  • pip3 install PyYAML

三、实操

# ========================yaml格式文件===================


data_one: zhangsan
data_dict: {
    
    'user':'A', 'pwd':'123456'}

data_list:
  - a
  - b
  - c

data_list_two: [1,2,3,4]

# 纯量  单个变量
str_one: "abcdefg"
bool_data: true
int_data: 100
float_data: 1.2
# 转换后 呈现格式都是 None
null_data: ~ # ~
null_one: null
null_two: None

datetime_data: 2020-10-13 16:14:24
date_two: 2020-10-13
# 这种时间格式不支持  会转换成秒数输出
time_data: 16:14:28
# ===========================测试代码=======================
import yaml
from pprint import pprint


def test_parse_yaml_file1():
    f = open('test.yaml', 'r')
    # data = f.read()

    # 1.读取解析 yaml文件的数据 Loader默认为空  但是不赋值会有警告
    data = yaml.load(f, Loader=yaml.FullLoader)
    # 字典 格式
    print(type(data))

    # 2.将数据  写入yaml文件
    f_write = open('test.yaml', 'a')
    # 新建一个字典数据
    data_dict = {
    
    'username': '吴亦凡'}
    # 转换格式写入
    yaml.dump(data_dict, f_write, allow_unicode=True)


def test_parse_yaml_file2():
    # 打开
    f = open('test.yaml', 'r')
    # 转换为字典
    data = yaml.load(f, Loader=yaml.FullLoader)
    # 优化打印出来的数据效果 pprint
    pprint(data)

猜你喜欢

转载自blog.csdn.net/Pei_Jobs/article/details/108980746