簡體   English   中英

Python CLI 程序單元測試

[英]Python CLI program unit testing

我正在做一個python Command-Line-Interface程序,做測試時覺得很無聊,例如,這里是程序的幫助信息:

usage: pyconv [-h] [-f ENCODING] [-t ENCODING] [-o file_path] file_path

Convert text file from one encoding to another.

positional arguments:
  file_path

optional arguments:
  -h, --help            show this help message and exit
  -f ENCODING, --from ENCODING
                        Encoding of source file
  -t ENCODING, --to ENCODING
                        Encoding you want
  -o file_path, --output file_path
                        Output file path

當我對程序進行更改並想要測試某些東西時,我必須打開一個終端,輸入命令(帶有選項和參數),輸入 enter,然后查看運行時是否出現任何錯誤。 如果真的發生錯誤,我必須回到編輯器並從頭到尾檢查代碼,猜測錯誤位置,進行小改動,編寫print行,返回終端,再次運行命令......

遞歸。

所以我的問題是,使用 CLI 程序進行測試的最佳方法是什么,它可以像使用普通 python 腳本進行單元測試一樣簡單嗎?

我認為在整個程序級別上進行功能測試是非常好的。 每次測試仍然可以測試一個方面/選項。 通過這種方式,您可以確定程序作為一個整體確實有效。 編寫單元測試通常意味着您可以更快地執行測試,並且失敗通常更容易解釋/理解。 但是單元測試通常更依賴於程序結構,當您在內部進行更改時需要更多的重構工作。

無論如何,使用py.test ,這是一個測試 pyconv 的 latin1 到 utf8 轉換的小例子::

# content of test_pyconv.py

import pytest

# we reuse a bit of pytest's own testing machinery, this should eventually come
# from a separatedly installable pytest-cli plugin. 
pytest_plugins = ["pytester"]

@pytest.fixture
def run(testdir):
    def do_run(*args):
        args = ["pyconv"] + list(args)
        return testdir._run(*args)
    return do_run

def test_pyconv_latin1_to_utf8(tmpdir, run):
    input = tmpdir.join("example.txt")
    content = unicode("\xc3\xa4\xc3\xb6", "latin1")
    with input.open("wb") as f:
        f.write(content.encode("latin1"))
    output = tmpdir.join("example.txt.utf8")
    result = run("-flatin1", "-tutf8", input, "-o", output)
    assert result.ret == 0
    with output.open("rb") as f:
        newcontent = f.read()
    assert content.encode("utf8") == newcontent

安裝 pytest(“pip install pytest”)后,您可以像這樣運行它:

$ py.test test_pyconv.py
=========================== test session starts ============================
platform linux2 -- Python 2.7.3 -- pytest-2.4.5dev1
collected 1 items

test_pyconv.py .

========================= 1 passed in 0.40 seconds =========================

該示例通過利用 pytest 的夾具機制重用 pytest 自己測試的一些內部機制,請參閱http://pytest.org/latest/fixture.html 如果您暫時忘記了細節,您可以從提供“run”和“tmpdir”來幫助您准備和運行測試的事實開始工作。 如果你想玩,你可以嘗試插入一個失敗的斷言語句或簡單地“斷言0”,然后查看回溯或發出“py.test --pdb”進入python提示符。

功能測試的用戶界面開始,逐步進行單元測試 感覺很困難,尤其是當您使用控制應用程序入口點的argparse模塊或click包時。

cli-test-helpers Python 包包含示例和幫助函數(上下文管理器),用於為 CLI 編寫測試的整體方法。 這是一個簡單的想法,並且與 TDD 完美配合:

  1. 從功能測試開始(以確保您的用戶界面定義)和
  2. 進行單元測試(以確保您的實施合同)

功能測試

注意:我假設您開發的代碼使用setup.py文件部署或作為模塊 ( -m ) 運行。

  • 是否安裝了入口點腳本? (測試 setup.py 中的配置)
  • 這個包可以作為 Python 模塊運行嗎? (即無需安裝)
  • 命令 XYZ 可用嗎? 等等。在這里涵蓋您的整個 CLI 使用情況!

這些測試很簡單:它們運行您將在終端中輸入的 shell 命令,例如

def test_entrypoint():
    exit_status = os.system('foobar --help')
    assert exit_status == 0

請注意使用非破壞性操作(例如--help--version )的技巧,因為我們無法使用這種方法模擬任何內容。

走向單元測試

要測試應用程序的單個方面,您需要模仿命令行參數和環境變量等內容。 您還需要捕獲腳本的退出,以避免測試因SystemExit異常而失敗。

使用ArgvContext模擬命令行參數的示例:

@patch('foobar.command.baz')
def test_cli_command(mock_command):
    """Is the correct code called when invoked via the CLI?"""
    with ArgvContext('foobar', 'baz'), pytest.raises(SystemExit):
        foobar.cli.main()

    assert mock_command.called

請注意,我們模擬了我們希望 CLI 框架(在此示例中為click )調用的函數,並且我們捕獲了框架自然引發的SystemExit 上下文管理器由cli-test-helperspytest 提供

