unittest之TestSuite类详解

python 同时被 2 个专栏收录
5 篇文章 0 订阅
5 篇文章 2 订阅

TestSuite

测试套件类,如何理解测试套件这个概念呢,从它的类定义来看,可以理解为:多个独立的测试用例(test case)或者多个独立的测试套件(test suite,可以理解为子套件)可以构成一个测试套件,那么我们写好了一个用例之后,如果去构建一个测试套件呢。下面介绍几种构建测试套件的方法:
1. 通过unittest.TestSuite()类直接构建,或者通过TestSuite实例的addTests、addTest方法构建
2. 通过unittest.TestLoader类的discover、loadTestsFromTestCase、loadTestsFromModule、loadTestsFromName、loadTestsFromNames这五个方法去构建
3. 通过unittest.makeSuite()、unittest.findTestCases()这两个方法去构建

下面我们分别对以上几种方法分别举一个例子:我们先写好一个TestCase:

#使用unittest.TestSuite()类直接构建,或者通过TestSuite实例的addTests、addTest方法构建
import unittest
class UserCase(unittest.TestCase):

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

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

if __name__ == '__main__':
    suite = unittest.TestSuite(map(UserCase,['testAddUser','testDelUser']))
    suite2 = unittest.TestSuite()
    suite2.addTests(map(UserCase,['testAddUser','testDelUser']))
    suite3 = unittest.TestSuite()
    suite3.addTest(UserCase('testAddUser'))
    suite3.addTest(UserCase('testDelUser'))
#通过unittest.TestLoader类的discover、loadTestsFromTestCase、loadTestsFromModule、loadTestsFromName、loadTestsFromNames这五个方法去构建
import unittest
class UserCase(unittest.TestCase):

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

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

if __name__ == '__main__':
    module = __import__(__name__)
    suite = unittest.TestLoader().discover('.','unittest_user.py') #unittest_user.py
    suite2 = unittest.TestLoader().loadTestsFromTestCase(UserCase)
    suite3 = unittest.TestLoader().loadTestsFromModule(module)
    #loadTestsFromName、loadTestsFromNames暂时不举例了,参数类型较多,不便举例,可以自行阅读其代码
#通过unittest.makeSuite()、unittest.findTestCases()这两个方法去构建
import unittest
class UserCase(unittest.TestCase):

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

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

if __name__ == '__main__':
    module = __import__(__name__)
    suite = unittest.makeSuite(UserCase,prefix='test')
    suite2 = unittest.findTestCases(module,prefix='test')

上面介绍了创建TestSuite的几种方法,其实这几种方法最终都脱离不了通过TestSuite去创建测试集,不信? 那我们就拿TestLoader()的loadTestsFromTestCase来验证一下是不是这样子的。
我们先看loadTestsFromTestCase的方法实现:

class TestLoader(object):
    """
    This class is responsible for loading tests according to various criteria
    and returning them wrapped in a TestSuite
    """
    testMethodPrefix = 'test'
    sortTestMethodsUsing = cmp
    suiteClass = suite.TestSuite
    _top_level_dir = None

    def loadTestsFromTestCase(self, testCaseClass):
        """Return a suite of all test cases contained in testCaseClass"""
        if issubclass(testCaseClass, suite.TestSuite):
            raise TypeError("Test cases should not be derived from TestSuite." \
                                " Maybe you meant to derive from TestCase?")
        testCaseNames = self.getTestCaseNames(testCaseClass)
        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
            testCaseNames = ['runTest']
        loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
        return loaded_suite

我们注意到类里面的几个变量:

testMethodPrefix = ‘test’
sortTestMethodsUsing = cmp
suiteClass = suite.TestSuite

第一句是用来过滤我们的测试方法,即默认的以test开头的方法
第二句是用来对我们的测试方法名排序的方法cmp
第三句是对suiteClass赋值,即TestSuite类

loadTestsFromTestCase的参数是一个TestCaseClass,就是我们编写的测试类的类名,
重要的一行代码:

testCaseNames = self.getTestCaseNames(testCaseClass)

这是干嘛的呢,读者们可以阅读一下getTestCaseNames的方法的实现,可以看出它是在获取TestCaseClass中所有以“test”开头的方法名,并且以字符串排序的方式排好序后并返回一个列表。然后还有一行重要的代码:

loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))

这就是我刚刚上面说的“所有的构建TestSuite方法都脱离不了通过TestSuite去创建测试集”的根源,loaded_suite最终的的值就是TestSuite的实例

有人可能会问,我们最终运行的是一个个的测试用例,而不是一整个测试集,既然我们是创建的测试集,那它是如何去运行我们的测试用例的呢。别着急,答案马上揭晓。

测试集TestSuite类中跟TestCase一样也有一个run方法,我们先看代码:

