Python测试框架-pytest与unittest的区别

前言

       在Python中进行测试时,两个最流行的测试框架是unittestpytest。虽然它们的目标相同,但它们之间存在许多不同之处。
       本文将详细比较它们在用例编写规则、前置和后置方法、参数化、断言功能、用例执行和报告生成等方面的差异,并适当补充pytest相较于unittest的其他优点。

相同点

首先,让我们看一下它们的共同点:
● 都是用于Python的测试框架
● 都使用断言(assertions)来验证代码的正确性
● 都支持自动化测试

尽管它们有着相同的目标和基本特征,但在实际应用过程中表现出了不同的特点。

用例编写规则的不同

unittest

       在unittest中,测试类必须继承unittest.TestCase。测试方法必须以test_开头,并且不能带有参数。以下是一个使用unittest编写测试用例的示例:

import unittest

# 继承 TestCase 类
class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])

        # 执行测试
if __name__ == '__main__':
    unittest.main()

pytest

       在pytest中,测试函数可以任意命名,无需继承特定的基类或遵循特定的命名规范。以下是一个使用pytest编写测试用例的示例:

# 测试函数可以随意命名
def test_upper():
    assert 'foo'.upper() == 'FOO'

def test_isupper():
    assert 'FOO'.isupper()
    assert not 'Foo'.isupper()

def test_split():
    s = 'hello world'
    assert s.split() == ['hello', 'world']

这里不同之处在于pytest对测试用例命名和所继承的基类没有要求,而unittest则需要遵循特定的命名规范和继承特定的基类。

前置和后置方法的不同

unittest

       unittest使用setUp和tearDown方法来设置和清理测试夹具(test fixtures)。setUp方法在每个测试方法之前执行,用于准备测试环境; tearDown方法在每个测试方法之后执行,用于清理测试环境。以下是一个使用setUp和tearDown方法的示例:

import unittest

class TestStringMethods(unittest.TestCase):
    def setUp(self):
        self.text = 'hello world'

    def test_upper(self):
        self.assertEqual(self.text.upper(), 'HELLO WORLD')

    def tearDown(self):
        self.text = None

if __name__ == '__main__':
    unittest.main()

pytest

       在pytest中,使用fixture系统管理测试夹具。夹具(fixture)是函数、类或模块级别的对象,可以提供测试数据、初始化代码等。如果需要在多个测试用例中重复使用相同的夹具,则fixture尤其有用。以下是一个使用fixture的示例:

import pytest

@pytest.fixture
def setup_text():
    return 'hello world'

def test_upper(setup_text):
    assert setup_text.upper() == 'HELLO WORLD'

这里不同之处在于pytest使用fixture来管理测试夹具,而unittest则使用setUp和tearDown方法。

参数化的不同

unittest

       在unittest中,参数化通常需要手动实现。例如,使用for循环迭代测试数据,并为每个数据集执行一个测试方法:

import unittest

class TestMultiplication(unittest.TestCase):
    def test_numbers_3_4(self):
        self.assertEqual(3 * 4, 12)

    def test_numbers_2_4(self):
        self.assertEqual(2 * 4, 8)

        def test_numbers_6_9(self):
            self.assertEqual(6 * 9, 54)

pytest

       pytest内置支持参数化,使得为一组输入执行相同的测试变得非常容易。以下是一个使用@pytest.mark.parametrize装饰器实现参数化测试的示例:

import pytest

# 参数化测试
@pytest.mark.parametrize("a, b, expected", [
    (3, 4, 12),
    (2, 4, 8),
    (6, 9, 54),
])
def test_multiplication(a, b, expected):
    assert a * b == expected

除此之外,pytest还提供了更多灵活的参数化方式,例如从CSV、YAML或JSON文件中加载测试数据等。

断言功能的不同

unittest

       unittest只能使用assertEqual、assertTrue和assertFalse等基本断言方法进行断言。如果需要其他特殊类型的断言,则需要手动编写代码来实现。例如,如果需要比较两个列表是否相等,则需要使用assertListEqual方法。

import unittest

class TestListMethods(unittest.TestCase):
    def test_list_equal(self):
        list1 = [1, 2, 3]
        list2 = [1, 2, 3]
        self.assertListEqual(list1, list2)

if __name__ == '__main__':
    unittest.main()

pytest

       pytest具有更丰富的内置断言方法,例如assertAlmostEqual、assertDictEqual和assertRegex等。如果需要自定义断言,则可以使用pytest.assert语法。

import pytest

def test_list_equal():
    list1 = [1, 2, 3]
    list2 = [1, 2, 3]
    assert list1 == list2