單元測試

其余的一切照舊。 通過上述兩種策略,我們克服了 CLI 框架可能奪走我們的控制權。 剩下的就是通常的單元測試。 希望是 TDD 風格。

披露:我是cli-test-helpers Python 包的作者。

所以我的問題是,使用 CLI 程序進行測試的最佳方法是什么,它可以像使用普通 python 腳本進行單元測試一樣簡單嗎?

唯一的區別是,當您將 Python 模塊作為腳本運行時,它的__name__屬性設置為'__main__' 所以一般來說,如果你打算從命令行運行你的腳本,它應該有以下形式:

import sys

# function and class definitions, etc.
# ...
def foo(arg):
    pass

def main():
    """Entry point to the script"""

    # Do parsing of command line arguments and other stuff here. And then
    # make calls to whatever functions and classes that are defined in your
    # module. For example:
    foo(sys.argv[1])


if __name__ == '__main__':
    main()

現在沒有區別,您將如何使用它:作為腳本或作為模塊。 因此,在您的單元測試代碼中,您只需導入foo函數,調用它並進行任何您想要的斷言。

也許太少太晚了,但你總是可以使用

import os.system
result =  os.system(<'Insert your command with options here'>
assert(0 == result)

這樣,您可以像從命令行一樣運行程序,並評估退出代碼。

(學習pytest后更新)你也可以使用capsys。 (來自運行 pytest --fixtures)

capsys 啟用對sys.stdoutsys.stderr的寫入的文本捕獲。

The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.

pytest-console-scripts是一個 Pytest 插件,用於測試通過setup.pyconsole_scripts入口點安裝的 python 腳本。

簡短的回答是肯定的,你可以使用單元測試,而且應該。 如果您的代碼結構良好,那么分別測試每個組件應該很容易,並且如果您需要,可以隨時模擬sys.argv以模擬使用不同參數運行它。

這不是專門針對 Python 的,但我測試命令行腳本的方法是使用各種預先確定的輸入和選項運行它們,並將正確的輸出存儲在文件中。 然后,為了在我進行更改時對其進行測試,我只需運行新腳本並將輸出通過管道傳輸到diff correct_output - 如果文件相同,則不輸出任何內容。 如果它們不同,它會告訴你在哪里。 這僅在您使用 Linux 或 OS X 時才有效; 在 Windows 上,您必須獲得 MSYS。

例子:

python mycliprogram --someoption "some input" | diff correct_output -

為了使它更容易,您可以將所有這些測試運行添加到您的“make test”Makefile 目標中,我假設您已經擁有它。 ;)

如果您同時運行其中的許多,則可以通過添加失敗標記使每個結束的位置更加明顯:

python mycliprogram --someoption "some input" | diff correct_output - || tput setaf 1 && echo "FAILED"

對於 Python 3.5+,您可以使用更簡單的subprocess.run從測試中調用 CLI 命令。

使用pytest

import subprocess

def test_command__works_properly():
    try:
        result = subprocess.run(['command', '--argument', 'value'], check=True, capture_output=True, text=True)
    except subprocess.CalledProcessError as error:
        print(error.stdout)
        print(error.stderr)
        raise error

如果需要,可以通過result.stdoutresult.stderrresult.returncode訪問輸出。

如果發生錯誤, check參數會引發異常。 注意capture_outputtext參數需要 Python 3.7+,這簡化了捕獲和讀取 stdout/stderr。

鑒於您明確要求測試命令行應用程序,我相信您知道 python 中的單元測試工具,並且您實際上正在尋找一種工具來自動化命令行工具的端到端測試。 有幾個專門為此設計的工具。 如果您正在尋找可 pip 安裝的東西,我會推薦cram 它與 python 環境的其余部分集成得很好(例如通過 pytest 擴展),並且非常易於使用:

只需將要運行的命令以$開頭,並以預期的輸出開頭 . 例如,以下將是一個有效的補習班測試:

  $ echo Hello
    Hello

通過在預期輸出前面有四個空格,在測試前面有兩個空格,您實際上可以使用這些測試來編寫文檔。 更多關於網站的內容。

您可以使用標准單元測試模塊:

# python -m unittest <test module>

或使用鼻子作為測試框架。 只需在單獨的目錄中編寫經典的 unittest 文件並運行:

# nosetests <test modules directory>

編寫單元測試很容易。 只需按照在線手冊進行單元測試

我不會將程序作為一個整體進行測試,這不是一個好的測試策略,並且可能實際上無法捕捉到錯誤的實際位置。 CLI 接口只是 API 的前端。 您通過單元測試測試 API,然后當您對特定部分進行更改時,您有一個測試用例來執行該更改。

因此,重構您的應用程序,以便您測試 API 而不是應用程序本身。 但是,您可以進行實際運行完整應用程序並檢查輸出是否正確的功能測試。

簡而言之,是的,測試代碼與測試任何其他代碼相同,但您必須測試各個部分而不是整體組合,以確保您的更改不會破壞所有內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM