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 = <test_class.TestClass object at 0x7fbf54cf5668>
    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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374