Pytest+Jenkins 學習筆記

2023-08-27 15:00:40

Pytest+Jenkins 學習筆記

在軟體測試工作中,單元測試通常是由開發人員執行的、針對最小單元粒度的元件測試,在完成了單元粒度的測試任務之後,通常就需要交由專職的測試人員將這些單元級的元件放到粒度更大的功能元件或子系統中來進行整合性的測試了。在專業術語中,粒度介於單元測試與系統測試之間的測試工作通常被稱作整合測試整合測試。在這一篇筆記中,我將重點介紹如何使用這一類工具進行持續的整合測試,希望讀者在閱讀完筆記的內容之後,能夠:

  • 設計出面向整合測試的測試用例並實現它的自動化執行;
  • 理解實現持續整合測試的必要性並掌握相關的自動化工具;

整合測試的自動化

正如本章開頭所說,整合測試的目的是檢查通過了單元測試的元件在被整合到一起之後是否依然能正常的通訊和協同工作。因此在執行整合測試的任務時,測試人員通常會先將已通過單元測試的、彼此有直接共同作業關係的元件收集起來,然後將它們按照各自的介面規範來進行組裝,並構建成一個粒度更大、功能更為完整的功能元件或子系統,最後再對其進行在這一粒度上的測試。接下來,筆者將重點針對這一型別的測試任務來探討一下測試用例的設計方法。

測試用例的設計方法

由於軟體本身的構造通常是一個從單元元件被組合成功能元件,再逐級構建成各級子系統,直至形成完整的軟體系統的分層結構,並且軟體的規模越大,這種分層結構所涉及的元件粒度就越多。而整合測試就是要針對這種分層結構來展開針對不同元件粒度的測試工作。所以在設計面向整合測試的測試用例時,測試人員就有了自上而下和自下而上兩種不同的設計方法。

  • 如果想採用自上而下的方法來進行整合測試,測試人員就得從待測目標最頂層的功能元件或子系統開始測試,然後按照軟體的分層結構逐級往下,直至測試到最底層的元件。
  • 如果想採用自下而上的方法來進行整合測試,測試人員就得從待測目標最底層的功能元件開始測試,然後按照軟體的分層結構逐級往上,直至測試到最底層的元件。

至於針對分層結構中各級件的具體測試用例,由於整合測試的側重點是各元件在被整合之後介面能否正常通訊,各自的功能是否能完成協同,所以本質上要進行的就是介面測試和功能測試,所以只需要採用針對這些測試任務來設計測試用例即可。但現在的問題是,一個軟體自各個功能的子系統以降,每個分層中可能都包含有數十個甚至上百個功能元件,假設測試人員針對每個元件都編寫了若干個介面測試用例和功能測試用例,那麼該軟體完成一次整合測試要執行的測試用例可能多達成百上千,甚至數萬個以上。在這種情況下,實現整合測試的自動化執行就成為了一件亟待完成的事情。舉個例子,假設筆者開發了一個名叫onlineResumes的 Web 應用程式(該範例的原始碼將被保持在本筆記所在目錄的examples/testOnlineResumes目錄中),該應用程式分層結構大致上如圖 1 所示。

圖 1:onlineResumes應用的分層結構

現在,如果要求讀者採用自上而下的方法對該應用程式中與使用者功能相關的子系統進行整合測試。那麼其測試用例的設計步驟大致如下。

  1. 首先對應用程式最頂層的子系統進行功能測試。根據範例程式的說明,這裡需要進行測試的只有新使用者註冊、使用者登入、檢視/修改使用者資訊、登出使用者這幾項基本功能。對其進行測試用例的設計步驟與之前在第 4 章中示範的功能測試步驟相同,具體如下。

    • 在 Web 瀏覽器中使用 Selenium IDE 針對要測試的每一項功能錄製一次正常的使用者操作,並將錄製的結果匯出為可自動化執行的指令碼。在這裡,筆者將該指令碼儲存在examples/testOnlineResumes/testScripts目錄下,並將指令碼檔案命名為test_userSystem.py
    • 基於等價類劃分+邊界值分析的策略設計具體的測試用例,並將它們整合到之前匯出的自動化測試指令碼中。
  2. 接著屬於第二級元件的 Web UI 進行測試。在 UI 測試中,測試人員也可以採用與功能測試相似的步驟來完成任務,即先使用 Selenium IDE 錄製模擬使用onlineResumes範例程式的使用者操作,並匯出自動化處測試指令碼,再輔以測試用例的方式來執行測試,只不過這一回設計的測試用例需要檢查 Web UI 自身的執行情況,例如確認它是否有應對 SQL 注入這類破壞性輸入的能力、使用者在正常登入之後是否能跳轉到使用者個人的資訊頁面等。同樣的,筆者將匯出的指令碼檔案儲存在examples/testOnlineResumes/testScripts目錄下,並將其命名為test_userUI.py

  3. 接下來再對同屬於第二級元件的伺服器端 API 進行介面測試。根據範例程式的說明,針對讀者在第一步測試的每一項基本功能,其伺服器端都提供有一個基於 RESTful 架構的 API,對其進行測試用例的設計步驟與之前在第 4 章中示範的介面測試步驟相同,具體如下。

    • examples/testOnlineResumes/testScripts目錄下建立一個命名為test_userAPI.py的、基於 PyTest 規範的自動化測試指令碼。
    • test_userAPI.py指令碼中使用requests這樣的擴充套件庫模擬 Web UI,向各項功能的 API 發起請求,並檢查介面呼叫的情況以及其返回的資料格式是否符合 RESTful 架構的規範,例如,發起 HTTP 請求時使用的方法是 GET 還是 POST,返回的資料格式是否為 JSON 等。
  4. 最後對屬於第三級元件的資料庫服務進行介面測試。根據範例程式的說明,它在伺服器端使用的是 MongoDB 資料庫服務,因此讀者可以按照如下步驟來設計針對它的測試用例。

    • examples/testOnlineResumes/testScripts目錄下建立一個命名為test_userDB.py的、基於 PyTest 規範的自動化測試指令碼。
    • test_userDB.py指令碼中使用PyMongo這樣的擴充套件庫模擬伺服器端的 API,嚮應用程式的資料庫發起資料操作請求,並檢查介面呼叫的情況以及其返回的資料格式是否符合伺服器端 API 的處理規範,例如,資料的增、刪、改、查操作是否成功,返回的資料格式是否為 JSON 等。

關於上述步驟生成的指令碼檔案,由於其中定義的測試用例較多,整體的程式碼規模也比較大,不便於直接在書中展示,讀者可前往之前建立的testOnlineResumes/testScripts目錄下,自行參考其中提供的測試指令碼範例。接下來,讀者的任務就是學習如何使用自動化工具來執行這些擁有一定數量的、針對各級元件的測試用例了。

使用自動化測試工具

雖然 PyTest 原本是一款基於 Python 語言實現的單元測試框架,程式設計師們可以輕鬆且高效地使用它編寫出各種用於單元測試的 Python 指令碼(它還可以直接使用 Python 原生的assert語句來進行測試判斷)。但與 Selenium 之類的測試框架相比,PyTest 在被用作一款自動化測試工具時所能提供的功能顯然更受專業測試人員青睞,它可以利用自身可執行引數化測試的特性幫助程式設計師更好地組織和管理基於 Selenium、Appium、Requests 等第三方測試框架設計的測試用例,以提高測試工作本身的質量和效率。接下來,筆者就以 PyTest 為例來向讀者簡單介紹一下自動化測試工具的使用方法,以幫助讀者更好地實現整合測試的自動化執行。

和所有基於 Python 語言實現的第三方擴充套件庫或工具一樣,在使用 PyTest 來組織和管理測試任務之前,讀者需要先在自己的計算機裝置中開啟 Powershell 這樣的命令列終端環境,並執行pip insatll pytest命令來安裝它。在該安裝命令執行完成之後,讀者就可以通過pytest --version命令來驗證安裝是否成功。如果命令列終端輸出了類似圖 2 中所示的版本資訊,就證明該工具已經處於可用狀態了。現在,讀者可以正式開始學習如何使用這款命令列工具來組織和管理測試用例了。

圖 2:PyTest 的版本資訊

當 PyTest 以命令列工具的形式被啟動時,該工具會先自動遞迴式地遍歷使用者當前所在的目錄及其所有子目錄,並根據 PyTest 的用例識別規則來自動收集可納入自動執行列表的測試用例;然後再按照該執行列表逐項執行測試用例。所以使用 PyTest 這款自動化測試工具的第一步,是按照它的用例識別規則來編寫執行測試用例的 Python 指令碼。在預設情況下,PyTest 的用例識別規則如下。

  • 模組級規則:在 PyTest 中,如果想以模組的形式組織測試用例,定義模組的檔名就必須以test_開頭或以_test結尾,例如:test_login.pylogin_test.py等。

  • 型別級規則:在 PyTest 中,如果想以class的形式組織測試用例,類名就必須以Test開頭,且不能定義__init__()方法,其中的每個測試方法也必須以test_開頭來命名,例如:

    class TestCases:
        def testCase1(self):
            assert 100 == 100
    
        def testCase2(self):
            assert 101 == 100
    
  • 函數級規則:在 PyTest 中,如果想以普通函數的形式組織測試用例,只需在定義函數時都採用以test開頭的函數名即可,例如:

    def testCase1():
        assert 100 == 100
    
    def testCase2():
        assert 101 == 100
    

在瞭解了上述用例識別規則,並按照該規則完成了測試指令碼的編寫工作之後。讀者就可以在命令列終端環境中使用 PyTest 來實現測試用例的自動化執行了。根據上述測試用例的組織方式,讀者可以分別採用對應的步驟來實現測試用例的自動化執行。例如,如果測試用例是以檔案模組為單位來組織的, 就將該模組儲存為以test_開頭或以_test結尾的檔案,然後使用 PyTest 執行該檔案即可。例如,如果讀者將下面這兩條assert語句儲存到一個名為test_dome.py檔案中。

assert 100 == 100
assert 101 == 100

然後在該檔案所在的目錄中開啟命令列終端環境,並執行pytest命令,就會得到如下輸出。

