可插拔黑盒测试工具包
pbbt的Python项目详细描述
目录
Overview
pbbt是黑盒测试的回归测试工具。它是 适用于测试具有明确输入的复杂软件组件 和输出接口。
input +----------+ output o--------> | Software | --------->o +----------+
在黑盒测试中,test case是input和 需要output数据。测试线束使用 给定输入并验证生成的输出与 预期产出。
黑盒测试可以用于许多不同类型的 软件。例如,
- a数据库系统:输入是一个sql语句,输出是一个集合 记录的数量;
- web服务:输入是http请求,输出是http 响应;
- a命令行实用程序:输入是一个命令行序列 参数和stdin,输出是stdout;
- gui应用程序:可以使用不同的方法;例如, 输入可以是一系列用户操作,输出可以是 特定小部件的状态。
pbbt是一个python库和一个应用程序,它允许您:
- 使用内置测试类型测试命令行脚本和python 代码;
- 注册自定义测试类型;
- 以简洁的YAML格式准备测试输入;
- 在train模式下,运行测试用例并记录预期输出;
- 在check模式下,运行测试用例并验证生成的 输出与预先记录的预期输出一致。
PBBT是麻省理工学院许可下发布的免费软件。PBBT是由 克拉克·C·埃文斯和基里尔·西蒙诺夫来自Prometheus Research, LLC。
Using PBBT
要安装pbbt,可以使用pip包管理器:
# pip install pbbt
此命令从下载并安装最新版本的pbbt Python Package Index。成功安装后,您应该 能够导入pbbtpython模块并运行pbbt命令行 公用事业。
要开始使用pbbt,需要创建一个包含输入数据的文件。为了 例如,使用以下内容创建input.yaml:
py: | print "Hello, World!"
此文件采用YAML格式,这是一种数据序列化语言 类似于JSON,实际上是json的超集。上面的文件 可以用json表示为:
{ "py": "print \"Hello, World!\"\n" }
有关yaml语法和语义的描述,请参见http://yaml.org/。
接下来,在training模式下执行pbbt以生成预期的输出数据。 运行:
$ pbbt input.yaml output.yaml --train
并接受新的输出。PBBT将输出数据写入 output.yaml:
py: print-hello-world stdout: | Hello, World!
现在您可以在checking模式下启动pbbt,在该模式下执行测试 案例并验证预期和实际输出数据是否一致:
$ pbbt input.yaml output.yaml
要向input.yaml添加更多测试用例,需要将其转换为 测试套件:
title: My Tests tests: - py: | print "Hello, World!" - sh: echo Hello, World!
该文件现在包含一个测试套件my tests和两个测试用例:一个 和前面的例子一样,还有一个执行shell命令 echo Hello, World!:
sh: echo Hello, World!
这个测试用例的输出是由shell生成的stdout。 命令。要记录预期输出,请在训练模式下运行pbbt。 再一次。
Built-in Test Types
开箱即用,PBBT支持一小组预定义的测试类型:
- 测试python代码;
- 测试shell命令;
- 文件操作测试。
还提供特殊测试类型:
- T型EST套房;
- 包括;
- 条件变量。
- 其他测试系统的网关。
每个测试类型定义输入和输出记录的结构,即 是,强制字段和可选字段的集合以及字段的类型 价值观。在本节中,我们列出所有可用的测试类型并描述 它们的输入字段。
Common Fields
以下可选字段适用于所有测试类型,其中 它们有意义:
- skip:true或false
- 在true,跳过此测试用例。
- if:变量、变量列表或python表达式
在variable name上,仅当变量是 已定义且不为假。
在变量列表中,仅当至少有一个 变量已定义且不为false。
在python表达式上,如果表达式 计算结果为true。您可以在 表达。
- unless:变量、变量列表或python表达式
在variable name上,如果定义了变量,则跳过此测试用例 不是假的。
在变量列表中,如果至少有一个 变量已定义且不为false。
对于python表达式,如果表达式 计算结果为true。您可以在 表达。
- ignore:true,false或正则表达式 在{tt12}$上,允许实际和预期的输出不相等。 测试用例仍然必须在没有任何错误的情况下执行。
- 对输出数据运行正则表达式并查找 所有匹配项。
- 如果正则表达式不包含()子组, 从输出中删除所有匹配项。
- 如果正则表达式包含一个或多个()子组, 从输出中删除子组的内容。
在正则表达式上,预处理实际的和预期的 比较前输出:
正则表达式是用MULTILINE和 VERBOSE标志。
示例:
title: Integration with MySQL if: has_mysql tests: - set: MYSQL_HOST: localhost MYSQL_PORT: 3306 unless: [MYSQL_HOST, MYSQL_PORT] - read: /etc/mysql/my.cnf if: MYSQL_HOST == 'localhost' - py: test-scalar-types.py ignore: | ^Today:.(\d\d\d\d)-(\d\d)-(\d\d)$ - py: test-array-types.py skip: true # No array type in MySQL
Test Suite
测试套件是测试用例的集合。
一个套件可以包含其他套件,因此所有测试套件形成一棵树 结构。由套件标识符形成的path可以唯一地定位 任何套房。我们对套件路径使用文件系统表示法(例如 /path/to/suite)。
字段:
- title:文本
- 套房的名称。
- suite:标识符(可选)
- 套件的标识符。如果未设置,则从标题生成。
- tests:输入记录列表
- 套房的内容。
- output:路径(可选)
- 如果设置,则从给定的 文件。
示例:
title: All Tests suite: all output: output.yaml tests: - py: ./test/core.py - py: ./test/ext.py - title: Database Tests tests: - py: ./test/sqlite.py - py: ./test/pgsql.py - py: ./test/mysql.py
在本例中,数据库测试套件的路径是 /all/database-tests。
Conditional Variables
这个测试用例定义了一个条件变量。
变量可以在if和unless子句中使用 有条件地启用或禁用测试用例。变量也可以是 通过全局字典__pbbt__设置或读取python测试。
也可以使用^{tt34}从命令行设置条件变量$ 选择。
设置条件变量会影响 相同的测试套件。从套件出口处重新设置变量值。
字段:
- set:变量或变量字典
在variable name上,将给定变量的值设置为 True。
在dictionary上,设置给定变量的值。
示例:
title: MySQL Tests tests: - set: MYSQL - set: MYSQL_HOST: localhost MYSQL_PORT: 3306 unless: [MYSQL_HOST, MYSQL_PORT] - py: | # Determine the version of the MySQL server import MySQLdb connection = MySQLdb.connect(host=__pbbt__['MYSQL_HOST'], port=int(__pbbt__['MYSQL_PORT']), db='mysql') cursor = connection.cursor() cursor.execute("""SELECT VERSION()""") version_string = cursor.fetchone()[0] version = tuple(map(int, version_string.split('-')[0].split('.'))) __pbbt__['MYSQL_VERSION'] = version - py: test-ddl.py - py: test-dml.py - py: test-select.py - py: test-new-features.py if: MYSQL_VERSION >= (5, 5)
Include Test
这个测试用例从一个文件。
字段:
- include:路径
- 要加载的文件。该文件应包含 yaml格式。
示例:
title: All Tests tests: - include: test/core.yaml - include: test/ext.yaml - include: test/sqlite.yaml - include: test/pgsql.yaml - include: test/mysql.yaml
Python Code
这个测试用例执行python代码并生成stdout。
字段:
- py:路径或python代码
onpython代码,要执行的源代码。
在文件名上,包含要执行的源代码的文件。
- stdin:文本(可选)
- 标准输入的内容。
- except:异常类型(可选)
- 如果已设置,则指示代码将引发 给定类型。
示例:
title: Python tests tests: - py: hello.py - py: &sum | # Sum of two numbers import sys a = int(sys.stdin.readline()) b = int(sys.stdin.readline()) c = a+b sys.stdout.write("%s\n" % c) stdin: | 2 2 - py: *sum stdin: | 1 -5 - py: *sum stdin: | one three except: ValueError
注意,我们使用yaml锚(由&sum表示)和别名 (用*sum表示)在多个测试中使用同一段代码。
Shell Command
这个测试用例执行一个shell命令并生成stdout。
字段:
- sh:带有参数列表的命令或可执行文件
- 要执行的shell命令。
- stdin:文本(可选)
- 标准输入的内容。
- cd:路径(可选)
- 将当前工作目录更改为之前指定的路径 执行命令。
- environ:变量字典(可选)
- 将给定的变量添加到命令环境中。
- exit:整数(可选) < d> >期望的退出代码;默认情况下{tT50}$。< /dd>
示例:
title: Shell tests tests: - sh: echo Hello, World! - sh: cat stdin: | Hello, World! - sh: [cat, /etc/shadow] exit: 1 # Permission denied
Write to File
此测试用例创建具有给定内容的文件。
字段:
- write:路径
- 要创建的文件。
- data:文本
- 文件内容。
示例:
write: test/tmp/data.txt data: | Hello, World!
Pytest
此测试用例执行py.test测试套件。
必须安装来自http://pytest.org/的包pytest。
字段:
- pytest:路径模式
- 带有py.test测试的文件。
示例:
pytest: test/test_*.py
Coverage
这个测试用例开始覆盖python代码。
来自http://nedbatchelder.com/code/coverage/的包coverage必须 安装。
字段:
- coverage:文件名或None
- 配置文件的路径。
- data_file:文件名
- 保存覆盖率数据的位置。
- timid:false或true(可选)
- 使用更简单的跟踪函数。
- branch:false或true(可选)
- 启用分支覆盖范围。
- source:包名称的文件路径(可选)
- 要测量的源文件或包。
- include:文件模式
- 要测量的文件。
- omit:文件模式
- 要忽略的文件。
示例:
coverage: source: pbbt branch: true
Custom Test Types
在本节中,我们将讨论如何向pbbt添加自定义测试类型。
假设我们想通过运行一系列sql来测试sql数据库 查询并验证是否获得预期输出。为了实现这个 测试方案,我们需要一种方法:
- 打开到数据库的连接;
- 执行SQL语句并生成一系列记录。
输入文件可能如下所示:
title: Database tests tests: # Remove the database file left after the last testing session. - rm: test.sqlite # Create a new SQLite database. - connect: test.sqlite # Run a series of SQL commands. - sql: | SELECT 'Hello, World!'; - sql: | CREATE TABLE student ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, gender CHAR(1) NOT NULL CHECK (gender in ('f', 'i', 'm')), dob DATE NOT NULL ); - sql: | INSERT INTO student (id, name, gender, dob) VALUES (1001, 'Linda Wright', 'f', '1988-10-03'), (1002, 'Beth Thompson', 'f', '1988-01-24'), (1003, 'Mark Melton', 'm', '1984-06-05'), (1004, 'Judy Flynn', 'f', '1986-09-02'), (1005, 'Jonathan Bouchard', 'm', '1982-02-12'); - sql: | SELECT * FROM student ORDER BY dob; - sql: | SELECT name FROM student WHERE id = 1003;
在这个输入文件中,我们使用两种新类型的测试用例:
- connect
- 指定到sqlite数据库的连接。
- sql
- 指定要执行的SQL语句。
我们将编写一个pbbt扩展来实现这些测试类型。
创建包含以下内容的文件sql.py:
from pbbt import Test, Field, BaseCase, MatchCase, listof import sqlite3, traceback, csv, StringIO @Test class ConnectCase(BaseCase): class Input: connect = Field(str) def check(self): self.state['connect'] = None try: self.state['connect'] = sqlite3.connect(self.input.connect) except: self.ui.literal(traceback.format_exc()) self.ui.warning("exception occurred while" " connecting to the database") self.ctl.failed() else: self.ctl.passed() @Test class SQLCase(MatchCase): class Input: sql = Field(str) class Output: sql = Field(str) rows = Field(listof(listof(object))) def render(self, output): stream = StringIO.StringIO() writer = csv.writer(stream, lineterminator='\n') writer.writerows(output.rows) return stream.getvalue() def run(self): connection = self.state.get('connect') if not connection: self.ui.warning("database connection is not defined") return rows = [] try: cursor = connection.cursor() cursor.execute(self.input.sql) for row in cursor.fetchall(): rows.append(list(row)) except: self.ui.literal(traceback.format_exc()) self.ui.warning("exception occurred while" " executing a SQL query") connection.rollback() new_output = None else: connection.commit() new_output = self.Output(sql=self.input.sql, rows=rows) return new_output
要使用此扩展,请将参数-E sql.py添加到所有pbbt 召唤。例如:
$ pbbt -E sql.py input.yaml output.yaml --train
现在我们将逐行解释sql.py的内容。
第一行导入我们将使用的一些类和装饰器:
from pbbt import Test, Field, BaseCase, MatchCase
要注册测试类型,请使用@Testdecorator。这里有一个 通用模板:
@Test class CustomCase(object): class Input: some_field = Field(...) [...] class Output: some_field = Field(...) [...] def __init__(self, ctl, input, output): self.ctl = ctl self.input = input self.output = output def __call__(self): [...] return new_output
decorator的参数必须是遵循以下内容的类 规则:
输入和输出记录的结构用嵌套 类Input和Output。记录字段使用 Field描述符。
要创建测试用例,测试工具将生成类的实例。 类构造函数接受三个参数:
- ctl
测试线束控制器。它用于用户交互, 报告测试成功或失败,并作为 条件变量。
- input
输入记录。
- output
预期的输出记录或None。
测试用例通过调用其__call__()方法执行。这个 方法必须运行测试用例,生成新的输出记录,并比较 它具有给定的预期输出记录。
如果预期和实际输出记录不一致:
- 在check模式下,该方法必须报告失败。
- 在train模式下,该方法可以请求用户允许 更新预期输出。如果要更新预期输出,则 方法应返回新的输出记录。
让我们回顾一下connecttest类型,它由 ConnectCase类:
@Test class ConnectCase(BaseCase): class Input: connect = Field(str) def check(self): [...]
ConnectCase继承自BaseCase,它适合于 不产生输出数据并在其一侧执行的测试 影响。嵌套的Input类定义用于声明 输入记录的字段。在这种情况下,输入记录只有一个 文本字段connect,其中包含数据库的名称。
从BaseCase继承的测试类型必须重写抽象方法 check():
import sqlite3, traceback [...] @Test class ConnectCase(BaseCase): [...] def check(self): self.state['connect'] = None try: self.state['connect'] = sqlite3.connect(self.input.connect) except: self.ui.literal(traceback.format_exc()) self.ui.warning("exception occurred while" " connecting to the database") self.ctl.failed() else: self.ctl.passed()
此代码试图创建新的数据库连接并将其存储为 条件变量connect。如果尝试失败,它将调用 ui.literal()显示异常回溯和ctl.failed() 报告测试失败。否则,ctl.passed()被调用 表示测试成功。
接下来,让我们回顾一下sql测试类型:
@Test class SQLCase(MatchCase): class Input: sql = Field(str) class Output: sql = Field(str) rows = Field(listof(listof(object))) def run(self): [...] def render(self, output): [...]
此测试类型同时具有输入和输出记录,如下所述 使用Input和Output嵌套类。输入记录包含 一个字段sql,一个要执行的sql查询。输出记录包含 两个字段:sql和rows。字段sql包含相同的sql 查询并用于将输出记录与 输入记录。字段rows包含生成的输出行列表 通过sql查询。
^ tT115}$继承自^ {TT94} $,这是一个MIXIN类 用于生成文本输出的测试类型。从继承的测试类型 MatchCase必须重写两个方法:
- run()
- 执行测试用例并返回生成的输出记录。
- render(output)
- 将输出记录转换为可打印格式。
在SQLCase中,render()通过将输出行转换为 CSV格式:
import csv, StringIO [...] @Test class SQLCase(MatchCase): [...] def render(self, output): stream = StringIO.StringIO() writer = csv.writer(stream, lineterminator='\n') writer.writerows(output.rows) return stream.getvalue()
方法run()的实现如下:
@Test class SQLCase(MatchCase): [...] def run(self): connection = self.state.get('connect') if not connection: self.ui.warning("database connection is not defined") return rows = [] try: cursor = connection.cursor() cursor.execute(self.input.sql) for row in cursor.fetchall(): rows.append(list(row)) except: self.ui.literal(traceback.format_exc()) self.ui.warning("exception occurred while" " executing a SQL query") connection.rollback() new_output = None else: connection.commit() new_output = self.Output(sql=self.input.sql, rows=rows) return new_output
Command-line Interface
用法:
pbbt [<options>] INPUT [OUTPUT]
这里,INPUT和OUTPUT是包含输入和输出的文件 分别测试数据。
提供以下选项:
- -h,--help
- 显示使用信息并退出.< /dD>
- -q,--quiet
- 仅显示警告和错误。
- -T,--train
- 在培训模式下运行测试。
- -P,--purge
- 清除过时的输出数据。
- -M N,--max-errors N
- 在N测试失败后停止;0表示“从不”。
- -D VAR,-D VAR=VALUE,--define VAR,--define VAR=VALUE
- 设置条件变量。
- -E FILE,-E MODULE,--extend FILE,--extend MODULE
- 从文件或python模块加载pbbt扩展名。
- -S ID,--suite ID
- 运行特定的测试套件。
PBBT还可以从以下文件中读取配置:
- setup.cfg
- 此文件为ini格式,在节中定义了pbbt设置 [pbbt]。可以识别以下参数:extend, ^{TT89}$,^{TT29}$,^{TT152}$,^{TT27}$,^{TT154}$,^{TT155}$, max_errors,quiet。
- pbbt.yaml
- 此文件必须是具有以下键的yaml文件:extend, ^{TT89}$,^{TT29}$,^{TT152}$,^{TT27}$,^{TT154}$,^{TT155}$, max-errors,quiet。
PBBT也可以作为distutils命令执行:
python setup.py pbbt
在这种情况下,pbbt配置可以在^{tt147}中指定$ 或者通过命令行参数。
API Reference
- pbbt.maybe(T)
- 用于isinstance(X, ...)的伪类型:检查X是否是 T或等于None的实例。
- pbbt.oneof(T1, T2, ...)
- 用于isinstance(X, ...)的伪类型:检查X是否是 T1的实例或T2的实例等。
- pbbt.choiceof(values)
- 用于isinstance(X, ...)的伪类型:检查X是否等于 给定值之一。
- pbbt.listof(T, length=None)
- 用于isinstance(X, ...)的伪类型:检查X是否是 T的元素。
- pbbt.tupleof(T1, T2, ...)
- 用于isinstance(X, ...)的伪类型:检查X是否是元组 包含类型为T1、T2等的字段。
- pbbt.dictof(T1, T2)
- 用于isinstance(X, ...)的伪类型:检查X是否是 带键类型T1和值类型T2的字典。
- pbbt.raises(E)
- 使用withwith子句验证嵌套块是否引发 给定类型的异常。
- pbbt.raises(E, fn, *args, **kwds)
- 验证函数调用fn(*args, **kwds)是否引发 给定类型的异常。
- pbbt.Test(CaseType)
- 将给定类注册为测试类型。
- pbbt.Field(check=None, default=REQUIRED, order=None, hint=None)
描述输入或输出记录的字段。
- check
- 字段值的类型。
- default
- 如果缺少字段,则为默认值。如果未设置,则 字段是必需的。
- order
- 如果已设置,则允许覆盖默认字段顺序。
- hint
- 字段的简短描述。
- pbbt.Record(*p_fields, **kv_fields)
所有输入和输出记录的基类。
Testdecorator转换嵌套的Input和Output 类到Record子类。
可以使用或重写以下方法:
- classmethod __recognizes__(keys)
检查密钥集是否与记录类型兼容。
默认实现检查密钥集是否包含 所有必需的记录字段。
- classmethod __load__(mapping)
- 从记录键和 数值。
- __dump__()
- 生成字段键和值的列表。
- __complements__(other)
- 检查两个记录是否是互补的输入和输出记录 对于相同的测试用例。
- __clone__(**kv_fields)
- 用给定字段的新值复制记录。
- __str__()
- 生成可打印的表示。
- pbbt.Control(...)
测试线束。
测试线束对象作为第一个对象传递给测试用例 构造函数的参数。以下方法和属性 可由测试用例对象使用。
属性:
- ui
- 提供用户交互服务。
- training
- 如果设置,则线束处于训练模式。
- purging
- 如果设置,则线束处于清除模式。
- quiet
- 如果设置,则只显示警告和错误。
- halted
- 如果设置,则测试过程已停止。
- state
- 条件变量。
- selection
- 选定套房。
方法:
- passed(text=None)
- 证明当前测试用例已通过。
- failed(text=None)
- 证明当前测试用例失败。
- updated(text=None)
- 证明当前测试用例的输出数据 已更新。
- halt(text=None)
- 停止测试过程。
- load_input(path)
- 从给定文件加载输入测试数据。
- load_output(path)
- 从给定文件加载输出测试数据。
- dump_output(path, data)
- 将输出测试数据保存到给定文件。
- run(case)
- 执行测试用例。
- __call__(input_path, output_path)
- 使用给定的输入和输出运行测试过程。
- pbbt.locate(record)
- 查找给定记录的位置。
- pbbt.Location(filename, line)
- 输入或输出记录在yaml文档中的位置。
- pbbt.run(input_path, output_path=None, **configuration)
从给定文件加载测试数据并运行测试。
配置:
- ui
- 用户交互控制器。
- variables
- 预定义的条件变量。
- targets
- 选定套房。
- training
- 将线束设置为训练模式。
- purging
- 清除过时的输出数据。
- max_errors
- 在吊带停止之前最大允许的故障数。
- quiet
- 仅显示警告和错误。
- pbbt.main()
- 实现pbbt命令行实用程序。
- pbbt.BaseCase
适合大多数测试类型的模板类。
子类需要重写以下方法:
- check()
- 以检查模式运行测试用例。
- train()
- 以列车模式运行测试用例。默认实现 只需调用check()。
- pbbt.MatchCase
生成输出的测试类型的模板类。
子类需要重写以下方法:
- run()
- 执行案例;返回生成的输出。
- render(output)
- 将输出记录转换为文本。
- pbbt.UI
用户交互服务的抽象类。
方法:
- part()
- 开始新的部分。
- section()
- 开始一个小节。
- header(text)
- 显示节标题。
- notice(text)
- 显示通知。
- warning(text)
- 显示警告。
- error(text)
- 显示错误。
- literal(text)
- 显示文本。
- choice(text, *choices)
- 问一个单选题。
- pbbt.ConsoleUI(stdin=None, stdout=None, stderr=None)
- 实现控制台输入/输出的UI。
- pbbt.SilentUI(backend)
- 实现与--quiet选项一起使用的UI。