pytest小白之从入门到实战

初次接触测试框架的你,肯定希望能更快速的编写自己的测试代码,那么我们开始吧!

1.Pytest介绍

pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但更简洁并高效。
官方网站优点简介:

  • 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
  • 能够支持简单的单元测试和复杂的功能测试
  • 支持参数化
  • 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
  • 支持重复执行失败的case
  • 支持运行由nose, unittest编写的测试case
  • 具有很多第三方插件,并且可以自定义扩展
  • 方便的和持续集成工具集成
    介绍到此吧,开始入手吧

2. 安装pytest

pip install -U pytest
easy_install -U pytest
二选一即可

安装完成验证安装的版本

py.test --version

3. 官方实例

  • 通过该实例,基本有个认识

    # content of test_sample.py
     
    def func(x):
        return x+1
     
    def test_func():
        assert func(3) == 5
    

    在文件所在目录打开终端,输入pytest即可执行并查看成功or失败原因了

  • 多测试case

    # content of test_class.py
     
    class TestClass:
        def test_one(self):
            x = "this"
            assert 'h' in x
     
        def test_two(self):
            x = "hello"
            assert hasattr(x, 'check')
    

    运行:

    pytest test_class.py
    

    执行结果如下

    $ py.test -q test_class.py
    .F
    ================================= FAILURES =================================
    ____________________________ TestClass.test_two ____________________________
    self = 
    def test_two(self):
    x = "hello"
    > assert hasattr(x, 'check')
    E assert hasattr('hello', 'check')
    test_class.py:8: AssertionError
    1 failed, 1 passed in 0.01 seconds
    

4.编写测试样例与规范

  • python命名规范

    一 包名、模块名、局部变量名、函数名
      全小写+下划线式驼峰
      example: this_is_var
    二 全局变量
      全大写+下划线式驼峰
      example: GLOBAL_VAR
    三 类名
      首字母大写式驼峰,否则会报错提示语法错误
      example: ClassName()
    
  • 测试用例规则

    - 测试文件以test_开头(以_test结尾也行)
    - 测试类以Test开头,并且不能带有__init__方法
    - 测试函数以test_开头
    - 断言使用基本的assert即可
    

5.入门实战之批量测试

这里有一个实际应用,我想批量检查一批机器上的CPU,内存,和机器上的2个分区,并将CPU大于80%,内存大于95,和分区大于80%的机器找出来,如何实现呢?
假设这里已经提供好了API,可以读取到所有机器上的CPU、内存、分区信息,API地址为http://api/latestMeteris?userCode=xxx&token=xxx&host=’172.20.116.70,172.20.116.72’&service=CPU,Memory,Disk

访问API时返回一串JSON,信息如下:

{
    "message":"success",
    "result":"success",
    "start":"2017-02-28 13:54:53",
    "data":{
        "Memory":{
            "172.20.116.72":{
                "swap_used":["9.60%"],
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "ram_used":["25.52%"]
            },
            "172.20.116.70":{
                "swap_used":["6.17%"],
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "ram_used":["25.97%"]
            }
        },
        "CPU":{
            "172.20.116.72":{
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "cpu_prct_used":["3.00%"]
            },
            "172.20.116.70":{
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "cpu_prct_used":["1.00%"]
            }
        },
        "Disk":{
            "172.20.116.72":{
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "/export":["25.06%"],
                "/":["21.6%"]
            },
            "172.20.116.70":{
                "datetime":["2017-02-28 13:54:41"],
                "merge_time":["2017-02-28 13:54:41"],
                "/export":["44.68%"],
                "/":["36.15%"]
            }
        }
    },
    "host_size":2,
    "end":"2017-02-28 13:54:53"
}

pytest脚本如下


#!/usr/bin/python
 
import os
import sys
import json
import urllib
import urllib2
import pytest
 
iplist = ["172.20.116.70", "172.20.116.72"]    #定义IP列表
ips = ','.join(iplist)
 
url = 'http://api/latestMeteris?userCode=xxx&token=xxx&host=' + ips + '&service=CPU,Memory,Disk'
req = urllib.urlopen(url)
result = req.read()   #get a string type
 
