1. 程式人生 > >Pytest(單元測試框架)

Pytest(單元測試框架)

一 安裝

1.1 安裝包安裝

  1進入下載包路徑

  2.python setup install

  3 安裝出現許可權問題:

      3.1.mac/linux 新增sudo,執行:sudo python setup install

      3.2.windows 管理員方式執行cmd視窗,執行:python setup install

1.2 命令列安裝

1.mac/linux:sudo pip3 install -U pytest # -U:可以理解為--upgrade,表示已安裝就升級為最新版本

2.管理員方式執行cmd:pip3 install -U pytest

1.3 安裝成功校驗:

1.進入命令列

 2.執行:pytest --version # 會展示當前已安裝版本

1.4 Pytest執行方式

      1.測試類主函式模式

  pytest.main("-s  test_abc.py")

       2.命令列模式

  pytest 檔案路徑/測試檔名

  例如:

      pytest ./test_abc.py

1.5 例子

import pytest # 引入pytest包

def test_a(): # test開頭的測試函式

    print("------->test_a")

    assert 1 # 斷言成功

def test_b():

    print("------->test_b")

    assert 0 # 斷言失敗

if __name__ == '__main__':

    pytest.main("-s  test_abc.py") # 呼叫pytest的main函式執行測試

    執行結果:

        test_abc.py

        ------->test_a

        . # .(代表成功)

        ------->test_b

        F # F(代表失敗)

二 Pytest的setup和teardown函式

1 概述

  1.setup和teardown主要分為:模組級,類級,功能級,函式級。

  2.存在於測試類內部

2 函式級別setup()/teardown()

      運行於測試方法的始末,即:執行一次測試函式會執行一次setup和teardown

      程式碼示例: 

import pytest
class Test_ABC:

    # 函式級開始

    def setup(self):

        print("------->setup_method")

    # 函式級結束

    def teardown(self):

        print("------,->teardown_method")

    def test_a(self):

        print("------->test_a")

        assert 1

    def test_b(self):

        print("------->test_b")

if __name__ == '__main__':

    pytest.main("-s  test_abc.py")

      執行結果:

          ------->setup_method # 第一次 setup()

          ------->test_a

          .

          ------->teardown_method # 第一次 teardown()

          ------->setup_method # 第二次 setup()

          ------->test_b

          .

          ------->teardown_method # 第二次 teardown()

3 類級別

      運行於測試類的始末,即:在一個測試內只執行一次setup_class和teardown_class,不關心測試類內有多少個測試函式。

      程式碼示例:         

import pytest

class Test_ABC:

    # 測試類級開始
    def setup_class(self):

        print("------->setup_class")

    # 測試類級結束

    def teardown_class(self):

        print("------->teardown_class")

    def test_a(self):

        print("------->test_a")

        assert 1

    def test_b(self):

        print("------->test_b")

    if __name__ == '__main__':

        pytest.main("-s  test_abc.py")

      執行結果:

          ------->setup_class # 第一次 setup_class()

          ------->test_a

          .

          ------->test_b

          F

          ------->teardown_class # 第一次 teardown_class()

三 配置檔案

pytest的配置檔案通常放在測試目錄下,名稱為pytest.ini,命令列執行時會使用該配置檔案中的配置.

1 配置pytest命令列執行引數

    [pytest]

    addopts = -s ... # 空格分隔,可新增多個命令列引數 -所有引數均為外掛包的引數

2 配置測試搜尋的路徑

    testpaths = ./scripts  # 當前目錄下的scripts資料夾 -可自定義

3 配置測試搜尋的檔名

    python_files = test_*.py 

    # 當前目錄下的scripts資料夾下,以test_開頭,以.py結尾的所有檔案 -可自定義

4 配置測試搜尋的測試類名

    python_classes = Test_* 

    # 當前目錄下的scripts資料夾下,以test_開頭,以.py結尾的所有檔案中,以Test_開頭的類 -可自定義

5 配置測試搜尋的測試函式名

    [pytest]

    python_functions = test_* 

    # 當前目錄下的scripts資料夾下,以test_開頭,以.py結尾的所有檔案中,以Test_開頭的類內,以test_開頭的方法 -可自定義