============== test session starts ===========================
platform win32 -- Python 3.10.10, pytest-7.2.2, pluggy-1.0.0
rootdir: D:\user\Documents\works\AutomatedTest_in_python\配套資源\範例程式碼\05_testOnlieResumes
collected 0 items / 1 error

============= ERRORS =========================================
_____________ ERROR collecting testScripts/test_dome.py ______
testScripts\test_dome.py:2: in <module>
    assert 101 == 100
E   assert 101 == 100
============ short test summary info ==========================
ERROR testScripts/test_dome.py - assert 101 == 100
!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!

而如果測試用例是以普通函數為單位來組織的,就在測試指令碼中以Test開頭來命名一組函數,然後使用 PyTest 執行這組函數所在的檔案即可。例如,如果讀者將之前建立的test_dome.py檔案修改如下。

def testCase1(self):
    assert 100 == 100

def testCase2(self):
    assert 101 == 100

然後,同樣在該檔案所在的目錄中開啟命令列終端環境,並執行pytest命令,就會得到如下輸出。

============== test session starts ===========================
platform win32 -- Python 3.10.10, pytest-7.2.2, pluggy-1.0.0
rootdir: D:\user\Documents\works\AutomatedTest_in_python\配套資源\範例程式碼\05_testOnlieResumes
collected 2 items

testScripts\test_dome.py .F                                              [100%]

=============== FAILURES =====================================
_________________________ testCase2 __________________________

    def testCase2():
>       assert 101 == 100
E       assert 101 == 100

testScripts\test_dome.py:9: AssertionError
=============== short test summary info ======================
FAILED testScripts/test_dome.py::testCase2 - assert 101 == 100
=============== 1 failed, 1 passed in 0.09s ==================

最後,如果測試用例是以自定義型別為單位來組織的,就在測試指令碼中以Test開頭來命名自定義型別,然後使用 PyTest 執行該型別所在的檔案即可。例如,如果讀者將之前建立的test_dome.py檔案修改如下。

class TestCases:
    def testCase1(self):
        assert 100 == 100

    def testCase2(self):
        assert 101 == 100

接著只需同樣在該檔案所在的目錄中開啟命令列終端環境,並執行pytest命令,就會得到如下輸出。

============== test session starts ===========================
platform win32 -- Python 3.10.10, pytest-7.2.2, pluggy-1.0.0
rootdir: D:\user\Documents\works\AutomatedTest_in_python\配套資源\範例程式碼\05_testOnlieResumes
collected 2 items

testScripts\test_dome.py .F                                              [100%]

=============== FAILURES =====================================
_________________________ TestCases.testCase2 ________________

self = <test_dome.TestCases object at 0x00000190CE18FD90>

    def testCase2(self):
>       assert 101 == 100
E       assert 101 == 100

testScripts\test_dome.py:14: AssertionError
=============== short test summary info ======================
FAILED testScripts/test_dome.py::TestCases::testCase2 - assert 101 == 100
=============== 1 failed, 1 passed in 0.06s =====================

讀者可以看到,上面三種組織形式執行的測試用例是相同的,即使用assert語句檢查100 == 100101 == 100這兩個等值表示式的執行,當表示式返回true就表示測試通過,返回false則表示測試不通過。因此 PyTest 輸出的結果也大同小異,即第一個表示式通過了測試,而另一個表示式測試不通過。

另外,在使用 PyTest 命令時,我們還可以使用特定的引數來展示測試用例的執行結果,以便獲得更詳細的測試報告。下面就帶讀者來具體瞭解一下在以pytest [參數列]的命令形式來執行測試用例時可新增的常用引數,具體如下。

  • -v引數:讓 PyTest 在終端環境中輸出更詳細的測試報告,譬如使用PASSEDFAILED標籤明確標記測試通過或不通過的測試用例。例如,如果我們使用-v引數重新執行之前的test_dome.py指令碼,就會得到如圖 3 所示的測試報告。

    圖 3:PyTest 使用-v引數時的輸出

  • -s引數:讓 PyTest 在終端環境中輸出測試指令碼產生的偵錯資訊,包括指令碼自身呼叫print()函數輸出的資訊。例如,如果我們在之前的test_dome.py指令碼中新增兩個print()函數的呼叫,具體如下。

    class TestCases:
        def testCase1(self):
            print('\n------testCase1 正在執行------')
            assert 100 == 100
    
        def testCase2(self):
            print('\n------testCase2 正在執行------')
            assert 101 == 100
    

    然後使用-s引數重新執行該測試指令碼,就會得到如圖 4 所示的測試報告。

    圖 4:PyTest 使用-s引數時的輸出

  • -vs引數:將上述兩個引數疊加使用,以便讓 PyTest 在終端環境中輸出更詳細的、帶偵錯詳細的測試報告,例如,如果我們使用-v引數重新執行之前的test_dome.py指令碼,就會得到如圖 3 所示的測試報告。

    圖 5:PyTest 使用-vs引數時的輸出

