Skip to content

19命令行参数,助力测试框架高度定制化

欢迎进入"模块四 深入自动化测试框架原理",算上开篇词、课前必读,以及前三个模块的学习,你对测试框架应该有了非常深刻的认识。

按照本课程知识,现在你已经能融合 API 和 UI 搭建出功能丰富的测试框架,并可以使用 PageObject 模型分离元素和操作;之后,我们又让测试框架具备 Data Driven 能力,并搭配 Jira 或者禅道创建出具备测试数据管理能力的一揽子测试解决方案。

用这些技能应对日常测试工作肯定没有任何问题,但是每一个有追求的测试人都会想:我能不能自己造一个轮子?

小提问: 为什么编写自己的测试框架或者应用程序,被称为造轮子?请带着这个问题进行下面的学习,在后面的部分我会解答。

从本讲开始,我将拆解自动化测试框架的重要组成部分,并带领你一一自主实现。模块四结束时,你应该就具备不借助任何第三方库,独自开发测试框架的能力了。

今天我们先来看测试框架的第一个重点部分:使用命令行参数定制化测试框架,下图是本讲的内容结构,可供你学习参考。

《10 | 你的第一个 API 测试框架(二)》 中,我讲了如何使用命令行参数来运行 pytest 命令。在 pytest 框架中,预置的命令行参数起着非常重要的作用,比如我们可以利用"-k"来根据函数名称挑选测试用例,利用"-m"来分组挑选测试用例。

但有些时候,我们还是希望可以定制一下命令行参数。比如,我的测试数据想通过命令行的方式传递,下面我们就来实际操作下。

pytest 添加命令行参数

假设我们的文件结构如下:

python
|--lagouAPITest
    |--tests_command_lines
        |--conftest.py
        |--test_sample.py
        |--__init__.py

其中 conftest.py 的代码如下:

python
import pytest

def pytest_addoption(parser):
    parser.addoption(
        "--auth", action="store", default=None, help="Your own auth key pair"
    )

@pytest.fixture(scope='session')
def auth(request):
    return request.config.getoption('--auth')

我在前面的章节讲过,在 conftest.py 定义的 fixtures 可作用于整个 Package 下的多个测试文件。在此 conftest.py 里,我定义了一个函数 pytest_addoption,这里注意:

  • pytest_addoption 是一个 hook 方法,其名称不可改变;

  • pytest_addoption 允许用户注册一个自定义的命令行参数,方便用户使用命令行传递数据;

  • pytest_addoption 仅能在 conftest.py 文件或者 pytest plugins 里实现;

  • pytest_addoption 在测试用例执行前被调用。

然后我们结合前面章节讲过的 fixture 函数,定义一个名字为 auth 的 fixture 接收自定义的命令行参数,并提供给整个 package 下的测试用例使用,这里需要注意的是:

  • auth 函数是正常的 fixture 函数,需要加装饰器 pytest.fixture 装饰;

  • auth 函数的 scope 可以自由定义,这里我定义为 session;

  • auth 函数的命名可以更改,但是参数 request 不可更改;

  • request.config.getoption 用于接收命令行命令参数。

通过函数 pytest_addoption 以及定义 fixture 方法,我们就可以自由定义命令行参数。

我们再来看下 test_sample.py 文件的代码:

python
import pytest

class TestDemo:
    def test_secret_auth(self, auth):
        print("\nmy auth are {}".format(auth))
        assert True

这段代码非常简单,定义了一个测试函数 test_secret_auth,其参数是之前定义的 fixture 函数auth。

在命令行中通过如下方式运行:

python
pytest tests_command_lines -k "secret_auth" -s -v --auth iTesting

运行结束后,可以发现我们传递的自定义命令行参数 auth 的值被正确接收并显示:

python
# 部分运行输出
collected 1 item                                                                                             tests_command_lines/test_sample.py::TestDemo::test_secret_auth
my auth are iTesting
PASSED

通过自定义命令行参数,可以扩展测试框架功能。例如可以通过命令行参数实现,当我从命令行传递某个具体的指令时,skip 掉一些测试用例。