a = json.loads(result)  #transfer string type to dict type
 
@pytest.mark.parametrize('ip', iplist)
def test_cpu(ip):
    value = a["data"]["CPU"][ip]["cpu_prct_used"][0]
    assert float(value.strip("%")) < 80
 
@pytest.mark.parametrize('ip', iplist)
def test_memory(ip):
    value = a["data"]["Memory"][ip]["ram_used"][0]
    assert float(value.strip("%")) < 95
 
@pytest.mark.parametrize('ip', iplist)
def test_disk(ip):
    value_root = a["data"]["Disk"][ip]['/'][0]
    value_export = a["data"]["Disk"][ip]['/export'][0]
    assert float(value_root.strip("%")) < 80 and float(value_export.strip("%")) < 80

运行结果如下


$ py.test 2.py 
========================= test session starts =========================
platform linux2 -- Python 2.7.4, pytest-3.0.6, py-1.4.31, pluggy-0.4.0
rootdir: /home/zhukun/0224, inifile: 
collected 6 items 
 
2.py ......
 
====================== 6 passed in 0.05 seconds ======================

表示6个用例全部通过。

  • @pytest.mark.parametrize('ip', iplist)

    至此,可能有些人会和我一样,对于其中的@pytest.mark.parametrize('ip', iplist)很困惑,虽然我知道这是一个装饰器,但是并不了解它有什么作用,其实很简单,他就是把iplist这个参数分解,并依次赋值给ip这个参数,类似于for ip in iplist:,所以iplist中有几个参数,该方法就会运行几次