這裡需要特別提醒的是,由於篇幅方面的限制,筆者在這裡介紹的只是在以自動化測試工具的形式使用 PyTest 時最常用的引數。如果讀者想了解該工具更多可用的引數,也可以自行參考該工具的官方檔案,或在命令列終端環境中利用pytest --help命令來獲取該工具自己提供給使用者的使用說明,如圖 6 所示。

圖 6:PyTest 提供的使用說明

除了執行引數之外,讀者還用pytest [目錄/檔案::函數 | 類::方法]的命令形式來具體指定要執行的測試用例,下面是針對一些具體使用場景的pytest命令範例。

  • 執行當前目錄下一個名為testScripts的子目錄中所有可識別的測試用例:pytest ./testScripts/
  • 執行當前目錄下一個名為test_dome.py的檔案中所有可識別的測試用例:pytest ./test_dome.py
  • 執行上述test_dome.py檔案中一個名為TestFunction的方法所要執行的測試用例:pytest ./test_dome.py::TestFunction
  • 執行上述test_dome.py檔案中由一個名為TestClass的類中定義的所有測試用例:pytest ./test_dome.py::TestClass
  • 執行上述TestClass類中一個名為test_method的方法所要執行的的測試用例:pytest ./test_dome.py::TestClass::test_method

最後,如果讀者想使用 PyTest 實現更強大的自動化測試能力,還可以學習一下如何使用 PyTest 的外掛系統。該外掛系統給這款自動化測試工具帶來了許多功能強大的第三方外掛,其中較為常用的包括:

  • pytest-xdist外掛:用於實現採用多核心並行或分散式的方式來執行測試用例;
  • pytest-ordering外掛:用於實現讓測試人員自定義測試用例的執行順序;
  • pytest-rerunfailures外掛:用於實現測試用例在測試失敗後重啟測試;
  • pytest-html外掛:用於生成 HTML 格式的自動化測試報告;
  • allure-pytest外掛:用於按照 Allure 格式生成更為美觀的自動化測試報告;

在 PyTest 中使用外掛的方式大同小異,通常只需先使用 pip 管理器安裝要使用的外掛,然後在執行 PyTest 命令時加上與該外掛相關的引數即可。下面來舉例說明:假設讀者選擇想讓之前針對test_dome.py的測試報告以 HTML 格式輸出,就可以執行以下步驟來安裝並使用pytest-html外掛。

  • 開啟 Powershell 之類命令列終端環境,並在其中執行pip install pytest-html命令來安裝外掛。

  • 待外掛成功安裝之後,進入到test_dome.py檔案所在的目錄,並執行pytest --html=./out.html test_dome.py命令。在這裡,--html的作用就是指示 PyTest 使用pytest-html外掛生成測試報告,並設定測試報告的檔名與儲存路徑。

  • 待 PyTest 完成測試用例的執行之後,test_dome.py檔案所在的目錄中就會自動生成一個名為out.html的檔案,如果讀者用 Web 瀏覽器開啟該檔案,就會看到如圖 7 所示的測試報告。

    圖 7:使用pytest-html外掛生成測試報告

再例如,如果讀者現在想按照自上而下的方式完成針對onlineResumes應用程式的整合測試,就得按照指定的順序執行之前在 5.1.1 節中編寫的四個測試指令碼,這就得藉助pytest-ordering外掛來實現,其具體步驟如下。

  • 開啟 Powershell 之類命令列終端環境,並在其中執行pip install pytest-ordering命令來安裝外掛。

  • 待外掛成功安裝之後,繼續用命令列終端進入到examples/testOnlineResumes/testScripts目錄中,並分別修改之前建立的四個測試指令碼,使用@pytest.mark.run(order=[數位])語句標註其中測試用例被執行的順序,數位1代表最先被執行,然後隨著數位的遞增執行順序依次延後。為了方便演示,這裡將這四個指令碼檔案的內容簡化如下。

    # test_userSystem.py
    import pytest
    @pytest.mark.run(order=1)
    def testfunc(): 
        print("userSystem")
    
    # test_userUI.py
    import pytest
    @pytest.mark.run(order=2)
    def testfunc(): 
        print("userUI")
    
    # test_userAPI.py
    import pytest
    @pytest.mark.run(order=3)
    def testfunc(): 
        print("userAPI")
    
    # test_userDB.py
    import pytest
    @pytest.mark.run(order=4)
    def testfunc(): 
        print("userDB")
    
  • 繼續用命令列終端環境在examples/testOnlineResumes/testScripts目錄中執行pytest -vs ./命令(在此之前,未必要記得先註釋掉test_dome.py檔案中的所有程式碼,以免這部分純粹用於臨時測試的程式碼也被被寫入針對onlineResumes範例程式的測試報告),就會看到如圖 8 所示的測試報告。

    圖 8:利用pytest-ordering外掛執行整合測試

