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

前文:
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))

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

  • @unittest.skip(reason)
    跳过被此装饰器装饰的测试用例, reason为测试被跳过的原因。

  • @unittest.skipIf(condition, reason)
    当condition为真时,跳过被装饰的测试。

  • @unittest.skipUnless(condition, reason)
    跳过被装饰的测试,除非condition为真。

  • @unittest.expectedFailure
    将测试标记为预期错误。如果测试失败或出错将被视为成功。如果测试通过,则视为失败。

  • exception unitest.SkipTest(reason)
    引发此异常以跳过一个测试。 通常来说,你可以使用TestCase.skipTest()或其中一个跳过测试的装饰器实现跳过测试的功能,而不是直接引发此异常。

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


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

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

推荐阅读更多精彩内容