四 常用外掛

    前置條件:

        1.檔案路徑:

            - Test_App

            - - pytest.ini

        2.pyetst.ini配置檔案內容:

            [pytest]

            # 命令列引數

            addopts = -s

            # 搜尋檔名

            python_files = test_*.py

            # 搜尋的類名

            python_classes = Test_*

            # 搜尋的函式名

            python_functions = test_*

1 Pytest測試報告

  通過命令列方式,生成xml/html格式的測試報告,儲存於使用者指定路徑。

    外掛名稱:pytest-html

    安裝方式:

        1.安裝包方式 python setup.py install

        2.命令列 pip3 install pytest-html

    使用方法:

        命令列格式:pytest --html=使用者路徑/report.html

    示例:       

import pytest

class Test_ABC:

    def setup_class(self):

        print("------->setup_class")

    def teardown_class(self):

        print("------->teardown_class")

    def test_a(self):

        print("------->test_a")
        assert 1

    def test_b(self):

        print("------->test_b")

        assert 0 # 斷言失敗

    執行方式:

        1.修改Test_App/pytest.ini檔案,新增報告引數,即:addopts = -s --html=./report.html

            # -s:輸出程式執行資訊

            # --html=./report.html 在當前目錄下生成report.html檔案

            ⚠️ 若要生成xml檔案,可將--html=./report.html 改成 --html=./report.xml

        2.命令列進入Test_App目錄

        3.執行命令: pytest

    執行結果:

        1.在當前目錄會生成assets資料夾和report.html檔案

2 Pytest控制函式執行順序

    函式修飾符的方式標記被測試函式執行的順序.

    外掛名稱:pytest-ordering

    安裝方式:

        1.安裝包方式 python setup.py install

        2.命令列 pip3 install pytest-ordering

    使用方法:

        1.標記於被測試函式,@pytest.mark.run(order=x)

        2.根據order傳入的引數來解決執行順序

        3.order值全為正數或全為負數時,執行順序:值越小,優先順序越高

        4.正數和負數同時存在:正數優先順序高

    預設情況下,pytest是根據測試方法名由小到大執行的,可以通過第三方外掛包改變其執行順序。

    預設執行方式

    示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            def test_a(self):

                print("------->test_a")

                assert 1

            def test_b(self):

                print("------->test_b")

                assert 0

        if __name__ == '__main__':

            pytest.main("-s  test_abc.py")

    執行結果:

        ------->setup_class

        ------->test_a # 預設第一個執行

        .

        ------->test_b # 預設第二個執行

        F

        ------->teardown_class

    示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            @pytest.mark.run(order=2)

            def test_a(self):

                print("------->test_a")

                assert 1

            @pytest.mark.run(order=1)

            def test_b(self):

                print("------->test_b")

                assert 0

        if __name__ == '__main__':

                pytest.main("-s  test_abc.py")

    執行結果:

        ------->setup_class

        ------->test_b # order=1 優先執行

        F

        ------->test_a # order=2 晚於 order=1 執行

        .

        ------->teardown_class

3 Pytest失敗重試

    通過命令列方式,控制失敗函式的重試次數。

    外掛名稱:pytest-rerunfailures

    安裝方式:

        1.安裝包方式 python setup.py install

        2.命令列 pip3 install pytest-rerunfailures

    使用方法:

        命令列格式:pytest --reruns n # n:為重試的次數

   示例:

    import pytest

    class Test_ABC:

        def setup_class(self):

            print("------->setup_class")

        def teardown_class(self):

            print("------->teardown_class")

        def test_a(self):

            print("------->test_a")

            assert 1

        def test_b(self):

            print("------->test_b")

            assert 0 # 斷言失敗

    執行方式:

        1.修改Test_App/pytest.ini檔案,新增失敗重試引數,即:addopts = -s  --reruns 2 --html=./report.html

            # -s:輸出程式執行資訊

            # --reruns 2 :失敗測試函式重試兩次

            # --html=./report.html 在當前目錄下生成report.html檔案

        2.命令列進入Test_App目錄

        3.執行命令: pytest

    執行結果:

        1.在測試報告中可以看到兩次重試記錄