此外,pytest还支持自定义的assertion helper函数和插件来扩展断言功能。

用例执行的不同

unittest

       在unittest中,测试套件由TestLoader加载,并由TextTestRunner运行。以下是一个手动创建并运行测试套件的示例:

import unittest

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(TestStringMethods('test_upper'))

    runner = unittest.TextTestRunner()
    runner.run(suite)

pytest

       在pytest中,使用pytest命令行工具运行测试。pytest会自动发现并执行所有符合命名规则(test_*.py)的测试文件。以下是一个在终端中运行pytest测试的示例:

$ pytest

pytest还支持多种插件扩展测试功能,例如pytest-xdist可以实现分布式测试。以下是一个使用pytest-xdist插件进行并发执行的示例:

$ pytest -n 4

这里不同之处在于unittest需要手动创建并运行测试套件,而pytest则由pytest命令行工具自动发现和执行测试。

报告生成的不同

unittest

       unittest默认情况下生成文本报告并将其输出到控制台。可以编写一个单独的测试运行器来生成HTML报告等其他格式。以下是一个使用HtmlTestRunner模块创建HTML测试报告的示例:

import unittest
import HtmlTestRunner

class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

if __name__ == '__main__':
    unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir'))

pytest

       在pytest中,可以使用多个插件来生成各种格式的测试报告,例如pytest-html可以生成漂亮的HTML测试报告:

$ pytest --html=report.html

此外,还有更多插件支持生成JUnit XML、JSON和Allure等报告格式。

pytest其他优点的补充

       除了上述功能之外,pytest还提供了许多其他有用的特性,使得它比unittest更加灵活、易用、高效和可扩展。以下是一些pytest的其他优点:

参数化

       pytest的参数化测试功能非常强大,可以轻松地对一组输入执行相同的测试,并在测试失败时提供更多的上下文信息。参数化测试还可以与fixture系统一起使用,从而实现更高级的夹具设置。例如,以下是一个使用参数化和fixture的示例:

import pytest

@pytest.fixture(params=[1, 2, 3])
def input_value(request):
    return request.param

def test_square(input_value):
    assert input_value ** 2 == 4

这里,fixture函数input_value返回一个参数(request.param) ,pytest将会用列表中的每个元素调用一次fixture。

fixture管理测试夹具

       除了基本夹具之外,pytest的fixture系统还支持诸如scope、autouse和yield-fixture等高级特性。这使得夹具的管理变得更加容易和灵活。例如,以下是一个使用autouse fixture自动启用测试日志记录的示例:

import pytest

@pytest.fixture(autouse=True)
def log_test_info(request):
    """Auto logs information about the tests being run"""
    print(f"Running test {request.node.name}")

这里,autouse=True表示此fixture将自动应用于所有测试用例,无需手动运行。

插件系统,使得扩展功能变得非常容易

       pytest的插件系统非常丰富,支持大量第三方插件和自定义插件。这些插件可以轻松地扩展pytest的功能,例如增强断言、生成更高级别的报告、支持分布式测试等。以下是一个使用pytest-html插件生成HTML测试报告的示例:

$ pytest --html=report.html

支持并发执行

       与unittest不同,pytest具有内置并发执行功能。pytest-xdist是一个非常流行的第三方插件,可用于实现分布式测试和多进程测试。以下是一个使用xdist插件并发运行测试的示例:

$ pytest -n 4

这里,-n参数告诉pytest使用4个进程来运行测试。

断言详细信息

       当测试失败时,pytest会在控制台上提供更详细的错误信息,以便开发人员更容易地调试代码。例如,以下是一个使用pytest的详细错误信息的示例:

E       assert 'foo'.upper() == 'FOO'
E         AssertionError: assert 'FOO' == 'FOO '
E           - FOO
E           + FOO

       pytest还提供了其他工具来帮助开发人员更轻松地查看和分析测试结果,例如pytest-watch用于自动重新运行测试,pytest-cov用于测量测试覆盖率等等。

结论

       本文对比了Python测试框架中的unittest和pytest之间的差异,包括用例编写规则、前置和后置方法、参数化、断言功能、用例执行和报告生成等方面。尽管它们有着相同的目标和基本特征,但在实际应用过程中表现出了不同的特点。
       pytest比unittest更加灵活、易用、高效和可扩展,因此建议优先考虑使用pytest进行Python测试。
       除了本文中提到的功能之外,pytest还具有其他许多有用的特性,例如参数化、fixture管理、插件系统、并发执行、详细断言信息等,可以进一步提高测试效率和质量。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容