## BDD 与 pytest-bdd
BDD 即 Behavior-driven development,行为驱动开发。BDD行为驱动是一种敏捷开发模式, 重点在于消除开发/测试对需求了解的歧义及用户场景的验证。
pytest-bdd 是一个BDD测试框架,类似于behave, cucumber。它可以统一单元测试和功能测试。
## 环境准备
首先需要确定是否安装了pytest-bdd框架,通过 `pip show pytest-bdd` 命令可以查看是否安装了pytest-bdd框架:
![](https://img.kancloud.cn/dc/94/dc944bc7def87bdb490e565c0c3125ce_377x44.png)
出现如上页面说明没有安装,安装pytest-bdd框架:使用pip安装pytest-bdd模块。
```
pip install pytest-bdd
```
备注: 也可以使用 `pip list` 命令查看所有已经安装的模块。
##示例场景与步骤
这里测试一个加法运算器, 在实际场景中验证加法使用Unit 测试即可,BDD 一般用作功能测试,这里为了演示方便使用该场景作为示例。
具体步骤如下:
1. 创建用户场景文件(.feature后缀的文件)
2. 编写步骤函数和测试场景。(这两者可以分开为不同文件,也可以合并在一起写)
3. 开始测试
## 项目目录
pytest-bdd 对于目录的要求非常灵活,可以根据自己的项目结构进行配置。通常情况下,pytest-bdd 会将 feature 文件和 step 实现文件分别放置在不同的目录中,这两个目录可以分别为`features/`和`step_defs/`,也可以根据自己的需要进行修改。同时,pytest-bdd 还可以支持多个 feature 目录和 step 实现目录,只需要在配置文件中进行相应的配置即可。
除了 feature 文件和 step 实现文件的目录外,pytest-bdd 还可以支持其他的目录,例如 fixture 目录、data 目录等。这些目录同样可以根据自己的需要进行配置。总之,pytest-bdd 的目录要求非常灵活,可以根据自己的项目进行配置。
简单起见, 这里演示的目录结构如下所示:
```
├────features/ # 用户场景
│ ├────calculator.feature
├────step_defs/ # 步骤函数和测试场景
│ ├────test_calculator.py
├────util/
│ └────calculator.py # 需要测试的计算器类
```
## 文件内容
0. 用于测试的计算器类文件calculator.py的内容如下:
```
# calculator.py
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
```
1. 用户场景文件calculator.feature 内容如下:
```
Feature: Addition
Scenario: Add two numbers
Given I have a calculator
When I enter "1" and "2"
Then the result should be "3"
```
2. 步骤函数和测试场景文件test_calculator.py 内容如下:
```
import sys
import os
import pytest
#sys.path.append('D:/devworkspace/python-ency/chp3/tests/bdd/util')
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'util'))
from calculator import Calculator
from pytest_bdd import scenario, given, when, then, parsers
@scenario('../features/calculator.feature','Add two numbers')
def test_add():
print(sys.path.append(os.path.dirname(os.path.dirname(__file__))+'util'))
pass
@pytest.fixture
@given("I have a calculator")
def calculator():
return Calculator()
@when(parsers.parse('I enter "{a}" and "{b}"'))
def enter_numbers(calculator, a, b):
calculator.a = int(a)
calculator.b = int(b)
@then(parsers.parse('the result should be "{result}"'))
def verify_result(calculator, result):
assert calculator.add(calculator.a, calculator.b) == int(result)
```
## 测试
命令行切换到对应的目录,执行 `pytest`即可。执行的效果如下图:
![](https://img.kancloud.cn/ca/ca/caca55b485d9f4bee8edf0f3b0c31340_1393x170.png)
## 问题
### 自定义类的导入方式
```
from calculator import Calculator
E ModuleNotFoundError: No module named 'calculator'
```
1. 添加绝对路径导入
```
import sys
sys.path.append("E:/mybdd/util")
```
2. 添加上层目录
```
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
```
修改`sys.path`之后的导入行为会对你的整个 Python 环境产生影响,使用时需要谨慎。
3. 相对导入
`.`来表示当前目录,两个(或更多的)点`..`来表示上一层(或更多层)目录
相对导入只有在作为模块的一部分时才能工作,也就是说,你不能直接运行一个使用了相对导入的 Python 文件,你需要通过主模块或者 -m 标志运行。
Python 的相对导入基于当前的模块名称。所以,你不能在一个脚本直接运行时使用相对导入,因为脚本的`__name__`属性为`__main__`,Python 就不知道如何找到父模块或者兄弟模块。
**将你的脚本作为模块执行**
你可以使用`-m`选项来告诉 Python 运行包含相对导入的脚本作为一个模块。首先,确保你在当前执行目录的上级目录中,然后使用类似以下的命令:
```
python -m mypackage.mysubpackage.mymodule
```
对于相对导入,你必须以包的方式运行你的项目(也就是说目录下需要有`__init__.py`文件,让Python把这个目录看作包),例如你可能需要在项目的顶层目录下运行`python -m main`,而非`python main.py`。
4. **使用环境变量 PYTHONPATH**:
也可以通过在环境变量`PYTHONPATH`中添加你的模块所在的目录,Python 在运行时会添加这些目录到`sys.path`中,这样就可以搜索到你的模块了。
export PYTHONPATH="${PYTHONPATH}:/my/new/path"
### fixture 'calculator' not found
```
@pytest.mark.usefixtures(*func_args)
def scenario_wrapper(request: FixtureRequest, _pytest_bdd_example: dict[str, str]) -> Any:
E fixture 'calculator' not found
```
注意下面代码
```
@pytest.fixture
@given("I have a calculator")
def calculator():
return Calculator()
```
`@pytest.fixture` 这一句必须加上, 很多在线的例子中都没有
### ERROR test\_calculator.py - TypeError: 'NoneType' object is not callable
```
@scenario('../features/calculator.feature','Add two numbers')
#@scenarios('../features/calculator.feature')
def test_add():
pass
```
注意:使用scenarios会出现错误
## 本篇完整示例
* [https://download.csdn.net/download/oscar999/88532317](https://download.csdn.net/download/oscar999/88532317)
## 参考
* [https://github.com/pytest-dev/pytest-bdd](https://github.com/pytest-dev/pytest-bdd)
* [https://pytest-bdd.readthedocs.io/en/stable/](https://pytest-bdd.readthedocs.io/en/stable/)
*****
*****
- 前言
- 1.入门篇
- Python介绍
- 安装与使用
- Python开发利器之VS Code
- 模块安装
- 命令行
- 一次Python无法安装模块的问题探索与解决之旅
- 命令运行
- Conda
- 下载地址
- 2.基础篇
- 基础语法
- 输入与输出
- with as的用法
- 注释
- Python命令行参数
- 编码
- 变量类型
- 列表遍历
- 运算符
- 表达式语句
- 条件
- 循环
- 日期和时间
- 函数
- 高级语法
- @符号-装饰器
- 模块和包
- name
- init.py
- 错误和异常
- 面向对象
- 3.专题篇
- 常用功能
- Python 字符串连接
- python web
- Python CGI编程
- Python OAuth2
- 认证 Flask-HTTPAuth
- 常用命令
- 内置函数
- dir()
- print(f)
- 标准模块
- sys
- pickle-数据序列化
- os
- IO(输入输出)
- 键盘输入
- 文件读写
- 测试
- Python测试框架之pytest快速入门
- pytest-bdd快速示例和问题解决
- 基于pytest-bdd的项目目录结构和命名规范
- python BDD 的相关概念
- Behave介绍和快速示例
- Python BDD之Behave测试报告
- Python BDD 框架比较之 pytest-bdd vs behave
- pytest进阶
- Flask + pytest测试
- 参考网址
- pytest-bdd进阶
- hehave进阶
- 测试路径
- python + selunium
- HTML 根据多层CSS 查找元素
- 等待执行
- 使用text 查找 span
- pytest如何在控制台输出
- 4.问题篇
- pip pip3 及区别
- TypeError: can only concatenate str (not "NoneType") to str
- 5.实战篇
- matplotlib-绘图包
- 导入类
- 命名规范
- 模块查找
- 6.进阶篇
- Flask
- Flask介绍
- Flask扩展模块
- Flask-Login
- 问题
- Jinja2
- Flask-RESTful
- Flask-JWT-Extended
- WSGI
- Flask-SQLAlchemy
- 部署
- Flask VS Django
- Flask Web
- Flask + Vue
- Flask实战
- Flask 标准目录结构
- Blueprints
- 参考
- FastAPI 测试
- https 证书 Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate