本文最后更新于:2024年11月22日 下午 04:42:52
在搭建一个pytest Demo之前,首先安装pytest,pytest对于Python版本的要求是3.8+
1 2 3 >> pip install -U pytest >> pytest --version pytest 7.4.4
创建第一个测试 创建一个包含一个函数和一个测试的test_sample.py
文件,内容如下:
1 2 3 4 5 6 7 def func (x ): return x + 1 def test_answer (): assert func(3 ) == 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 >> pytest =============================================== test session starts ================================================ platform win32 -- Python 3.12.1, pytest-7.4.4, pluggy-1.4.0 rootdir: C:\Users\miaoh\Desktop\pytest collected 1 item test_sample.py F [100%] ===================================================== FAILURES ===================================================== ___________________________________________________ test_answer ____________________________________________________ def test_answer(): > assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) test_sample.py:7: AssertionError ============================================= short test summary info ============================================== FAILED test_sample.py::test_answer - assert 4 == 5 ================================================ 1 failed in 0.06s =================================================
这里的[100%]
指的是运行的所有测试用例的进度。很明显,fun(3)返回的结果是4,这个assert失败了。
运行多个测试用例 简言之,pytest
会在运行该命令的当前目录和子目录中查找格式为test_*.py or *_test.py的文件并且运行,具体的查找规则参考standard test discovery rules
断言一个特定的异常 参考raises 我们可以断言某个异常的发生
1 2 3 4 5 6 7 8 9 10 11 import pytestdef f (): raise SystemExit(1 )def test_mytest (): with pytest.raises(SystemExit): f()
也可以使用由raises](How to write and report assertions in tests — pytest documentation )提供的上下文来断言预期的异常是抛出的ExceptionGroup
的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import pytestdef f (): raise ExceptionGroup( "Group message" , [ RuntimeError(), ], )def test_exception_in_group (): with pytest.raises(ExceptionGroup) as excinfo: f() assert excinfo.group_contains(RuntimeError) assert not excinfo.group_contains(TypeError)
这个case会失败,因为在目前的stable版本python中,这个Execinfo方法还什么都没有写,而assert
需要后边是个boolean值。在GitHub pytest的issue里也可以看到有相关的issue
1 2 def group_contains (self, RuntimeError ): pass
在一个类中定义多个测试用例 当你添加多个测试用例的时候,你可能希望将它们分组到一个类中。pytest使得创建包含多个测试的类变得很容易:
1 2 3 4 5 6 7 8 9 class TestClass : def test_one (self ): x = "this" assert "h" in x def test_two (self ): x = "hello" assert hasattr (x, "check" )
pytest会根据我们之前提到的python测试发现的规定来发现所有的测试,所以它会找到所有以test_为前缀的函数。不需要通过继承任何类来实现,但是确保你的类的前缀是Test
,否则这个类会被跳过。我们可以通过传递文件名来运行模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ pytest -q test_class.py .F [100%] ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ self = <test_class.TestClass object at 0xdeadbeef0001> def test_two(self): x = "hello"> assert hasattr(x, "check" ) E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:8: AssertionError ========================= short test summary info ========================== FAILED test_class.py::TestClass::test_two - AssertionError: assert False 1 failed, 1 passed in 0.12s
第一个测试通过了,第二个测试失败了。你可以很容易地看到断言中的中间值,帮助你理解失败的原因。
将测试分组到类中可能有以下好处:
方便组织测试用例
仅在该特定类中共享fixtures
在类级别应用标记,并隐式地应用到所有测试
当在类中分组测试时,需要注意的是每个测试用例都对分别对该类进行实例化,所以类的值不能在测试用例之间共享。让每个测试共享同一个类实例对测试隔离非常不利。这在下面有详细说明:
1 2 3 4 5 6 7 8 9 10 class TestClassDemoInstance : value = 0 def test_one (self ): self.value = 1 assert self.value == 1 def test_two (self ): assert self.value == 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ pytest -k TestClassDemoInstance -q .F [100%] ================================= FAILURES ================================= ______________________ TestClassDemoInstance.test_two ______________________ self = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002> def test_two(self):> assert self.value == 1 E assert 0 == 1 E + where 0 = <test_class_demo.TestClassDemoInstance object at 0xdeadbeef0002>.value test_class_demo.py:9: AssertionError ========================= short test summary info ========================== FAILED test_class_demo.py::TestClassDemoInstance::test_two - assert 0 == 1 1 failed, 1 passed in 0.12s
请注意,添加/更改类级别的属性是类属性,它们会在测试用例之间共享。有关类属性和实例属性以及命名空间的概念属于python的语法知识,此处不做解释。
为功能测试请求一个特殊的临时目录 pytest
提供了内置的fixtures/函数参数来请求任意资源,比如一个唯一的临时目录:
1 2 3 4 def test_needsfiles (tmp_path ): print (tmp_path) assert 0
在测试函数签名中列出 tmp_path
名称,pytest
将查找并调用一个fixture factory
来在执行测试函数调用之前创建资源。在测试运行之前,pytest
会创建一个每次测试调用都唯一的临时目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ pytest -q test_tmp_path.py F [100%] ================================= FAILURES ================================= _____________________________ test_needsfiles ______________________________ tmp_path = PosixPath('PYTEST_TMPDIR/test_needsfiles0') def test_needsfiles(tmp_path): print(tmp_path)> assert 0 E assert 0 test_tmp_path.py:3: AssertionError --------------------------- Captured stdout call --------------------------- PYTEST_TMPDIR/test_needsfiles0 ========================= short test summary info ========================== FAILED test_tmp_path.py::test_needsfiles - assert 0 1 failed in 0.12s
关于临时目录的相信文档可以在 Temporary directories and files 找到。
你可以使用以下命令查看哪些内置的pytest fixtures
存在,并且有一个简单的pydoc说明这个fixture是做什么的:
1 pytest --fixtures # 显示内置和自定义的fixtures
注意,除非添加了 -v
选项,否则这个命令会省略以 _
开头的fixtures。
Demo Repo