pytest
流程示例:
使用函数定义测试用例:
def test_something():
# 执行测试的代码...
assert 1 + 1 == 2
使用类和方法定义测试用例:
class TestSomething:
def test_something(self):
# 执行测试的代码...
assert 1 + 1 == 2
你可以将这些测试函数或测试方法放在任意位置,只要它们符合 pytest 的命名规则并位于以 “test_” 开头的文件中,pytest 就能够自动发现并执行它们。
当你运行 pytest 命令时,pytest 会自动搜索项目中的测试文件,并执行其中的测试函数或测试方法。
下面是 pytest 的基本使用步骤:
- 安装 pytest:
pip install pytest
-
编写测试函数或测试类:
- 使用函数定义测试用例:
def test_something(): # 执行测试的代码... assert 1 + 1 == 2
- 使用类和方法定义测试用例:
class TestSomething: def test_something(self): # 执行测试的代码... assert 1 + 1 == 2
- 使用函数定义测试用例:
-
在项目目录中创建一个名为 “test_” 开头的文件或目录,并将测试函数或测试类放入其中。
-
运行测试:
- 在命令行中运行
pytest
命令,它会自动查找并执行项目中的测试文件或目录。 - 若要仅运行特定的测试文件或用例,可以指定它们的路径作为参数:
pytest path/to/test_file.py pytest path/to/test_directory/
- 在命令行中运行
-
可选:使用 pytest 提供的丰富功能编写更灵活和可读性更高的测试代码:
- 使用断言函数替代
assert
语句来进行验证,如assertEqual
、assertTrue
、assertFalse
等。 - 使用装饰器来标记和自定义测试用例,如
@pytest.mark.parametrize
来定义参数化测试等。 - 使用
pytest.fixture
创建和管理测试数据和测试环境。 - 使用插件扩展 pytest 的功能,如 coverage、mock、pytest-html 等。
- 使用断言函数替代
0、pytest的主入口
# pytest的主入口
import pytest
if __name__ == '__main__':
pytest.main()
1、配置文件pytest.ini
[pytest]
addopts = -vs
# addopts = -vs -m mytest --alluredir=./result --clean-alluredir
# addopts = -vs --html=./test-reports/reports.html --reruns 2
# addopts = -vs -m controller
# addopts = -vs -m "slow_manager or smoke"
# target path
# testpaths = ./s
# default model rules
#python_files = xxx_*.py
#python_classes = xxx*
#python_function = xxx*
# baseurl
base_url = http://localhost:3333/api
# mark
# markers =
# slow_manager :slow
markers =
mytest : test
base_url使用
在 pytest 中,您可以使用 pytest.ini
文件来配置项目的一些基本设置,包括 base_url
。base_url
可以用于存储项目中使用的基本 URL 地址,例如网站的根地址或者 API 的根路径。
要在 pytest.ini
中使用 base_url
,您可以这样配置:
[pytest]
base_url = http://example.com
接着,您可以在测试用例中通过 request.config.getoption('base_url')
来获取配置的 base_url
,并在测试中使用它。示例如下:
import pytest
def test_url(request):
url = request.config.getoption('base_url')
assert url == 'http://example.com'
在这个示例中,test_url
测试函数通过 request.config.getoption('base_url')
获取了配置的 base_url
,然后进行断言验证。
通过这种方式,您可以在项目中统一配置 base_url
,并在测试中方便地引用,从而避免了在多个测试文件中重复定义 base_url
。
2、内置request参数是什么
用于访问测试函数、测试类或模块级别的上下文和信息。
在 pytest 中,request
参数是一个内置的 fixture,它提供了访问测试过程中的一些信息和功能。您可以在测试函数的参数列表中使用 request
,并通过它来获取测试过程中需要的各种资源、配置等信息。
request
提供了许多有用的方法和属性,其中一个常见的用法是通过 request.config.getoption()
来获取配置选项,例如获取 pytest.ini
中定义的配置。
除了获取配置外,request
还可以用于获取其他的 fixture,例如 request.fixturename
可以获取名为 fixturename
的 fixture 的值。此外,request
还可以用于获取当前测试函数的名称、模块、类等信息,以及在测试过程中动态地添加和调用 fixture。
3、@pytest.fixture
@pytest.fixture
是 Pytest 测试框架提供的装饰器,用于定义测试中需要共享的对象、数据或功能。
通过使用 @pytest.fixture
,可以将一些通用的初始化、配置或准备工作封装起来,然后在测试函数中通过参数引用这些 fixture。
在使用 @pytest.fixture
时,您可以将 fixture 函数添加到测试模块中的顶层,以便多个测试函数可以共享它。当测试函数需要使用 fixture 提供的数据或功能时,只需将 fixture 函数的名称作为参数传递给测试函数即可。
例如,如果您需要在多个测试函数中使用相同的测试数据或设置相同的测试环境,您可以通过 @pytest.fixture
来定义一个 fixture,并在需要的测试函数中引用它。
在pytest中,夹具可以放置在测试文件中,也可以放置在固定的夹具文件中。推荐将夹具定义在一个或多个专门的夹具文件中,以便在整个测试套件中共享和重复使用。
夹具文件必须符合 pytest 命名规则:
- 夹具文件必须以
conftest.py
命名。 - 夹具文件必须位于测试根目录或测试相关子目录下。
示例目录结构:
tests/
conftest.py
test_module_1.py
test_module_2.py
在上面的示例中,conftest.py
是用于存放夹具的文件,test_module_1.py
和 test_module_2.py
则是存放测试函数的文件。由于 conftest.py
位于测试根目录下,因此该文件中定义的夹具可以被整个测试套件使用。
如果在子目录中有自己的 conftest.py
文件,那么该目录下的测试文件将会优先使用该文件中定义的夹具。如果所需的夹具在当前目录中未找到,则 pytest 将会自动查找父级目录中的 conftest.py
文件,并尝试从中获取夹具定义。
需要注意的是,pytest 只会在测试运行前加载所有的 conftest.py
文件,并为其中定义的所有夹具建立作用域。因此,如果你需要使用某个夹具,请确保其定义在测试函数所在的作用域内,或者定义在能够覆盖该作用域的父级作用域中。
下面是一个示例代码,演示了如何使用 @pytest.fixture
:
# conftest.py 或者测试模块中
import pytest
@pytest.fixture
def some_data():
return [1, 2, 3]
---
# 测试函数中
def test_using_fixture(some_data):
assert len(some_data) == 3
在上面的示例中,我们定义了一个名为 some_data
的 fixture,它返回了一个包含三个元素的列表。然后,在 test_using_fixture
测试函数中,我们通过将 some_data
作为参数传递给测试函数,从而使用了这个 fixture 提供的数据。
另一个例子:
@pytest.fixture
def delete_test():
# 执行一些准备工作
print("Preparing for the test...")
yield # 这里使用 yield 分隔准备工作和清理工作
# 执行一些清理工作
print("Cleaning up after the test...")
---
# 测试文件
@pytest.mark.usefixtures("delete_test")
@pytest.mark.parametrize('yaml_conf', read_testcase('test_createUser'))
def test_crud(self, yaml_conf):
使用了 @pytest.mark.usefixtures(“delete_test”) 装饰器,因此只有它会执行 delete_test 夹具中的准备工作和清理工作
fixture的scope
@pytest.fixture(scope='class', autouse=True)
@pytest.fixture
中的 scope
参数用于指定 fixture 的作用域,即决定 fixture 在整个测试过程中的调用次数和生命周期。下面是常见的 scope
取值及其对应的场景:
-
function
:这是默认值,表示每个测试函数(test function)在执行前都会调用一次 fixture。适合那些只在特定测试函数中需要用到的 fixture。 -
class
:表示在每个测试类(test class)中调用一次 fixture。适合那些需要在整个测试类范围内共享状态或资源的情况。 -
module
:表示在每个测试模块(test module)中调用一次 fixture。适合那些需要在整个测试模块范围内共享状态或资源的情况。 -
session
:表示在整个测试会话(test session)中只调用一次 fixture。适合那些需要在整个测试过程中共享状态或资源的情况。
根据不同的测试需求,您可以选择合适的 scope
来定义 fixture,以确保它在恰当的时机被调用,并且能够满足测试过程中的需求。
需要注意的是,使用较大作用域的 fixture 会导致更多的开销和可能的副作用,因此在使用时需要慎重考虑。
4、@pytest.mark.parametrize
@pytest.mark.parametrize
是 pytest 中用于参数化测试的装饰器之一。通过使用@pytest.mark.parametrize
装饰器,您可以在一个测试函数上定义多组输入参数,并且让pytest运行每一组参数的测试。
具体来说,您可以将 @pytest.mark.parametrize
应用在一个测试函数上,并指定参数名称和参数值的组合,例如:
import pytest
@pytest.mark.parametrize("input, expected", [
(1, 2),
(2, 3),
(3, 4)
])
def test_increment(input, expected):
assert input + 1 == expected
在这个例子中,test_increment
函数接受 input
和 expected
两个参数。@pytest.mark.parametrize
装饰器中指定了三组输入参数,分别是 (1, 2)、(2, 3) 和 (3, 4),这意味着 test_increment
函数会被调用三次,每次传入不同的参数进行测试。
对于每个参数组,test_increment() 函数将使用该参数组的第一个元素作为输入参数 input,并将其加 1 后与该参数组的第二个元素(即预期输出)进行比较。如果两者相等,则测试通过。
unittest
unittest的使用步骤
使用 Python 的 unittest 模块可以进行单元测试。下面是 unittest 的基本使用步骤:
-
导入 unittest 模块:
import unittest
-
创建测试类继承自 unittest.TestCase:
class MyTestCase(unittest.TestCase): # 测试方法...
-
在测试类中定义测试方法:
def test_something(self): # 执行测试的代码... # 使用断言来检查测试结果是否符合预期 self.assertEqual(1 + 1, 2)
-
可选:在测试方法中使用各种断言来验证测试结果:
assertEqual(a, b)
:断言 a 和 b 相等。assertTrue(x)
:断言 x 是 True。assertFalse(x)
:断言 x 是 False。assertRaises(exception, callable, *args, **kwargs)
:断言调用 callable 时会引发指定的异常。
-
可选:在测试方法中使用
setUp()
方法设置测试环境:def setUp(self): # 设置测试环境的代码...
-
可选:在测试方法中使用
tearDown()
方法清理测试环境:def tearDown(self): # 清理测试环境的代码...
例如:
class TestLogin(unittest.TestCase): def setUp(self) -> None: self.driver = webdriver.Chrome() self.driver.maximize_window() self.path = "file://" + os.path.dirname(__file__) + '\\test.html' self.driver.get(self.path) self.login = PageLogin(self.driver) def tearDown(self) -> None: self.driver.quit()
-
运行测试:
- 在脚本中运行测试:
if __name__ == '__main__': unittest.main()
- 在命令行中使用
python -m unittest
命令运行测试。
- 在脚本中运行测试:
unittest中的@parameterized.expand()
@parameterized.expand() 是 unittest 模块中的一个装饰器,它用于在单元测试中对一组参数进行测试。它可以让你更加方便地编写参数化测试用例,尤其是当有很多参数组合需要测试时。
使用 @parameterized.expand() 装饰器的基本步骤如下:
- 定义一个列表或元组,其中包含需要测试的参数组合。
- 在测试方法上添加 @parameterized.expand() 装饰器,并将参数列表作为其参数。
- 在测试方法内部使用传入的参数进行测试。
下面是一个示例,演示了如何使用 @parameterized.expand() 装饰器进行参数化测试:
import unittest
from parameterized import parameterized
def add(a, b):
return a + b
class TestAdd(unittest.TestCase):
@parameterized.expand([
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0)
])
def test_add(self, a, b, expected_result):
self.assertEqual(add(a, b), expected_result)
if __name__ == '__main__':
unittest.main()
假设我们有一个简单的函数 is_even,它用于判断一个整数是否为偶数,我们可以使用 @parameterized.expand() 来测试这个函数:
import unittest
from parameterized import parameterized
def is_even(number):
return number % 2 == 0
class TestIsEven(unittest.TestCase):
@parameterized.expand([
(2, True),
(3, False),
(0, True),
(-1, False)
])
def test_is_even(self, number, expected_result):
self.assertEqual(is_even(number), expected_result)
if __name__ == '__main__':
unittest.main()