6. pytest高级进阶——Fixture

  • 何为fixture

    fixture是pytest的一个闪光点,fixture是pytest特有的功能,它用pytest.fixture标识,定义在函数前面。在你编写测试函数的时候,你可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数。

    fixture有明确的名字,在其他函数,模块,类或整个工程调用它时会被激活。
    fixture是基于模块来执行的,每个fixture的名字就可以触发一个fixture的函数,它自身也可以调用其他的fixture。

    fixture主要的目的是为了提供一种可靠和可重复性的手段去运行那些最基本的测试内容。比如在测试网站的功能时,每个测试用例都要登录和退出,利用fixture就可以只做一次,否则每个测试用例都要做这两步也是冗余。

  • Fixture入门实例

    把一个函数定义为Fixture很简单,只能在函数声明之前加上“@pytest.fixture”。其他函数要来调用这个Fixture,只用把它当做一个输入的参数即可。
    test_fixture_basic.py

    import pytest
    
    @pytest.fixture()
    def before():
        print '\nbefore each test'
    
    def test_1(before):
        print 'test_1()'
    
    def test_2(before):
        print 'test_2()'
        assert 0
    

    下面是运行结果,test_1和test_2运行之前都调用了before,也就是before执行了两次。默认情况下,fixture是每个测试用例如果调用了该fixture就会执行一次的。

    C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_basic.py
    ============================= test session starts =============================
    platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe
    cachedir: .cache
    metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd
    k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}}
    rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile:
    plugins: metadata-1.3.0, html-1.14.2
    collected 2 items 
    
    test_fixture_basic.py::test_1
    before each test
    test_1()
    PASSED
    test_fixture_basic.py::test_2
    before each test
    test_2()
    FAILED
    
    ================================== FAILURES ===================================
    ___________________________________ test_2 ____________________________________
    
    before = None
    
        def test_2(before):
            print 'test_2()'
    >       assert 0
    E       assert 0
    
    test_fixture_basic.py:12: AssertionError
    ===================== 1 failed, 1 passed in 0.23 seconds ======================
    
    • 调用fixture有三种方式,个人推荐如下自动调用,了解更多可查看底部引用

      使用autos调用fixture

      fixture decorator一个optional的参数是autouse, 默认设置为False。
      当默认为False,就可以选择用上面两种方式来试用fixture。
      当设置为True时,在一个session内的所有的test都会自动调用这个fixture。
      权限大,责任也大,所以用该功能时也要谨慎小心。

      import time
      import pytest
      
      @pytest.fixture(scope="module", autouse=True)
      def mod_header(request):
          print('\n-----------------')
          print('module      : %s' % request.module.__name__)
          print('-----------------')
      
      @pytest.fixture(scope="function", autouse=True)
      def func_header(request):
          print('\n-----------------')
          print('function    : %s' % request.function.__name__)
          print('time        : %s' % time.asctime())
          print('-----------------')
      
      def test_one():
          print('in test_one()')
      
      def test_two():
          print('in test_two()')
      

      从下面的运行结果,可以看到mod_header在该module内运行了一次,而func_header对于每个test都运行了一次,总共两次。该方式如果用得好,还是可以使代码更为简洁。
      但是对于不熟悉自己组的测试框架的人来说,在pytest里面去新写测试用例,需要去了解是否已有一些fixture是module或者class级别的需要注意。

      C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_auto.py
      ============================= test session starts =============================
      platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe
      cachedir: .cache
      metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd
      k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}}
      rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile:
      plugins: metadata-1.3.0, html-1.14.2
      collected 2 items 
      
      test_fixture_auto.py::test_one
      -----------------
      module      : test_fixture_auto
      -----------------
      
      -----------------
      function    : test_one
      time        : Sat Mar 18 06:56:54 2017
      -----------------
      in test_one()
      PASSED
      test_fixture_auto.py::test_two
      -----------------
      function    : test_two
      time        : Sat Mar 18 06:56:54 2017
      -----------------
      in test_two()
      PASSED
      

      fixture scope

      • function:每个test都运行,默认是function的scope
      • class:每个class的所有test只运行一次
      • module:每个module的所有test只运行一次
      • session:每个session只运行一次
    • fixture 返回值

      在上面的例子中,fixture返回值都是默认None,我们可以选择让fixture返回我们需要的东西。如果你的fixture需要配置一些数据,读个文件,或者连接一个数据库,那么你可以让fixture返回这些数据或资源。

      如何带参数
      fixture还可以带参数,可以把参数赋值给params,默认是None。对于param里面的每个值,fixture都会去调用执行一次,就像执行for循环一样把params里的值遍历一次。
      test_fixture_param.py

      import pytest
      
      @pytest.fixture(params=[1, 2, 3])
      def test_data(request):
          return request.param
      
      def test_not_2(test_data):
          print('test_data: %s' % test_data)
          assert test_data != 2
      

      可以看到test_not_2里面把用test_data里面定义的3个参数运行里三次。

      C:\Users\yatyang\PycharmProjects\pytest_example>pytest -v -s test_fixture_param.py
      ============================= test session starts =============================
      platform win32 -- Python 2.7.13, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- C:\Python27\python.exe
      cachedir: .cache
      metadata: {'Python': '2.7.13', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'py': '1.4.32', 'pytest': '3.0.6', 'pluggy': '0.4.0'}, 'JAVA_HOME': 'C:\\Program Files (x86)\\Java\\jd
      k1.7.0_01', 'Plugins': {'html': '1.14.2', 'metadata': '1.3.0'}}
      rootdir: C:\Users\yatyang\PycharmProjects\pytest_example, inifile:
      plugins: metadata-1.3.0, html-1.14.2
      collected 3 items 
      
      test_fixture_param.py::test_not_2[1] test_data: 1
      PASSED
      test_fixture_param.py::test_not_2[2] test_data: 2
      FAILED
      test_fixture_param.py::test_not_2[3] test_data: 3
      PASSED
      
      ================================== FAILURES ===================================
      ________________________________ test_not_2[2] ________________________________
      
      test_data = 2
      
          def test_not_2(test_data):
              print('test_data: %s' % test_data)
      >       assert test_data != 2
      E       assert 2 != 2
      
      test_fixture_param.py:9: AssertionError
      ===================== 1 failed, 2 passed in 0.24 seconds ======================
      

本文从pytest基础到深入使用fixture进行了讲解,谢谢支持

参考文章
使用pytest进行批量测试

python单元测试框架pytest简介

Pytest高级进阶之Fixture

你可能感兴趣的:(pytest小白之从入门到实战)