和設計測試用例時一樣,真正針對onlineResumes範例程式的整合測試報告要比上面所展示的內容複雜得多,它們也不便於直接在書中展示,讀者可以自行使用 Selenium IDE 之類的工具來獲得針對實際問題的測試用例及其自動化指令碼,然後再實踐上述步驟,以便檢視真正的測試報告。如果實踐的過程一切順利,就意味著讀者初步掌握了使用 PyTest 實現自動化整合測試的方法。當然了,除了這裡介紹的 PyTest 之外,類似的自動化測試工具還有 Unittest。它們最初都是基於 Python 語言實現的單元測試框架,後來更受人青睞的是其組織和管理測試用例的功能,以及與 Selenium、Appium 等第三方測試框架的整合能力。讀者如有興趣,也可以自行去了解一下 Unittest 框架作為自動化測試工具的使用方法。

持續整合測試

到目前為止,筆者所演示的都是基於傳統軟體工程理論的測試工作,這意味著測試人員的整合測試任務必須要等待軟體的開發團隊完成所有相關模組的開發之後才能啟動。這種流水線式的生產方式背後的管理理念顯然是機械化生產時代的產物,它需要專案需求非常穩定、專案的時間和經濟成本都非常充足,以便大家可以按部就班地各司其職,但在實際生產環境中,專案需求往往是模糊不清且隨時變化的,專案的開發與測試之間不是單向運作的,留給軟體生產團隊的時間和經費也永遠是不足的[1]。更重要的是,傳統的軟體工程還存在著在許多脫離現實的問題。因為,絕大部分軟體在生產初期根本不會有那麼多人蔘與,通常都是兩三個人要做所有的事情。在這種情況下,將生產過程劃分為不同的階段,然後進行相關的分工並沒有多少實質意義。總而言之,如果在瞬息萬變的網際網路時代還繼續採用這種方式,無疑會讓軟體的生產過程變得非常龐雜而僵化。

DevOps 工作理念

為了解決傳統軟體工程所帶來的問題,業界在 2009 年前後基於敏捷開發、極限程式設計等現代軟體工程理論進一步發展出了 DevOps 這個新的工作理念。從字面上來看,DevOps 是 Development 和 Operations 這兩個英文單詞的組合詞,所以它事實上可以被理解成是一套主張將開發(Dev)與運維(Ops)這兩項工作一體化的軟體生產方式,其核心內容是希望通過制定一整套自動化流程,以便讓軟體生產的整體過程更加快捷和可靠。在這種工作理念的指導下,軟體的整合測試工作通常會按照一種被稱作持續整合的方式來進行。這種方式主張讓軟體的測試任務與其開發任務同步進行,即軟體中的各種功能模組在被開發出一個原型時就會持續以增量的方式被整合到整個系統中,並同步進行整合測試,以便隨時修復測試所發現的問題。在整個生產過程中,測試工作的任務就是確保軟體的各個功能模組能儘快地被正確整合到軟體產品中,以達到快速交付的目的,並在此後的整個軟體生命週期中維持快速迭代的狀態。這種理念能給軟體的生產過程帶來如下好處。

  • 降低整合風險:在傳統的軟體工程理念中,同一個軟體通常會分別交給多組人馬來分階段進行開發、測試和維護。而在各司其職且溝通不良的情況下,軟體專案涉及的人員越多,其整合的風險越大,頻繁地進行增量式整合測試將有助於降低此類風險。
  • 降低溝通障礙:由於持續整合的理念主張讓測試與開發工作交叉進行,這就需要雙方深度瞭解彼此的工作內容,所以它有助於降低軟體專案團隊中不同成員之間的溝通障礙。
  • 保障程式碼質量:由於持續整合的理念可以讓開發人員隨時修復測試所發現的問題,所以它有助於軟體專案團隊將精力集中在業務程式碼和功能上,從而獲得更高質量的軟體。
  • 實現版本控制:由於持續整合的理念讓測試人員可以隨時發現軟體在整合過程中出現的問題。所以軟體專案團隊通常都能更及時地發現有問題的程式碼,並趕在有問題的軟體版本被髮布之前修復這些問題,以降低無效的版本迭代。

簡而言之,由 DevOps 所主張的軟體生產方式是:鼓勵軟體專案團隊先快速開發一個能滿足客戶最基本需求的、可供交付使用的軟體原型,然後通過快速迭代版本的方式來滿足不斷變化的客戶需求,並同時藉助有效的持續整合測試發現並修復軟體中存在著各種 bug、逐步提高其自身的效能和穩定性。這樣一來,之前流水線式的生產流程就變成了一個周而復始的迴圈體系,開發人員在這個體系中需要各自獨立完成各種不同的小目標,然後將所有的小目標合起來就能完成大目標。

需要特別強調的是,DevOps 工作理念主張的並不是簡單地在軟體生產過程中將開發、測試與運維等工作合而為一,這種簡單粗暴地理解可能就是該工作理念一直以來難以被真正落實的主要原因。畢竟,這三種工作在傳統思路上是相互衝突的,對於運維工作來說穩定是壓倒一切的,而測試工作的任務是找出問題,開發人員則更傾向於找到富有創造力的解決方案。所以,如果想要將這一工作理念真正落到實處,首先要完成的是思想解放。換而言之,程式設計師們需要改變的並不僅僅是軟體生產過程中的工作流程,更重要的是整個開發團隊中的各個工作角色,從管理到開發、再到測試與運維都需要在思想觀念上進行變革。如果不能做到這一點,即使將所有工作角色集於一人,不同工作之間的思維轉換也依然會是個問題。

