Unittest 单元测试框架2 - 编写测试代码以及如何跳过测

2021-04-16  本文已影响0人  庄周幻梦

前文:
Unittest 单元测试框架1 - 基本使用和命令行选项


组织你的测试代码

单元测试的构建单位是test cases: 独立的,包含执行条件与正确性检查的方案。在unittest中,测试用例表示为unittest.TestCase的实例。通过编写TestCase的子类或使用FunctionTestCase编写你自己的测试用例。

一个TestCase实例的测试代码必须是完全自含的,因此它可以独立运行,或与其他任意组合任意数量的测试用例一起运行。

TestCase最简单的子类需要实现一个测试方法(例如一个明明以test开头的方法)以执行特定的测试代码:

import unittest

class DefaultWidgetSizeTestCase(unittest.TestCase):
    def test_default_widget_size(self):
        widget = Widget("The widget")
        self.assertEqual(widget.size(), (50, 50))

可以看到,为了进行测试,我们使用了基类TestCase提供的一个assert*()方法。若测试不通过,将会引发一个带有说明信息的异常,并且unittest会将这个测试用例标记为测试不通过。任何其他类型的异常将会被当做错误处理。

可能同时存在多个前置操作相同的测试,我们可以把测试的前置操作从测试代码中拆解出来,并实现测试前置方法setUp()。在运行测试时,测试框架会自动的为每个单独测试调用前置方法。

import unittest

class WidgetTestCase(unittest.TestCase):
    def setUp(self):
        self.widget = Widget('The widget')
    
    def test_default_widget_size(self):
        self.assertEqual(self.widget.size(), (50, 50), 'incorrect default size')
    
    def test_widget_resize(self):
        self.widget.resize(100, 150)
        self.assertEqual(self.widget.size(), (100, 150), 'wrong size after resize')
注解: 多个测试运行的顺序由内置字符串排序方法对测试名进行排序的结果决定.

在测试运行时,若setUp()方法引发异常,测试框架会认为测试发生了错误,因此测试方法不会被运行。

相似的,我们提供了一个tearDown()方法在测试方法运行后进行清理工作。

import unittest

class WidgetTestCase(unittest.TestCase):
    def setUp():
        self.widget = Widget('The widget')
    
    def tearDown(self):
        self.widget.dispose()

setUp()成功运行,无论测试方法是否成功,都会运行tearDown()

这样的一个测试代码运行的环境被称为test fixture。一个新的TestCase实例作为一个test fixture, 用于运行各个独立的测试方法。在运行每个测试时,setUp(), tearDown()__init__()会被调用一次。

建议你根据所测试的功能,将测试用TestCase实现集合起来。unittest为此提供了机制:test suite, 以unittest的类TestSuite为代表。大部分情况下,调用unittest.main()即可, 并且它会为你集合所有模块的测试用例并执行。

然而,如果你需要自定义你的测试套件的话,你可以参考以下方法组织你的测试:

def suite():
    suite = unittest.TestSuite()
    suite.addTest(WidgetTestCase('test_default_widget_size'))
    suite.addTest(WidgetTestCase('tset_widget_resize'))
    return suite

if __name__ == '__main__':
    runner = unitest.TextTestRunner()
    runner.run(suite())

您可以将测试用例和测试套件的定义与要测试的代码放在相同的模块中(例如 widget.py), 但是将测试代码放在单独的模块中有几个优点,例如test_widget.py:

复用已有的测试代码

一些用户希望直接使用unittest运行已有的测试代码,而不需要把已有的每个测试函数转化为一个TestCase的子类。

因此, unittest提供FunctionTestCase类。这个TestCase的子类可用于打包已有的测试函数,并支持设置前置与后置函数。

假定有一个测试函数:

def testSomething():
    something = makeSomething()
    assert something.name is not None
    # ...

可以创建等价的测试用例如下, 其中前置和后置方法是可选的。

testcase = unittest.FunctionTestCase(testSomething, setUp=markSomethingDB, tearDown=deleteSomethingDB)
注解: 即使可以使用FunctionTestCase能够被将现有的测试库转换为基于unitest的系统,也不建议使用这种方法。花时间建立适当的TestCase子类将使将来的测试重构变得更加容易。

在某些情况下,现有的测试可能是使用doctest模块编写的。如果是这样,doctest提供了一个DocTestSuite类,可以自动生成unittest. TestSuite 现有基于doctest的测试的实例。

跳过测试与预计的失败

3.1 新版本功能

Unittest支持跳过单个测试方法甚至跳过整个测试类。此外,他还支持将测试标记为预期失败, 即可以被破坏并将失败的测试,但不应再TestResult上被视为失败。

跳过测试只是使用skip()装饰器或其变量之一,调用TestCase.skipTest()setUp()或者测试方法,或者直接跑出 SkipTest.

跳过测试的基本用法如下:

import unittest

class MyTestCase(unittest.TestCase):
    
    @unittest.skip("demonstrating skipping")
    def test_nothing(self):
        self.fail("shouldn't happen")

    @unittest.skipIf(mylib.__version__ < (1, 3), "not supported in this library version")
    def test_format(self)L
        # Tests that work for only a certain version of the library
        pass

    @unittest.skipUnless(sys.platform.startwith("win"), "requires Windows")
    def test_windows_support(self):
        # windows specific testing code
        pass
    
    def test_maybe_skipped(self):
        if not external_resource_available():
            self.skipTest("external resource not available")
        # test code that appends on the external resource
        pass

在啰嗦模式下运行以上测试用例时,程序输出如下:

test_nothing (__main__.MyTestCase) ... skipped 'demonstrating skipping'
test_maybe_skipped (__main__.MyTestCase) ... skipped 'external resource not available'
test_windows_support (__main__.MyTestCase) ... skipped 'requires Windows'

----------------------------------------------------------------------
Ran 4 tests in 0.005s

OK (skipped=4)

跳过测试类的写法跟跳过测试方法的写法相似:

@unittest.skip("showing class skipping")
class MySkippedTestCase(unittest.TestCase):
    def test_not_run(self):
        pass

TestCase.setUp() 也可以跳过测试。可以用于所需资源不可用的情况下跳过接下来的测试。

使用expectedFailure() 装饰器表明这个测试预计失败。:

class ExpectedFailureTestCase(unittest.TestCase):
    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 0, "broken")

通过使一个decorator在测试中调用'skip()',当它想要跳过它时,就可以很容易地滚动您自己的跳过decorator。除非传递的对象具有特定属性,否则此devorator将跳过测试:

def skipUnlessHasattr(obj, attr):
    if hasattr(obj, attr):
        return lambda func:func
    return unittest.skip("{!r} doesn't have {!r}".format(obj, attr))

以下装饰器和异常实现了测试跳过和预期的失败:

被跳过的测试的setUp()tearDown() 不会被运行。被跳过的类和setUpClass()tearDownClass()不会被运行。被跳过的模组的setUpModule()tearDownModule()不会被运行。


原文来自于https://docs.python.org/3.9/library/unittest.html#
如有侵权,请联系删除

上一篇下一篇

猜你喜欢

热点阅读