class TestSuite(BaseTestSuite):

    def run(self, result, debug=False):
        topLevel = False
        if getattr(result, '_testRunEntered', False) is False:
            result._testRunEntered = topLevel = True

        for test in self:
            if result.shouldStop:
                break

            if _isnotsuite(test):
                self._tearDownPreviousClass(test, result)
                self._handleModuleFixture(test, result)
                self._handleClassSetUp(test, result)
                result._previousTestClass = test.__class__

                if (getattr(test.__class__, '_classSetupFailed', False) or
                    getattr(result, '_moduleSetUpFailed', False)):
                    continue

            if not debug:
                test(result)
            else:
                test.debug()

        if topLevel:
            self._tearDownPreviousClass(None, result)
            self._handleModuleTearDown(result)
            result._testRunEntered = False
        return result

注意TestSuite是继承BaseTestSuite类的,并且重写了BaseTestSuite类run方法。并且参数是result,这跟TestCase类的run方法的参数一样,后面的debug参数我们先不关注。
前面三行代码我们也先忽略,我们来看中间部分的代码:

for test in self

第一句就是循环,迭代器的用法,为什么说是迭代器呢,因为BaseTestSuite类实现了iter魔术方法。我们可以看一下:

def __iter__(self):
    return iter(self._tests)

这下知道为什么可以循环了吧,并且每一次迭代返回的是一个test,这个test是什么呢,就是我们创建测试套件时往里面添加的TestCase的实例或TestSuite的实例。

接着往下看:

if _isnotsuite(test):
                self._tearDownPreviousClass(test, result)
                self._handleModuleFixture(test, result)
                self._handleClassSetUp(test, result)
                result._previousTestClass = test.__class__

                if (getattr(test.__class__, '_classSetupFailed', False) or
                    getattr(result, '_moduleSetUpFailed', False)):
                    continue

这段代码是干嘛的呢,我总结了一下,是处理setUpClass和TearDownClass部分的逻辑(里面的方法的源码较多,暂时就不一一解释了),如果setUpClass失败,则continue跳出本次循环继续执行下一个用例,包括==后面测试用例的执行和TearDownClass的执行==
(所以如果我们的用例里面有重写了setUpClass,并且失败了,是不会继续往下执行的)
继续往下读代码,如果setUpClass没有失败,则继续执行下面的代码:

if not debug:
    test(result)
else:
    test.debug()

上面我们说过这个test是我们实例化的一个TestCase类或TestSuite类,假如这个test就是TestCase类的实例吧。然后test(result)这句是干嘛的呢,还记得TestCase类里面的run方法吗,没错,这句话就是调用的TestCase类里面的run方法去执行用例的。这下明白了TestSuite是怎样去执行我们的TestCase了吧。

简单总结一下就是,测试套件(TestSuite)的执行是通过它的run方法,而它的run方法又会去调用每一个用例(TestCase)的run方法。这样就实现了用例的执行

不知道大家有没有注意到,TestCase和TestSuite的run方法中有一个比较明显的区别:
在TestCase中的run方法没有去处理setUpClass和TearDownClass部分的逻辑代码,而在TestSuite的run方法中才有处理setUpClass和TearDownClass部分的逻辑代码。所以这个地方是有一些区别的。在本篇文章的最后的 [补充部分] 我举例说明一下他们执行后的不同。

还有一小部分代码,已经无关紧要了,也是处理我们写的测试用例类的TearDownClass部分的:

if topLevel:
    self._tearDownPreviousClass(None, result)
    self._handleModuleTearDown(result)
    result._testRunEntered = False
return result

好了,我们的测试套件TestSuite分析到这里就差不多结束了,写的有些烂,希望大家能有所收获吧。

下一篇的unittest之TextTestRunner类详解我会继续分析TextTestRunner类。期待中。。。

[补充部分]
TestCase和TestSuite的run方法的不同点:

#unittest_prac.py
import sys
import unittest
class UserCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print('setUpClass')

    def setUp(self):
        print("setup")

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

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

    def tearDown(self):
        print('teardown')    

    @classmethod
    def tearDownClass(cls):
        print('tearDownClass')

if __name__ == '__main__':
    #实例化一个TextTestResult类
    result = unittest.TextTestResult(sys.stdout,'test result',1)
    suite = unittest.TestSuite(map(UserCase,['testAddUser']))
    suite.run(result)

    testcase = UserCase('testAddUser')
    testcase.run(result)

C:\software\PythonWorkspace>python unittest_prac.py
setUpClass
setup
add a user
teardown
.tearDownClass
setup
add a user
teardown
.

可以很明显的看到,TestSuite的run方法执行setUpClass和tearDownClass,但TestCase的run方法没有执行setUpClass和tearDownClass

  • 3
    点赞
  • 1
    评论
  • 27
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值