綜上所述,程式設計師們在落實 DevOps 理念時需要重新制定軟體生產流程中的一系列規範和標準。按照這些規範和標準,針對軟體的測試工作需要積極介入到其各個功能模組的開發工作中,瞭解這些模組在被開發過程中所使用的宏觀架構和技術細節,以便制定出與之相對應的測試方案。而開發人員在工作中也要及時修復測試過程中發現的 bug,並提供更有利於軟體部署和後期維護的優化建議。在這種情況下,DevOps 工作理念所主張的軟體生產方式考驗的不僅是軟體開發/測試的技術,更是專案的組織管理水平。

持續整合工具

在將 DevOps 理念運用到軟體生產的實踐過程中,開發者們往往需要經常性地對自己正在開發的功能模組執行系統整合的操作,在很多時候,專案管理員甚至會要求其團隊的每個開發人員每天至少提交一次整合請求。這意味著在軟體的整個開發週期中,每天可能會發生多次整合。而每一次的整合動作都必須要經歷構建、測試和部署等一系列的操作,這其中會涉及大量的程式碼編譯,測試用例執行和軟體部署工作,這顯然不是靠人力可以完成的,因此能否構建一套系統來實現持續整合的自動化就成為了我們接下來要探討的問題。

如果想要構建一個自動化的持續整合系統,首先要做的是部署一個供專案內部使用的版本控制系統,以便團隊常用能有可靠的方法來集中和儲存他們的工作成果。然後,專案團隊會專門部署一個持續整合伺服器。在這裡,持續整合伺服器(也稱為構建伺服器)是一種能在各種平臺上完成自動化構建操作的軟體工具,通常具有很好的可設定性,通常用於為團隊的專案提供可靠和穩定的環境。

具體到實際生產環境中,專案管理員通常會選擇將版本控制系統和持續整合伺服器部署在一臺相對乾淨的伺服器裝置上,以免它受到不相關的工具、環境變數或其他設定的影響。待部署工作完成之後,持續整合伺服器就會利用版本控制系統來監控專案的進度,並自動化地執行系統整合操作。具體來說就是:它會在開發人員成功提交程式碼之後,按照專案管理員設定的規則和被修改的部分來完成軟體的自動化構建,並進行整合測試任務。在完成相關任務之後,該伺服器系統就會向專案團隊中的相關成員傳送軟體的整合報告,其內容包括專案的最新版本資訊、它所執行的構建指令碼、測試用例以及其他需要傳送的通知資訊。除此之外,程式碼分析、程式碼覆蓋率、程式碼質量報告、agent pooling、pipeline、構建比較、IDE 整合、第三方工具支援等也是持續整合伺服器常有的功能。

