unittest之TestCase类详解

TestCase

测试用例类,我们做单元测试时编写的测试用例就是继承TestCase类来实现具体的测试用例 \
例如:

import unittest
class UserCase(unittest.TestCase):

    def testAddUser(self):
        print("add a user")

    def testDelUser(self):
        print("delete a user")

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

可以这样理解:每一个继承TestCase类的子类里面实现的具体的方法(以test开头的方法)都是一条用例 \
既然我们写了用例,那用例又是如何被执行的呢:这就是下面要讲的TestCase类里面run方法,先贴一下代码:

    def run(self, result=None):
        orig_result = result
        #初始化result类,此类就是TestResult类或者其子类,用来记录用例的执行结果信息等
        if result is None:
            result = self.defaultTestResult()   
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

        self._resultForDoCleanups = result
        result.startTest(self)

        testMethod = getattr(self, self._testMethodName)    #获取当前TestCase类里面实现的某一个具体的方法(以test开头的方法)
        #判断TestCase类是否有被skip装饰,或类的testMethod方法有没有被skip装饰,如果有skip装饰,则直接跳过这个类或者这个方法不执行(包括后面tearDown也不会被执行),
        if (getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)):
            # If the class or method was skipped.
            try:
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                self._addSkip(result, skip_why)
            finally:
                result.stopTest(self)
            return
        #下面部分的代码逻辑是:执行setup部分,如果没有异常执行testMethod(即我们写的以test开头的TestCase类的方法),然后执行tearDown部分代码(不管testMethod是否通过)
        try:
            success = False
            try:
                self.setUp()
            except SkipTest as e:
                self._addSkip(result, str(e))
            except KeyboardInterrupt:
                raise
            except:
                result.addError(self, sys.exc_info())
            else:
                try:
                    testMethod()
                except KeyboardInterrupt:
                    raise
                except self.failureException:
                    result.addFailure(self, sys.exc_info())
                except _ExpectedFailure as e:
                    addExpectedFailure = getattr(result, 'addExpectedFailure', None)
                    if addExpectedFailure is not None:
                        addExpectedFailure(self, e.exc_info)
                    else:
                        warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
                                      RuntimeWarning)
                        result.addSuccess(self)
                except _UnexpectedSuccess:
                    addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
                    if addUnexpectedSuccess is not None:
                        addUnexpectedSuccess(self)
                    else:
                        warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
                                      RuntimeWarning)
                        result.addFailure(self, sys.exc_info())
                except SkipTest as e:
                    self._addSkip(result, str(e))
                except:
                    result.addError(self, sys.exc_info())
                else:
                    success = True

                try:
                    self.tearDown()
                except KeyboardInterrupt:
                    raise
                except:
                    result.addError(self, sys.exc_info())
                    success = False

            cleanUpSuccess = self.doCleanups()
            success = success and cleanUpSuccess
            if success:
                result.addSuccess(self)
        #此部分是用例执行完成之后的收尾工作
        finally:
            result.stopTest(self)
            if orig_result is None:
                stopTestRun = getattr(result, 'stopTestRun', None)
                if stopTestRun is not None:
                    stopTestRun()

注意:run方法的第一个参数是result
下面详细说明一下run方法里面得逻辑:

orig_result = result
if result is None:
            result = self.defaultTestResult()   
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

self._resultForDoCleanups = result
result.startTest(self)

这一部分代码是用来实例化一个TestResult类,如果run方法没有传入result参数,则用self.defaultTestResult方法返回一个TestResult类的实例,并且如果TestResult如果有startTestRun方法就执行startTestRun方法。后面两句中的第一句是将result赋值TestCase实例_resultForDoCleanups属性,这个会在后面再说,第二句是执行result的startTest方法(可以看一下TestResult的startTest方法)。

继续往下看代码:

testMethod = getattr(self, self._testMethodName)
if (getattr(self.__class__, "__unittest_skip__", False) or
    getattr(testMethod, "__unittest_skip__", False)):
    # If the class or method was skipped.
    try:
        skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                    or getattr(testMethod, '__unittest_skip_why__', ''))
        self._addSkip(result, skip_why)
    finally:
        result.stopTest(self)
    return

第一句代码:testMethod = getattr(self, self._testMethodName),这句主要获取TestCase类里面的方法,而self._testMethodName就是我们编写测试类里面的的方法(以test开头的方法)。比如上面例子中的testAddUser方法\
后面部分代码是判断当前我们写的TestCase类是否有unittest_skip,或者我们写的TestCase类的方法(以test开头)是否有unittest_skip方法,如果有则获取类的unittest_skip_why属性或者方法的unittest_skip_why属性。并在result中添加skip的原因。之后执行result的stopTest方法,并直接返回不继续执行下去(==包括setUp和teardown都不会执行==)。那么我们如何去实现这skip的逻辑呢,举例如下:

1、TestCase带有__unittest_skip__属性:
@unittest.skip('class skip')
class UnitTestCase(unittest.TestCase):
    def test01(self):
        print('test01')
    def test02(self):
        print('test02')
    def tearDown(self):
        print('teardown')
if __name__ == '__main__':
    unittest.main()

2、方法带有__unittest_skip__属性
@unittest.skip('class skip')
class UnitTestCase(unittest.TestCase):
    @unittest.skip('skip')
    def test01(self):
        print('test01')
    @unittest.skip('skip')
    def test02(self):
        print('test02')
    def tearDown(self):
        print('teardown')
if __name__ == '__main__':
    unittest.main()

unittest_skipunittest_skip_why这两个属性是如何给类或方法带上的呢,那让我们来看一下skip方法。先贴代码:

def skip(reason):
    """
    Unconditionally skip a test.
    """
    def decorator(test_item):
        if not isinstance(test_item, (type, types.ClassType)):
            @functools.wraps(test_item)
            def skip_wrapper(*args, **kwargs):
                raise SkipTest(reason)
            test_item = skip_wrapper

        test_item.__unittest_skip__ = True
        test_item.__unittest_skip_why__ = reason
        return test_item
    return decorator

从代码中可以看出skip方法会给test_item(也就是类名或者方法名)增加两个属性unittest_skipunittest_skip_why并分别赋值为True和reason。

我们继续上面的run方法剩下部分的代码分析:如果没有skip装饰类或方法,则执行下面的逻辑:

try:
    success = False
    try:
        self.setUp()
    except SkipTest as e:
        self._addSkip(result, str(e))
    except KeyboardInterrupt:
        raise
    except:
        result.addError(self, sys.exc_info())
    else:
        try:
            testMethod()
        except KeyboardInterrupt:
            raise
        except self.failureException:
            result.addFailure(self, sys.exc_info())
        except _ExpectedFailure as e:
            addExpectedFailure = getattr(result, 'addExpectedFailure', None)
            if addExpectedFailure is not None:
                addExpectedFailure(self, e.exc_info)
            else:
                warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
                              RuntimeWarning)
                result.addSuccess(self)
        except _UnexpectedSuccess:
            addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
            if addUnexpectedSuccess is not None:
                addUnexpectedSuccess(self)
            else:
                warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
                              RuntimeWarning)
                result.addFailure(self, sys.exc_info())
        except SkipTest as e:
            self._addSkip(result, str(e))
        except:
            result.addError(self, sys.exc_info())
        else:
            success = True

        try:
            self.tearDown()
        except KeyboardInterrupt:
            raise
        except:
            result.addError(self, sys.exc_info())
            success = False

    cleanUpSuccess = self.doCleanups()
    success = success and cleanUpSuccess
    if success:
        result.addSuccess(self)
finally:
    result.stopTest(self)
    if orig_result is None:
        stopTestRun = getattr(result, 'stopTestRun', None)
        if stopTestRun is not None:
            stopTestRun()

我们分解开来分析,第一部分

try:
    success = False
    self.setUp()
    except SkipTest as e:
        self._addSkip(result, str(e))
    except KeyboardInterrupt:
        raise
    except:
        result.addError(self, sys.exc_info())

在用例执行之前给是否成功的标识赋值为false,success=False \
接着执行setUp方法,如果是在setUp中skipTest异常抛出,则在result中添加异常信息。如何实现这个SkipTest异常呢,我们可以在setUp方法中增加一句代码:raise unittest.SkipTest(‘setup skip test’),如果是在setUp中遇到KeyboardInterrupt异常(Ctrl+c),则会直接抛出异常,如果是除了这两个异常之外的其他异常,则在result中添加error信息。