如果你仔细观察上文中pytest_addoption 这个函数,你会发现它定义命令行参数使用了 parser.addoption。那么这个parser.addoption是什么呢?

下图是 parser.addoption 的源代码:

通过代码可以发现,addoption 接收如下参数。

  • *optnames:它是一个可变字符串,用来表示 option 的名称,例如上面例子中的"--auth";

  • attrs:attrs 是关键字参数,通常以 key:value 的形式存在,它能接收的参数与标准库 argparse**的 add_argument() 这个函数可接收的参数一致。

自主定义命令行参数(argparse 库)

如果你进一步查看 pytest 的源码,你将发现其实 pytest 中用于解析命令行参数的 parser 正是argparse 这个标准库

argparse 是内置于标准库中的,用于 python 命令行解析的模块,使用 argparse 可以直接在命令行中向程序传入参数。argparse 用法,一般遵循如下步骤。

1. 导入 argparse 模块

argparse 无须安装,使用时直接 import 即可:

python
import argparse

2. 创建 ArgumentParser 对象

html
 parser = argparse.ArgumentParser()

下面列出 ArgumentParser 可接收的常用参数如下:

python
prog - 程序的名称(默认:sys.argv[0])
usage - 描述程序用途的字符串(默认值:从添加到解析器的参数生成)
description - 在参数帮助文档之前显示的文本(默认值:无)
epilog - 在参数帮助文档之后显示的文本(默认值:无)
parents - 一个 ArgumentParser 对象的列表,它们的参数也应包含在内
formatter_class - 用于自定义帮助文档输出格式的类
prefix_chars - 可选参数的前缀字符集合(默认值:'-'
fromfile_prefix_chars - 当需要从文件中读取其他参数时,用于标识文件名的前缀字符集合(默认值:None
argument_default - 参数的全局默认值(默认值: None
conflict_handler - 解决冲突选项的策略(通常是不必要的)
add_help - 为解析器添加一个 -h/--help 选项(默认值: True
allow_abbrev - 如果缩写是无歧义的,则允许缩写长选项 (默认值:True
exit_on_error - 决定当错误发生时是否让 ArgumentParser 附带错误信息退出。 (默认值: True)

一般情况下,prog、usage、description 这三个参数使用得比较多。

3. 添加参数

python
# 可以通过add_argument添加一个或多个参数
parser.add_argument()

add_argument() 方法用于指定程序能够接受哪些命令行参数,add_argument() 接收两种类型的参数,分别是位置参数 (Positional arguments) 和可选参数 (Optional arguments)。下面详细介绍下两种参数。

  • 位置参数(Positional arguments)

位置参数是指必须传递的参数,如果不传递就会报错。例如,在 tests_command_lines 文件夹下创建一个文件:

python
# test_argparse.py
import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("name", help="This is a demo",action="store")
    args = parser.parse_args()
    if args.name:
        print(args.name)

首先,在命令行中查看支持的参数:

python
# 定位到test_argparse.py所在的文件夹,然后直接执行
D:\_Automation\lagouAPITest\tests_command_lines>python test_argparse.py -h

你会看到如下输出:

python
D:\_Automation\lagouAPITest\tests\tests_command_lines>python test_argparse.py -h
usage: test_argparse.py [-h] name
positional arguments:
  name        This is a demo

optional arguments:
  -h, --help  show this help message and exit

通过 -h 命令,可以查看定义的所有位置参数(本例中是 name)。

下面不给定位置参数直接运行:

python
D:\_Automation\lagouAPITest\tests\tests_command_lines>python test_argparse.py

运行结果如下:

python
usage: test_argparse.py [-h] name
test_argparse.py: error: the following arguments are required: name

由此可见,如果定义了位置参数,则位置参数必须传递。

如果给定参数的值运行:

python
D:\_Automation\lagouAPITest\tests\tests_command_lines>python test_argparse.py iTesting

则会运行成功。

  • 可选参数(Optional arguments)

相对于位置参数,可选参数的定义如下:

python
# test_argparse.py
import argparse

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--name", default='iTesting', help="This is a demo",action="store")
    args = parser.parse_args()
    if args.name:
        print(args.name)

不给定可选参数直接运行:

python
D:\_Automation\lagouAPITest\tests\tests_command_lines>python test_argparse.py

运行结果如下:

python
D:\_Automation\lagouAPITest\tests\tests_command_lines>python test_argparse.py
iTesting

当可选参数不填写时,运行也不会报错。如果可选参数有默认值,argparse 将会把此可选参数默认传入。

4.解析参数

解析参数即"步骤3 添加参数"中的对参数的解析:

python
args = parser.parse_args()

ArgumentParser 通过 parse_args() 方法,把每个参数转换为适当的类型然后调用相应的操作。例如"**步骤3 添加参数"**中的打印语句:

python
if args.name:
        print(args.name)

了解了 argparse 的基本使用后,我们就可以通过命令行参数来更好的执行测试。更多关于 argparse 的高级用法,请直接参考 Python 标准库。

测试框架雏形 ------ 创建自己的命令行程序

现在我们知道,有了 argparse 就可以通过命令行参数来进行测试,那么假设你有一个测试框架,如何让你的测试框架也支持通过命令行测试呢?或者说,你有没有好奇过,为什么我们在命令行中直接输入"pytest", 测试脚本就会运行?

下面,我们就通过创建一个支持命令行参数的"测试框架"来解开这个疑惑。

1. 创建项目

首先,创建一个项目,名称为 iTestingDemoFramework。

python
|--iTestingDemoFramework
    |--iTesting
        |--main.py
        |--__init__.py
    |--setup.py

其次,更新各个文件的代码。其中 main.py 的内容如下:

python
# main.py
import argparse

def main():
    parser = argparse.ArgumentParser(prog='iTesting', usage='This is a demo, please follow iTesting on wechat')
    parser.add_argument("name", default='iTesting', help="This is a demo framework", action="store")
    args = parser.parse_args()
    if args.name:
        print("Hello, My name is Kevin Cai, Please search and follow below account from wechat:\n")
        print(args.name)

if __name__ == "__main__":
    main()

setup.py 的代码如下:

python
from setuptools import setup, find_packages
setup(
    name='iTesting',
    version='0.1',
    description='This is a demo framework',
    author='kevin.cai',
    author_email='testertalk@outlook.com',
    zip_safe=False,
    include_package_data=True,
    packages=find_packages(),
    license='MIT',
    url='https://www.helloqa.com',
    entry_points={
        'console_scripts': [
            'iTesting = iTesting.main:main'
        ]
    }
)

setup.py 文件是 python 模块安装所需要的文件,它描述了你的模块的各项信息。你可以按照上面的代码,创建自己的 setup.py 文件。更多关于 setup.py 的知识,请查看 setuptools

2. 检验 setup.py

在命令行中切换到项目所在的根目录,执行如下命令:

python
# 切换到项目根目录, 此处是iTestingDemoFramework
python setup.py check

这个命令会验证 setup.py 里所填写内容的正确性及安装相应的依赖包(如果有的话)。如果 setup.py 的内容及格式不正确,则命令行中会提示。

3. 本地验证

当执行检验 setup.py,没有错误提示后,我们就可以在本地验证包的内容:

python
# 1.项目根目录下,此处为iTestingDemoFramework。执行
python setup.py install
# 2.执行成功后,在命令行执行如下命令:
iTesting -h

执行后,可以看到 Console 的输出如下:

html
(venv) D:\_Automation\iTestingDemoFramework>iTesting -h
usage: This is a demo, please follow iTesting on wechat
positional arguments:
  name        This is a demo framework
optional arguments:
  -h, --help  show this help message and exit

注意:此刻执行命令时不必在项目根目录下,你可以打开命令行直接输入 iTesting 也是可以执行的,因为 iTesting 这个程序已经被我们注册到系统了。

继续测试,打开命令行,直接输入我们定义的命令行参数 name 的值:

python
iTesting iTesting

执行完毕后输出如下:

python
Hello, My name is Kevin Cai, Please search and follow below account from wechat:
iTesting

由此可以看到,命令行程序创建成功。如果 iTesting 是你的测试框架,是不是就意味着你的测试框架可以直接通过命令行运行了。

其实这也是为什么你在命令行中直接执行 pytest,pytest 会运行的原因。

当然 pytest 里还有查找测试用例的逻辑,但是我们的程序 iTesting 里没有实现。

测试框架雏形 ------上传自己的命令行程序

当你的测试框架开发完成,并且支持命令行执行后,你就可以上传至 Pypi,分享给更多人使用。

PyPI(Python Package Index) 是 python 官方的第三方库的仓库,可以帮助我们查找和安装 Python 社区开发和共享的软件。

下面演示下如何将你的应用程序打包(以 Win 10 系统为例)。

1. 注册 PyPI 账户

要上传你自己打包好的程序到 PyPI,必须先注册一个账户,登录PyPI 官网直接注册即可。

2. 打包程序

你的程序要上传到 PyPI,必须打包。在命令行中执行如下命令,将程序打包:

python
# 切换到项目根目录, 此处是iTestingDemoFramework
python setup.py sdist build

打包成功后,你会看到项目根目录下多了三个文件夹,分别是 build、dist 以及 iTesting.egg-info,如下图所示:

打包的方式有很多种,除了上述所讲外,还可以用 wheel 打包。

首先安装 wheel:

java
pip install wheel

安装好后,就可以把应用程序打包:

java
python setup.py bdist_wheel

wheel 翻译过来就是"轮子"的意思,所以创建自己的程序并打包成 wheel,就是一个造轮子的过程。后来造轮子,专指编写与已有应用程序拥有相同功能的程序。

3. 发布程序

首先,应该确保 twine 这个库安装。

Twine 是一个在 PyPI 上发布 Python 包的实用程序。

python
pip install twine

其次,通过命令行工具,在你的项目根目录下,通过如下命令上传:

python
# 在项目根目录下执行,本例为 iTestingDemoFramework
D:\_Automation\iTestingDemoFramework>twine upload dist/*

执行后,系统会交互式询问,让你提供 PyPI 上注册的用户名和密码,填写正确即可上传成功:

最后访问上传后的地址,你可以看到,我们创建的程序已经被正确上传。

4. 通过 pip 安装使用

应用程序上传至 PyPI 后,每个人都可以通过 pip install 直接下载使用,在命令行中输入:

java
pip install iTesting

注意,此命令可在任意目录下执行,不必限制在我们项目根目录下,因为我们的程序已经上传至 PyPI。

你会看到如下安装界面:

安装成功后,我们来测试下我们的功能, 在命令行中直接输入:

java
iTesting iTesting

可以看到,结果如下:

java
Hello, My name is Kevin Cai, Please search and follow below account from wechat:
iTesting

至此,你的"测试框架"已经上架并可被任何人下载使用。

小结

我来总结下今天所讲的内容。

本章从 pytest 命令行参数引入,首先回顾了下 pytest 内置的命令行参数使用(例如 -k 的使用);接着,在 pytest 框架中,创建自己的自定义参数来扩展应用程序功能。

然后我们更进一步,通过阅读 pytest 源码,得知 pytest 解析命令行参数正是用的 argparse 这个标准库,于是我介绍了 argparse 的基本使用,并带领大家实现了一个基于命令行的测试框架雏形(虽然什么具体功能都没有),并结合打包程序上传至 PyPI。

通过这个过程,你应该对如何开发支持命令行参数的测试框架,如何发布测试框架供他人使用这个流程有了比较清楚的了解。

好的,在后面的章节中,我将继续带领你,深入测试框架原理。

我是蔡超,我们下节课再见。

更多关于"测试框架"的知识,请关注公众号 iTesting 并回复"测试框架"查看。


课程评价入口,挑选 5 名小伙伴赠送小礼品~