到目前為止,市面上常用的持續整合系統主要包括 Jenkins、TeamCity、Travis CI、GO CD、Bamboo、Gitlab CI、CircleCI 等。接下來,筆者將以 PyTest+Jenkins 這一工具組合為例具體為讀者介紹一下如何實現基於 DevOps 理念的、自動化的持續整合測試。首先,讀者需要按照以下步驟來安裝 Jenkins 系統,並對它進行相應的設定。

  1. 準備一臺相對乾淨的伺服器裝置(如果沒有條件部署物理裝置,也可以選擇使用虛擬環境來代替),伺服器的作業系統可以是任意一種 Linux 發行版或 Windows Server 系統。在這裡,筆者將選擇使用一臺安裝了 Windows 系統環境的虛擬機器器來示範後續步驟。

  2. 安裝版本控制系統,目的是讓專案團隊有可靠的方法提交他們的工作成果,並交由 Jenkins 系統進行自動化的持續整合。在這裡,筆者推薦讀者安裝 Git 這一款分散式的版本控制系統。

  3. 安裝 Java 執行時環境,由於 Jenkins 是一款基於 Java 語言開發的持續整合服務系統,它的執行依賴於 Java 執行時環境。

  4. 前往 Google、百度等搜尋引擎並搜尋「Jenkins download」,找到 Jenkins 的官方下載頁面,如圖 9 所示。

    圖 9:Jenkins 官方下載頁面

  5. 從上述頁面中選擇下載左側 LTS 版本的 Windows 安裝包(檔名為jenkins.msi),並將其上傳到準備好的伺服器上。

  6. 進入到伺服器中,並在jenkins.msi檔案所在的目錄中用滑鼠左鍵雙擊該安裝包,啟動 Jenkins 系統的圖形化安裝嚮導,如圖 10 所示。

    圖 10:Jenkins 系統的圖形化安裝嚮導

  7. 在上述介面中用滑鼠左鍵單擊「Next」按鈕,就會看到如圖 11 所示的安裝路徑設定介面,讀者通常只需要保持預設選項,並直接用滑鼠左鍵單擊「Next」按鈕即可。

    圖 11:設定 Jenkins 的安裝路徑

  8. 在接下來的如圖 12 所示介面中,讀者需要在這裡設定當前伺服器系統的管理員賬戶和密碼,並通過用滑鼠單擊「Test Credentials」按鈕來驗證賬戶的有效性(通常情況下,這裡輸入的應該是專案內部網路的管理員賬戶)。

    圖 12:設定 Jenkins 的管理員賬戶

  9. 在管理員賬戶通過驗證之後,繼續在上述介面中用滑鼠左鍵單擊「Next」按鈕,就會看到如圖 13 所示的伺服器埠設定介面,讀者通常只需要保持預設的8080埠,並直接用滑鼠左鍵單擊「Test Port」按鈕來驗證該埠的可用性即可(通常情況下,只要該埠沒有被其他服務佔用就會通過驗證)。

    圖 13:設定 Jenkins 的伺服器埠

  10. 在伺服器埠通過驗證之後,繼續在上述介面中用滑鼠左鍵單擊「Next」按鈕,就會看到如圖 14 所示的 Java 執行環境設定介面,讀者通常只需根據之前在第 3 步中安裝的情況來設定該目錄即可。

    圖 14:設定 Java 執行環境

  11. 繼續在上述介面中用滑鼠左鍵單擊「Next」按鈕,就會看到如圖 15 所示的元件安裝介面,讀者可以在這裡指定要安裝的 Jenkins 元件,但通常只需要保持預設選項,並直接用滑鼠左鍵單擊「Next」按鈕即可。

    圖 15:設定要安裝的 Jenkins 元件

  12. 最後只需在彈出的確認介面中用滑鼠左鍵單擊「Install」按鈕即可開始 Jenkins 系統的安裝。待安裝過程完成之後,就會看到如圖 16 所示的安裝結束介面,讀者用滑鼠單擊「Finish」按鈕就可以關閉該安裝嚮導。

    圖 16:結束安裝嚮導介面

  13. 如果上述操作一切順利,讀者現在只需要使用伺服器自帶的 Web 瀏覽器開啟該 Jenkins 服務在本地可存取的 URL(假設讀者之前的安裝步驟中為該服務指定的埠是8080,那麼該 URL 就是http://localhost:8080,即可看到如圖 17 所示的介面。在這裡,讀者需要根據該介面中的提示資訊,找到 Jenkins 系統的初始密碼,然後將該密碼輸入到下面的「管理員密碼」輸入框中並使用滑鼠左鍵單擊「繼續」按鈕,以便啟用該系統。

    圖 17:Jenkins 系統的初始介面

  14. 在 Jenkins 系統比成功啟用之後,隨即就會啟動一個名為「新手入門」的設定向導程式,讀者首先看到的是用於安裝外掛的頁面(如圖 18 所示)。對於初學者,筆者建議選擇「安裝推薦的外掛」,以便確保 Jenkins 系統的基本功能可以正常使用。

    圖 18:選擇要安裝的 Jenkins 外掛

  15. 接下來,讀者就會看到 Jenkins 系統推薦安裝的外掛,以及外掛按的進度,如圖 19 所示。

    圖 19:安裝系統推薦的外掛

  16. 待外掛完成之後,Jenkins 設定向導就會來到如圖 20 所示的管理員賬戶設定頁面,在這裡,讀者需要設定一個由專案管理員使用的使用者名稱、密碼以及電子郵件等資訊,然後用滑鼠左鍵單擊「儲存並完成」按鈕即可完成 Jenkins 系統的初始設定。

    圖 20:設定系統的管理員賬戶

  17. 在初始設定結束之後,讀者就會正式進入到如圖 21 所示的控制面板介面中。

    圖 21:Jenkins 系統的控制介面

  18. 通常情況下。開發專案的管理員接下來要做的就是給這臺持續整合伺服器設定一個可供專案內部網路中其他工作機遠端登入的 URL,這樣就不必所有的管理操作都必須物理接觸到這臺伺服器裝置了。具體方法是:在上述控制介面中用滑鼠左鍵單擊其左側的「Mange Jenkins」連結,並進入到如圖 22 所示的 Jenkins 設定介面中。

    圖 22:Jenkins 設定介面

  19. 在上述介面中繼續用滑鼠左鍵單擊「Configure System」連結,並在隨後進入的頁面中找到「Jenkins URL」設定項。在這裡,讀者可以根據自己所在的團隊設定一個內部網路可用的 URL,如圖 23 所示。

    圖 23:設定 Jenkins URL

現在只要設定好相關域名的 DNS 表,專案管理員應該就可以在這臺伺服器裝置所在的區域網內任意一臺工作機上存取該 Jenkins 服務了。例如,圖 24 所示的是筆者在另一臺 Windows 系統上使用 Microsoft Edge 瀏覽器存取該持續整合伺服器時的情況。當然,如果讀者想在整個網際網路的任何地方都能存取該服務,就需要專門購買面向全球網際網路的域名服務了,然後在其「Configure Global Security」頁面中完成更嚴格的安全設定了,由於這種需求在大多數軟體專案的開發工作中並不常見,筆者在這裡就不展開介紹了,讀者如有興趣,可自行參考 Jenkins 的官方檔案。

圖 24:遠端存取 Jenkins 服務

接下來,筆者將繼續以onlineResumes範例程式為目標來演示一下如何使用 PyTest+Jenkins 這一工具組合來實現基於持續整合方式的自動化測試。其主要步驟如下。

  1. 在圖 21 所示的控制介面中用滑鼠左鍵單擊其左側的「新建Item」連結並進入的新建任務介面中。在這裡,讀者需要給要建立的自動整合任務設定一個名稱,並選擇下面的「Freestyle Project」選項,如圖 25 所示。

    圖 25:新建任務介面

  2. 在用滑鼠左鍵單擊了上述介面中的「確定」按鈕之後,讀者就會來到如圖 26 所示的任務設定介面。由於筆者在這裡要執行的是一個基於 Python 環境的此自動化測試任務,所以只需填寫一些簡單說明,其他保持預設選項即可。

    圖 26:任務設定介面

  3. 在上述介面中繼續用滑鼠左鍵單擊了「儲存」按鈕之後,讀者就會來到如圖 27 所示的任務設定介面。在這裡,如果讀者直接檢視任務的工作空間是會報錯的,我們必須先用滑鼠左鍵單擊一下任務管理介面左側的「Build now」連結,先執行一次構建動作。

    圖 27:任務管理介面

  4. 在執行完第一次構建任務後,如果讀者再次檢視任務的工作空間,就會看到當前任務的工作空間是一個空目錄。接下來要做的就是將之前在 5.1.1 節中建立的testOnlieResumes/testScripts目錄下的檔案複製到 Jenkins 系統在伺服器上的[Jenkins的程式目錄]/workspace/testOnlineResumes目錄中(在 Windows 系統中,[Jenkins的程式目錄在預設情況下通常即C:\ProgramData\Jenkins\.jenkins目錄),然後再次重新整理任務的工作空間頁面,就會看到相關的自動化測試指令碼檔案了,如圖 28 所示。

    圖 28:任務的工作空間

  5. 現在讀者需要做的是:在[Jenkins的程式目錄]/workspace/testOnlineResumes目錄中建立一個名為auto_run.py的指令碼檔案,並在其中以呼叫pytest.main()方法來啟動測試指令碼,具體程式碼如下。

    import pytest
    
    if (__name__ == "__main__") :
        pytest.main(["-v", "-s"])
    
  6. 然後繼續在testOnlineResumes任務的管理介面中用滑鼠左鍵單擊左側的「設定」連結,回到任務的設定介面,並找到該頁面中的「Build Steps」選項,新增一個 Windows 批次處理型別的步驟,具體如圖 29 所示。

    圖 29 設定構建步驟

  7. 最後再次返回到testOnlineResumes任務的管理介面中,並繼續用滑鼠左鍵單擊左側的「Build Now」連結之後,讀者就可以該管理介面左側的「Build History」一欄中看到由綠色對鉤標記的成功構建操作(不成功的操作則由紅色的錯叉標記),如圖 30 所示。

    圖 30 Jenkins 的構建記錄

  8. 如果讀者用滑鼠左鍵單擊最後一次構建操作(在這裡是#9)並檢視其「控制檯輸出」,就會看到與之前使用 PyTest 命令列工具時相同的自動化測試報告,如圖 31 所示。

    圖 31 構建步驟的控制檯輸出

到目前為止,讀者已經完成了使用 Jenkins 服務呼叫 PyTest 框架執行自動化測試指令碼的設定,接下來要做的就是讓該伺服器利用版本控制系統監控要測試的目標程式(在這裡就是onlineResumes範例程式),只要該程式的程式碼發生了改動,就自動執行testOnlineResumes任務,這部分的具體操作步驟如下。

  1. 按照筆者之前演示的步驟在 Jenkins 服務中再建立一個用於自動構建onlineResumes範例程式的新任務,並將該任務命名為buildOnlineResumes。在這裡,讀者唯一需要注意的是,由於目標程式是一個基於 Node.js 實現的應用程式,所以需要在 Jenkins 系統中安裝 NodeJS 外掛,並安裝指定版本的 Node.js 執行時環境,如圖 32 所示。

    圖 32 buildOnlineResumes任務

  2. 返回到testOnlineResumes任務的管理介面中,找到該頁面中的「構建觸發器」選項,並勾選該選項下面的「在其他專案完成構建時執行」選項,然後在彈出的表單中填入用於持續整合目標程式的任務名稱,在這裡就是之前建立的buildOnlineResumes任務,如圖 33 所示。

    圖 33 設定testOnlineResumes任務的觸發器

現在,只要buildOnlineResumes任務完成構建動作,testOnlineResumes就會自動啟動構建任 務。這樣一來,前者負責待測軟體的自動化持續整合,後者負責自動化的整合測試,至此,我們就完成了一次基於持續整合方式的自動化測試演示。當然了,在實際生產環境中,這兩個任務的設定過程都要比這裡演示的複雜得多,讀者如有需要,還需再仔細查閱 Jenkins 的官方檔案,這裡基於篇幅的考慮,就不再繼續展開演示了。


已完成


  1. 關於傳統軟體工程思想在實際專案運作的過程中遇到的問題,讀者可以去參考弗雷德裡克·布魯克斯(Frederick Brooks, Jr)所著的文集—《人月神話:軟體專案管理之道》。 ↩︎