五 用法(一)

    前置條件:

        1.檔案路徑:

            - Test_App

            - - pytest.ini

        2.pyetst.ini配置檔案內容:

            [pytest]

            # 命令列引數

            addopts = -s

            # 搜尋檔名

            python_files = test_*.py

            # 搜尋的類名

            python_classes = Test_*

            # 搜尋的函式名

            python_functions = test_*

1 pytest之fixture

    fixture修飾器來標記固定的工廠函式,在其他函式,模組,類或整個工程呼叫它時會被啟用並優先執行,

        通常會被用於完成預置處理和重複操作。

    方法:fixture(scope="function", params=None, autouse=False, ids=None, name=None)

    常用引數:

        scope:被標記方法的作用域

            function" (default):作用於每個測試方法,每個test都執行一次

            "class":作用於整個類,每個class的所有test只執行一次

            "module":作用於整個模組,每個module的所有test只執行一次

            "session:作用於整個session(慎用),每個session只執行一次

        params:(list型別)提供引數資料,供呼叫標記方法的函式使用

        autouse:是否自動執行,預設為False不執行,設定為True自動執行

2 fixture第一個例子(通過引數引用)

    示例:

        import pytest

        class Test_ABC:

            @pytest.fixture()

            def before(self):

                print("------->before")

            def test_a(self,before): # ⚠️ test_a方法傳入了被fixture標識的函式,已變數的形式

                print("------->test_a")

                assert 1

        if __name__ == '__main__':

            pytest.main("-s  test_abc.py")

    執行結果:

        ------->before # 發現before會優先於測試函式執行

        ------->test_a

        .

3.fixture第二個例子(通過函式引用)

  示例:

      import pytest

      @pytest.fixture() # fixture標記的函式可以應用於測試類外部

      def before():

          print("------->before")

      @pytest.mark.usefixtures("before")

      class Test_ABC:

          def setup(self):

              print("------->setup")

          def test_a(self):

              print("------->test_a")

              assert 1

      if __name__ == '__main__':

          pytest.main("-s  test_abc.py")

  執行結果:

      ------->before # 發現before會優先於測試類執行

      ------->setup

      ------->test_a

      .

4.fixture第三個例子(預設設定為執行)

    示例:

        import pytest

        @pytest.fixture(autouse=True) # 設定為預設執行

        def before():

            print("------->before")

        class Test_ABC:

            def setup(self):

                print("------->setup")

            def test_a(self):

                print("------->test_a")

                assert 1

        if __name__ == '__main__':

            pytest.main("-s  test_abc.py")

    執行結果:

        ------->before # 發現before自動優先於測試類執行

        ------->setup

        ------->test_a

        .

5.fixture第四個例子(設定作用域為function)

    示例:

        import pytest

        @pytest.fixture(scope='function',autouse=True) # 作用域設定為function,自動執行

        def before():

            print("------->before")

        class Test_ABC:

            def setup(self):

                print("------->setup")

            def test_a(self):

                print("------->test_a")

                assert 1

            def test_b(self):

                print("------->test_b")

                assert 1

        if __name__ == '__main__':

            pytest.main("-s  test_abc.py")

    執行結果:

        ------->before # 執行第一次

        ------->setup

        ------->test_a

        .------->before # 執行第二次

        ------->setup

        ------->test_b

        .

6.fixture第五個例子(設定作用域為class)

    示例:

        import pytest

        @pytest.fixture(scope='class',autouse=True) # 作用域設定為class,自動執行

        def before():

            print("------->before")

        class Test_ABC:

            def setup(self):

                print("------->setup")

            def test_a(self):

                print("------->test_a")

                assert 1

            def test_b(self):

                print("------->test_b")

                assert 1

        if __name__ == '__main__':

            pytest.main("-s  test_abc.py")

    執行結果:

        ------->before # 發現只執行一次

        ------->setup

        ------->test_a

        .

        ------->setup

        ------->test_b

        .

7.fixture第六個例子(返回值)

  示例一:

      import pytest

      @pytest.fixture()

      def need_data():

          return 2 # 返回數字2

      class Test_ABC:

          def test_a(self,need_data):

              print("------->test_a")

              assert need_data != 3 # 拿到返回值做一次斷言

      if __name__ == '__main__':

          pytest.main("-s  test_abc.py")

  執行結果:

      ------->test_a

      .

  示例二:

      import pytest

      @pytest.fixture(params=[1, 2, 3])

      def need_data(request): # 傳入引數request 系統封裝引數

          return request.param # 取列表中單個值,預設的取值方式

      class Test_ABC:

          def test_a(self,need_data):

              print("------->test_a")

              assert need_data != 3 # 斷言need_data不等於3

      if __name__ == '__main__':

          pytest.main("-s  test_abc.py")

  執行結果:

      # 可以發現結果運行了三次

      1

      ------->test_a

      .

      2

      ------->test_a

      .

      3

      ------->test_a

      F

六 用法(二)

1.1 跳過測試函式

  根據特定的條件,不執行標識的測試函式.

    方法:

        skipif(condition, reason=None)

    引數:

        condition:跳過的條件,必傳引數

        reason:標註原因,必傳引數

    使用方法:

        @pytest.mark.skipif(condition, reason="xxx")

    示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            def test_a(self):

                print("------->test_a")

                assert 1

            @pytest.mark.skipif(condition=2>1,reason = "跳過該函式") # 跳過測試函式test_b

            def test_b(self):

                print("------->test_b")

                assert 0

    執行結果:

        ------->setup_class

        ------->test_a #只執行了函式test_a

        .

        ------->teardown_class

        s # 跳過函式

1.2 標記為預期失敗函式

    標記測試函式為失敗函式

    方法:

        xfail(condition=None, reason=None, raises=None, run=True, strict=False)

    常用引數:

        condition:預期失敗的條件,必傳引數

        reason:失敗的原因,必傳引數

    使用方法:

        @pytest.mark.xfail(condition, reason="xx")

    示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            def test_a(self):

                print("------->test_a")

                assert 1

            @pytest.mark.xfail(2 > 1, reason="標註為預期失敗") # 標記為預期失敗函式test_b

            def test_b(self):

                print("------->test_b")

                assert 0

    執行結果:

        ------->setup_class

        ------->test_a

        .

        ------->test_b

        ------->teardown_class

        x  # 失敗標記

2.函式資料引數化

    方便測試函式對測試屬性的獲取。

    方法:

        parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

    常用引數:

        argnames:引數名

        argvalues:引數對應值,型別必須為list

                    當引數個數為一個時, 格式:[value]

                    當引數個數大於一個時,格式為:[(param_value1,param_value2.....),(param_value1,param_value2.....)]

    使用方法:

        @pytest.mark.parametrize(argnames,argvalues)

        ⚠️ 引數值為N個,測試方法就會執行N次

    單個引數示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            @pytest.mark.parametrize("a",[3,6]) # a引數被賦予兩個值,函式會執行兩遍

            def test_a(self,a): # 引數必須和parametrize裡面的引數一致

                print("test data:a=%d"%a)

                assert a%3 == 0

    執行結果:

        ------->setup_class

        test data:a=3 # 執行第一次取值a=3

        .

        test data:a=6 # 執行第二次取值a=6

        .

        ------->teardown_class

    多個引數示例:

        import pytest

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            @pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 引數a,b均被賦予兩個值,函式會執行兩遍

            def test_a(self,a,b): # 引數必須和parametrize裡面的引數一致

                print("test data:a=%d,b=%d"%(a,b))

                assert a+b == 3

    執行結果:

        ------->setup_class

        test data:a=1,b=2 # 執行第一次取值 a=1,b=2

        .

        test data:a=0,b=3 # 執行第二次取值 a=0,b=3

        .

        ------->teardown_class

    函式返回值型別示例:

        import pytest

        def return_test_data():

            return [(1,2),(0,3)]

        class Test_ABC:

            def setup_class(self):

                print("------->setup_class")

            def teardown_class(self):

                print("------->teardown_class")

            @pytest.mark.parametrize("a,b",return_test_data()) # 使用函式返回值的形式傳入引數值

            def test_a(self,a,b):

                print("test data:a=%d,b=%d"%(a,b))

                assert a+b == 3

    執行結果:

        test_abc.py

        ------->setup_class

        test data:a=1,b=2 # 執行第一次取值 a=1,b=2

        .

        test data:a=0,b=3 # 執行第二次取值 a=0,b=3

        .

        ------->teardown_class