宝安龙华积分商城网站建设,佛山网站建设专家,网站关键词代码位置,网页制作基础教程字体居中颜色大家都知道#xff0c;目前最流行的Python单元测试框架有三种#xff0c;分别是unittest, nose和pytest。其中unittest是Python自带的测试框架#xff0c;但问题是比较老了#xff0c;赶不上时代发展了#xff08;哈哈哈#xff09;#xff1b;nose2定位是带插件的unitt…大家都知道目前最流行的Python单元测试框架有三种分别是unittest, nose和pytest。其中unittest是Python自带的测试框架但问题是比较老了赶不上时代发展了哈哈哈nose2定位是带插件的unittest实则对unittest的扩展。长远来看pytest属于潜力股。通过官网介绍的特点和使用经验可以将pytest优点总结如下1支持用简单的assert语句实现丰富的断言无需复杂的self.assert*函数2可以自动识别测试模块和测试函数3兼容unittest和nose测试集4支持参数化5支持测试用例的skip和xfail处理6可以很好的和jenkins集成7支持丰富的插件例如报告插件pytest-html、allure-pytest、失败重试插件pytest-rerunfailures、8活跃的社区遇到的问题可以高效解决。简化样板代码大多数功能测试遵循 Arrange-Act-Assert 模型设置测试前置条件调用函数来执行测试断言执行结果测试框架通常会挂接到测试的断言中以便它们可以在断言失败时提供信息。unittest例如提供了许多开箱即用的断言方法但是不友好的地方是unittest编写的用例即使是一小部分测试也需要相当数量的样板代码。下面写一个unittest测试用例并断言在项目中正常工作。# test_with_unittest.pyfrom unittest import TestCaseclass TryTesting(TestCase): def test_always_passes(self): self.assertTrue(True) def test_always_fails(self): self.assertTrue(False)然后你可以使用命令行运行这些测试(venv) $ python -m unittest discoverF.FAIL: test_always_fails (test_with_unittest.TryTesting)----------------------------------------------------------------------Traceback (most recent call last): File ...\effective-python-testing-with-pytest\test_with_unittest.py, line 10, in test_always_fails self.assertTrue(False)AssertionError: False is not true----------------------------------------------------------------------Ran 2 tests in 0.006sFAILED (failures1)一个测试通过一个测试失败。OK下面我们总结一下一个完整的测试需要写多少代码导入类unittest引入TestCase创建TryTesting一个子类TestCaseTryTesting为每个测试写一个方法使用self.assert*方法进行断言这是要编写的大量代码的所以在开发测试用例时会一遍又一遍地编写相同的样板代码。而pytest则不同它允许你直接使用普通函数和python assert关键字来简化样板代码达到相同的目的# test_with_pytest.pydef test_always_passes(): assert Truedef test_always_fails(): assert False就是如此简单。你需要做的就是写一个带有test_前缀的函数使用assert关键字断言期望是True/False然后执行测试即可。Pytest不仅消除了很多样板代码而且还提供了更加详细和易于阅读的输出。更好的测试输出在项目根目录文件夹下使用pytest命令运行所有测试用例(venv) $ pytest test session starts platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0rootdir: ...\effective-python-testing-with-pytestcollected 4 itemstest_with_pytest.py .F [ 50%]test_with_unittest.py F. [100%] FAILURES ______________________________ test_always_fails ______________________________ def test_always_fails(): assert FalseE assert Falsetest_with_pytest.py:7: AssertionError________________________ TryTesting.test_always_fails _________________________self test_with_unittest.TryTesting testMethodtest_always_fails def test_always_fails(self): self.assertTrue(False)E AssertionError: False is not truetest_with_unittest.py:10: AssertionError short test summary info FAILED test_with_pytest.py::test_always_fails - assert FalseFAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:... 2 failed, 2 passed in 0.20s 不同于unittestpytest的测试结果展示的更详细系统状态包括 Python的版本pytest以及你安装的任何插件测试目录runner发现的测试数量这些内容显示在输出的第一部分 test session starts platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0rootdir: ...\effective-python-testing-with-pytestcollected 4 items输出使用以下的语法指示每个测试的状态点 (.)表示测试通过。AnF表示测试失败。AnE表示测试引发了意外异常。特殊字符显示在用例名称后右侧显示测试套件的整体进度test_with_pytest.py .F [ 50%]test_with_unittest.py F. [100%]对于失败的测试报告会详细说明失败情况。 FAILURES ______________________________ test_always_fails ______________________________ def test_always_fails(): assert FalseE assert Falsetest_with_pytest.py:7: AssertionError________________________ TryTesting.test_always_fails _________________________self test_with_unittest.TryTesting testMethodtest_always_fails def test_always_fails(self): self.assertTrue(False)E AssertionError: False is not truetest_with_unittest.py:10: AssertionError下面这个额外的输出在调试时非常有用。最后报告给出了测试用例执行的整体状态报告 short test summary info FAILED test_with_pytest.py::test_always_fails - assert FalseFAILED test_with_unittest.py::TryTesting::test_always_fails - AssertionError:... 2 failed, 2 passed in 0.20s 与 unittest 相比pytest输出的信息量和可读性要高得多。强大的assertassert关键字很强大支持丰富多样的断言形式。以下是一些断言示例你可以了解支持的断言类型# test_assert_examples.pydef test_uppercase(): assert loud noises.upper() LOUD NOISESdef test_reversed(): assert list(reversed([1, 2, 3, 4])) [4, 3, 2, 1]def test_some_primes(): assert 37 in { num for num in range(2, 50) if not any(num % div 0 for div in range(2, num)) }更容易管理前置参数和依赖众所周知测试用例通常需要依赖准备数据或者其他服务作为前置条件。使用unittest你可以将这些依赖项提取到方法中.setUp().tearDown()以便类中的每个测试都可以使用它们。使用这些特殊方法很好但是随着你的测试类变得越来越大你可能会不经意地使测试的依赖完全隐式。换句话说通过孤立地查看众多测试中的一个你可能不会立即看出它依赖于其他东西。随着时间的推移隐式依赖关系会导致代码变得复杂你必须展开这些代码才能理解测试用例。事实上测试应该有助于使你的代码更易于理解。如果测试本身很难理解那么就有问题了pytest采取不同的方法。由于fixture的可用性它会引导你进行显式的依赖声明这些声明可以重用。fixture 是可以为测试用例创建数据、测试mock或初始化系统状态的函数。任何想要使用fixture的测试都必须显式地使用这个fixture函数作为测试函数的参数所以依赖关系总是在前面声明# fixture_demo.pyimport pytestpytest.fixturedef example_fixture(): return 1def test_with_fixture(example_fixture): assert example_fixture 1查看测试函数你可以立即看出它依赖于一个fixture而无需检查整个文件的fixture定义。易于过滤测试随着测试套件的增多你可能会有只想对某个功能运行一些测试并保存整个套件以备后用的需求。pytest提供了一些方法来实现这一点基于名称的过滤你可以限制pytest只运行那些完全限定名称与特定表达式匹配的测试。你可以使用-k参数执行此操作。目录范围默认情况下pytest将仅运行当前目录中/下的测试用例。测试分类pytest可以包含或排除你定义的特定类别的测试可以使用-m参数执行此操作。测试分类是一个非常强大的工具。pytest使你能够为你喜欢的任何测试创建标签或自定义标签。一个测试可能有多个标签你可以使用它们来精细化控制要运行的测试。丰富的插件pytest生态是开源的pytest用户开发了一个丰富的有用插件生态系统。fixturePytest fixture是一种为测试提供数据、测试mock的方法。每个依赖于fixture的测试都必须显式地接受fixture作为参数。何时创建fixture假设你正在编写一个函数format_data_for_display()来处理 API 接口返回的数据。输入数据是一个人员列表每个人都有给定的姓名、姓氏和职位。该函数应输出一个字符串列表其中包括每个人的全名、冒号和title# format_data.pydef format_data_for_display(people): ... # Implement this!践行TDD模式你需要为其编写测试用例。# test_format_data.pydef test_format_data_for_display(): people [ { given_name: Alfonsa, family_name: Ruiz, title: Senior Software Engineer, }, { given_name: Sayid, family_name: Khan, title: Project Manager, }, ] assert format_data_for_display(people) [ Alfonsa Ruiz: Senior Software Engineer, Sayid Khan: Project Manager, ]在开发测试用例时你可能需要开发另一个函数来将数据转换为逗号分隔值以便在Excel中使用# format_data.pydef format_data_for_display(people): ... # Implement this!def format_data_for_excel(people): ... # Implement this!你的TODO清单变长了TDD 的优势之一是它可以帮助你规划未来的工作。format_data_for_excel()函数的测试看起来与format_data_for_display()函数非常相似# test_format_data.pydef test_format_data_for_display(): # ...def test_format_data_for_excel(): people [ { given_name: Alfonsa, family_name: Ruiz, title: Senior Software Engineer, }, { given_name: Sayid, family_name: Khan, title: Project Manager, }, ] assert format_data_for_excel(people) given,family,titleAlfonsa,Ruiz,Senior Software EngineerSayid,Khan,Project Manager值得注意的是这两个测试都必须重复people变量的定义而这需要相当多的代码。如果你正在编写多个测试这些测试都使用相同的底层测试数据那么使用fixture你可以将重复的数据拉入一个装饰到函数中使用pytest.fixture表示该函数是一个pytest fixture:# test_format_data.pyimport pytestpytest.fixturedef example_people_data(): return [ { given_name: Alfonsa, family_name: Ruiz, title: Senior Software Engineer, }, { given_name: Sayid, family_name: Khan, title: Project Manager, }, ]# ...你可以通过将函数引用作为参数添加到测试中来使用fixture这样可以使用 fixture函数的返回值作为 fixture 函数的名称# test_format_data.py# ...def test_format_data_for_display(example_people_data): assert format_data_for_display(example_people_data) [ Alfonsa Ruiz: Senior Software Engineer, Sayid Khan: Project Manager, ]def test_format_data_for_excel(example_people_data): assert format_data_for_excel(example_people_data) given,family,titleAlfonsa,Ruiz,Senior Software EngineerSayid,Khan,每个测试的代码量明显更短但仍然有一条清晰的路径返回它所依赖的数据。什么时候避免使用fixturefixture非常适合提取你在多个测试中使用的数据或对象。但是对于需要数据有变化的测试fixture并不总是那么好。在你的测试套件中乱用fixture可能会导致情况更糟。与大多数抽象一样需要一些实践和思考才能找到合适的fixture使用场景。尽管如此fixture 很可能是你的测试套件不可或缺的一部分。随着项目范围的扩大测试规模的挑战开始出现。任何类型的工具面临的挑战之一是它如何应对大规模使用pytest具有一系列有用的功能可以帮助你管理用例增长带来的复杂性。如何规模化使用fixture当你从测试中提取更多的fixture时你可能会发现一些fixture可以从进一步的抽象中受益。在 中pytestfixture是模块化的。模块化意味着 fixture 可以导入可以导入其他模块它们可以依赖和导入其他 fixture。所有这些都允许你为你的用例编写合适的fixture抽象。例如你可能会发现两个单独文件或模块中的fixture共享一个共同的依赖项。在这种情况下你可以将fixture从测试模块移动到更通用的fixture相关模块中然后就可以将它们导入任何需要它们的测试模块中。如果你想让一个fixture在你的整个项目中可用而不必导入它可以使用特殊配置模块conftest.py文件。pytest在每个目录中查找一个conftest.py模块。如果你将通用fixture添加到conftest.py模块中那么你将能够在整个模块的父目录和任何子目录中使用该fixture而无需导入它。conftest.py是放置高频率使用fixture的好地方。conftest.py另一个有用的地方是它可以保护对资源的访问。想象一下你已经为处理API 调用的代码编写了一个测试套件你希望确保测试套件不会进行任何真正的网络调用即使有人不小心编写了这样做的测试。pytest提供一个monkeypatch fixture来替换值和行为你可以使用它来产生很好的效果# conftest.pyimport pytestimport requestspytest.fixture(autouseTrue)def disable_network_calls(monkeypatch): def stunted_get(): raise RuntimeError(Network access not allowed during testing!) monkeypatch.setattr(requests, get, lambda *args, **kwargs: stunted_get())通过放置disable_network_calls()和conftest.py添加autouseTrue的选项你可以确保在整个套件的每个测试中都将禁用网络调用。任何执行代码调用的测试requests.get()都会引发一个RuntimeError指示将发生意外网络调用的错误。marks分类测试在任何大型测试套件中当你尝试快速迭代新功能时最好避免运行所有测试做好精细化测试。除了pytest在当前工作目录中运行所有测试的过滤功能之外你还可以利用mark。pytest使你能够为测试定义类别并提供在运行套件时包含或排除类别的选项。你可以使用任意数量的类别标记测试。标记测试对于按子系统或依赖项对测试进行分类很有用。例如如果你的某些测试需要访问数据库那么你可以pytest.mark.database_access为它们创建一个标记。当需要运行测试时你仍然可以使用命令默认运行它们pytest。如果你只想运行那些需要访问数据库的测试那么你可以使用pytest -m database_access. 要运行除需要访问数据库的测试之外的所有测试你可以使用pytest -m not database_access. 你甚至可以使用autousefixture来限制对那些标有 的测试的数据库访问database_access。pytest-django插件提供了一个django_db标记。没有此标记的任何尝试访问数据库的测试都将失败。尝试访问数据库的第一个测试将触发 Django 测试数据库的创建。pytest提供开箱即用的一些标记skip无条件跳过测试。skipif如果传递给它的表达式的计算结果为True则跳过测试。xfail表示测试会失败因此如果测试确实失败整个套件仍会导致通过状态。parametrize使用不同的值作为参数创建测试的多个变体。Parametrization组合测试上文提到了如何使用fixture提取公共依赖项来减少代码重复。当你进行多个输入和预期输出略有不同的测试时fixture就没有那么有用了。在这些情况下你可以使用参数化为单个测试定义指定参数。假设你编写了一个函数来判断一个字符串是否为回文。一组初始测试代码如下所示def test_is_palindrome_empty_string(): assert is_palindrome()def test_is_palindrome_single_character(): assert is_palindrome(a)def test_is_palindrome_mixed_casing(): assert is_palindrome(Bob)def test_is_palindrome_with_spaces(): assert is_palindrome(Never odd or even)def test_is_palindrome_with_punctuation(): assert is_palindrome(Do geese see God?)def test_is_palindrome_not_palindrome(): assert not is_palindrome(abc)def test_is_palindrome_not_quite(): assert not is_palindrome(abab)除了最后两个之外所有这些测试都具有相同的样板def test_is_palindrome_in some situation(): assert is_palindrome(some string)乍看很像样板文件。我们可以使用pytest.mark.parametrize()减少测试代码pytest.mark.parametrize(palindrome, [ , a, Bob, Never odd or even, Do geese see God?,])def test_is_palindrome(palindrome): assert is_palindrome(palindrome)pytest.mark.parametrize(non_palindrome, [ abc, abab,])def test_is_palindrome_not_palindrome(non_palindrome): assert not is_palindrome(non_palindrome)第一个参数parametrize()是以逗号分隔的参数名称字符串。第二个参数是表示参数值的元组或单个值的列表。你可以将参数化进一步处理将所有测试合并为一个pytest.mark.parametrize(maybe_palindrome, expected_result, [ (, True), (a, True), (Bob, True), (Never odd or even, True), (Do geese see God?, True), (abc, False), (abab, False),])def test_is_palindrome(maybe_palindrome, expected_result): assert is_palindrome(maybe_palindrome) expected_result尽管这缩短了你的代码但重要的是要注意在这种情况下你实际上丢失了原始函数的一些更具描述性的特性。确保你没有将测试套件参数化到难以理解的地步之前可以使用参数化将测试数据与测试行为分开以便清楚测试用例在测试什么同时也使不同的测试用例更易于阅读和维护。durations命令行使用--durationspytest在测试结果中会包含持续时间报告。--durations需要一个整数值n并将报告最慢的n测试(venv) $ pytest --durations5... slowest 5 durations 3.03s call test_code.py::test_request_read_timeout1.07s call test_code.py::test_request_connection_timeout0.57s call test_code.py::test_database_read(2 durations 0.005s hidden. Use -vv to show these durations.) short test summary info ...显示在持续时间报告中的每个测试占用的总测试时间高于平均水平。注某些测试也可能有不可见的设置开销。例如django_db将触发 Django 测试数据库的创建。durations报告反映了在触发数据库创建的测试中设置数据库所花费的时间。