第二部分

else:
    try:
        testMethod()
    except KeyboardInterrupt:
        raise
    except self.failureException:
        result.addFailure(self, sys.exc_info())
    except _ExpectedFailure as e:
        addExpectedFailure = getattr(result, 'addExpectedFailure', None)
        if addExpectedFailure is not None:
            addExpectedFailure(self, e.exc_info)
        else:
            warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
                          RuntimeWarning)
            result.addSuccess(self)
    except _UnexpectedSuccess:
        addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
        if addUnexpectedSuccess is not None:
            addUnexpectedSuccess(self)
        else:
            warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
                          RuntimeWarning)
            result.addFailure(self, sys.exc_info())
    except SkipTest as e:
        self._addSkip(result, str(e))
    except:
        result.addError(self, sys.exc_info())
    else:
        success = True

    try:
        self.tearDown()
    except KeyboardInterrupt:
        raise
    except:
        result.addError(self, sys.exc_info())
        success = False

cleanUpSuccess = self.doCleanups()
success = success and cleanUpSuccess
if success:
    result.addSuccess(self)

如果是setup部分没有报错或异常,则执行我们的testMethod方法(即我们写的具体的用例方法,以test开头),接下来的代码都是处理异常,else部分是如果没有异常则把success标识置为true,表示用例执行通过,然后执行tearDown部分的代码,如果tearDown部分遇到异常了,success标识又置为false。

第三部分:

finally:
    result.stopTest(self)
    if orig_result is None:
        stopTestRun = getattr(result, 'stopTestRun', None)
        if stopTestRun is not None:
            stopTestRun()

执行result的stopTest和stopTestRun方法(如果有)\
至此,run方法分析完成了。

上面我们介绍了run方法,那又是什么时候会调用run方法呢,再看TestCase的类里面方法,发现有一个call魔术方法:

def __call__(self, *args, **kwds):
    return self.run(*args, **kwds)

会去调我们的run方法(如果不太明白call方法是如何使用的,请百度一下),这就让我们知道肯定有一个地方调用了类似这样一个方法(以上面的例子为例):TestCase(‘testAddUser’)(result),这就是我的下一篇分析unittest之TestSuite类说明要讲的内容了

我们根据上面的分析可以来举一个列子来试验一下:

import unittest
class UserCase(unittest.TestCase):

    def testAddUser(self):
        print("add a user")

    def testDelUser(self):
        print("delete a user")

if __name__ == '__main__':
    result = unittest.TextTestResult(sys.stdout,'test result',1) #初始化TextTestResult类实例
    testcase = UserCase('testAddUser')  #初始化UserCase类实例
    testcase(result) #跟testcase.run(result)的结果是一样的,我们只需要传入一个result对象即可

运行结果如下:

E:\PythonWorkSpace>python test.py
add a user
.

E:\PythonWorkSpace>

可以运行成功,说明跟我们的分析一直,我们可以直接通过TestCase的run方法运行我们的用例,或者用过它的call魔术方法也是可以的,即上面例子中的testcase(result)就是通过此魔术方法去调用的run方法。

  • 10
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
unittest.TestCase是一个定义测试用例的,它是unittest框架中的核心之一。通过继承unittest.TestCase,我们可以创建测试,并在其中编写具体的测试用例。该提供了一些常用的方法和断言方法,如setUp和tearDown方法用于在测试之前和之后的准备工作和清理工作,以及各种断言方法用于验证预期结果与实际结果是否一致。我们可以在测试用例中定义多个测试方法,每个测试方法对应一个具体的测试场景。在每个测试方法中,我们可以编写测试逻辑并使用断言方法进行结果验证。通过运行测试用例,可以执行其中的所有测试方法,并查看测试结果。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [unittest单元测试框架总结--转载](https://blog.csdn.net/weixin_39650679/article/details/88682395)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [unittestTestCase详解](https://blog.csdn.net/fengguangke/article/details/81673971)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值