教程 9 - 测试时间

大多数软件开发并不涉及编写新代码,而是修改现有代码。确保现有代码以我们期望的方式继续工作是软件开发过程的关键部分。确保应用程序行为的方法之一就是*测试套件*。

运行测试套件

原来我们的项目已经有了一个测试套件!我们最初生成项目时,会生成两个顶级目录:src “和 “tests”。src “文件夹包含应用程序的代码;”tests “文件夹包含测试套件。在 tests 文件夹中,有一个名为 test_app.py 的文件,内容如下::

def test_first():
    "An initial test for the app"
    assert 1 + 1 == 2

这是一个 Pytest 测试用例 - 可以执行的代码块,用于验证应用程序的某些行为。在本例中,该测试是一个占位符,并不测试我们应用程序的任何内容,但我们可以执行该测试。

我们可以使用 briefcase dev--test 选项来运行这个测试套件。由于这是第一次运行测试,我们还需要传递 -r 选项,以确保测试需求也已安装:

(beeware-venv) $ briefcase dev --test -r

[helloworld] Installing requirements...
...
Installing dev requirements... done

[helloworld] Running test suite in dev environment...
===========================================================================
============================= test session starts ==============================
platform darwin -- Python 3.11.0, pytest-7.2.0, pluggy-1.0.0 -- /Users/brutus/beeware-tutorial/beeware-venv/bin/python3.11
cachedir: /var/folders/b_/khqk71xd45d049kxc_59ltp80000gn/T/.pytest_cache
rootdir: /Users/brutus
plugins: anyio-3.6.2
collecting ... collected 1 item

tests/test_app.py::test_first PASSED                                     [100%]

============================== 1 passed in 0.01s ===============================

成功了!我们刚刚执行了一个测试,验证了 Python 数学以我们预期的方式运行(真是松了一口气!)。

让我们用一个测试来替换这个占位符测试,以验证我们的 greeting() 方法是否按照我们预期的方式运行。用以下内容替换 test_app.py 中的内容::

from helloworld.app import greeting


def test_name():
    """If a name is provided, the greeting includes the name"""

    assert greeting("Alice") == "Hello, Alice"


def test_empty():
    """If a name is not provided, a generic greeting is provided"""

    assert greeting("") == "Hello, stranger"

这将定义两个新测试,验证我们期望看到的两种行为:提供名称时的输出和名称为空时的输出。

现在我们可以重新运行测试套件。这一次,我们不需要提供 -r 选项,因为测试需求已经安装完毕;我们只需要使用 --test 选项:

(beeware-venv) $ briefcase dev --test

[helloworld] Running test suite in dev environment...
===========================================================================
============================= test session starts ==============================
...
collecting ... collected 2 items

tests/test_app.py::test_name PASSED                                      [ 50%]
tests/test_app.py::test_empty PASSED                                     [100%]

============================== 2 passed in 0.11s ===============================

非常好我们的 greeting() 实用程序方法如期工作了。

测试驱动开发

现在我们有了测试套件,可以用它来推动新功能的开发。让我们修改应用程序,为某位用户添加特殊的问候语。首先,我们可以在 test_app.py’:的底部为我们希望看到的新行为添加一个测试用例:

def test_brutus():
    """If the name is Brutus, a special greeting is provided"""

    assert greeting("Brutus") == "BeeWare the IDEs of Python!"

然后,用这个新测试运行测试套件:

(beeware-venv) $ briefcase dev --test

[helloworld] Running test suite in dev environment...
===========================================================================
============================= test session starts ==============================
...
collecting ... collected 3 items

tests/test_app.py::test_name PASSED                                      [ 33%]
tests/test_app.py::test_empty PASSED                                     [ 66%]
tests/test_app.py::test_brutus FAILED                                    [100%]

=================================== FAILURES ===================================
_________________________________ test_brutus __________________________________

    def test_brutus():
        """If the name is Brutus, a special greeting is provided"""

>       assert greeting("Brutus") == "BeeWare the IDEs of Python!"
E       AssertionError: assert 'Hello, Brutus' == 'BeeWare the IDEs of Python!'
E         - BeeWare the IDEs of Python!
E         + Hello, Brutus

tests/test_app.py:19: AssertionError
=========================== short test summary info ============================
FAILED tests/test_app.py::test_brutus - AssertionError: assert 'Hello, Brutus...
========================= 1 failed, 2 passed in 0.14s ==========================

这一次,我们看到了测试失败–输出结果解释了失败的原因:测试期望输出 “BeeWare the IDEs of Python!”,但我们的 greeting() 实现却返回 “Hello, Brutus”。让我们修改 src/helloworld/app.pygreeting() 的实现,使其具有新的行为::

def greeting(name):
    if name:
        if name == "Brutus":
            return "BeeWare the IDEs of Python!"
        else:
            return f"Hello, {name}"
    else:
        return "Hello, stranger"

如果我们再次运行测试,就会发现测试通过了:

(beeware-venv) $ briefcase dev --test

[helloworld] Running test suite in dev environment...
===========================================================================
============================= test session starts ==============================
...
collecting ... collected 3 items

tests/test_app.py::test_name PASSED                                      [ 33%]
tests/test_app.py::test_empty PASSED                                     [ 66%]
tests/test_app.py::test_brutus PASSED                                    [100%]

============================== 3 passed in 0.15s ===============================

运行时测试

到目前为止,我们一直在开发模式下运行测试。这在开发新功能时尤其有用,因为您可以快速迭代添加测试,并添加代码使测试通过。不过,在某些情况下,您会希望验证您的代码在捆绑应用程序环境下是否也能正确运行。

-test``和-r``选项也可以传递给`run`命令。如果使用 briefcase run --test -r,将运行相同的测试套件,但它将在打包的应用程序捆绑包内运行,而不是在开发环境中运行:

(beeware-venv) $ briefcase run --test -r

[helloworld] Updating application code...
Installing src/helloworld... done
Installing tests... done

[helloworld] Updating requirements...
...
[helloworld] Built build/helloworld/macos/app/Hello World.app (test mode)

[helloworld] Starting test suite...
===========================================================================
Configuring isolated Python...
Pre-initializing Python runtime...
PythonHome: /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/support/python-stdlib
PYTHONPATH:
- /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/support/python311.zip
- /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/support/python-stdlib
- /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/support/python-stdlib/lib-dynload
- /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/app_packages
- /Users/brutus/beeware-tutorial/helloworld/macOS/app/Hello World/Hello World.app/Contents/Resources/app
Configure argc/argv...
Initializing Python runtime...
Installing Python NSLog handler...
Running app module: tests.helloworld
---------------------------------------------------------------------------
============================= test session starts ==============================
...
collecting ... collected 3 items

tests/test_app.py::test_name PASSED [ 33%]
tests/test_app.py::test_empty PASSED [ 66%]
tests/test_app.py::test_brutus PASSED [100%]

============================== 3 passed in 0.21s ===============================

[helloworld] Test suite passed!

briefcase dev --test 一样,只有在第一次运行测试套件时才需要使用 -r 选项,以确保测试依赖项的存在。以后运行时,可以省略该选项。

你也可以在移动后端使用 --test 选项:因此 briefcase run iOS --testbriefcase run android --test 都可以在你选择的移动设备上运行测试套件。

下一步

We’ve now got a a test suite for our application. But it still looks like a tutorial app. Is there anything we can do about that? Turn to Tutorial 10 to find out…