一种数据与逻辑分离的Python单元测试工具

几个概念

TestCase

TestCase是一个完整的测试单元,最小的测试执行实体,就是我们常说的测试用例。

TestSuite

以某种特性将测试用例组合到一起的测试用例集合被称作TestSuite,中文可以叫做测试套件。TestSuite可以包含TestCase,也可以嵌套TestSuite。

TestFixtrue

TestFixtrue是测试主程序运行时所需要的一切东西,可能是数据,可能是系统环境,也有可能是某个具体实例化类,中文可以称作测试固件。TestFixtrue包括构建(setUp)和销毁(tearDown)两个部分。例如,某个测试用例需要访问数据库,那么可以在setUp中建立数据库连接并初始化相关数据,在tearDown中释放数据库连接并还原相关数据。此外,TestFixtrue既可以存在于测试用例层面,也可以存在于测试套件层面。

单元测试框架

结合以上概念,一个测试用例的执行过程包括:

  • 测试用例固件构建
  • 执行测试用例主程序
  • 测试用例固件销毁

一个测试套件的执行过程包括:

  • 测试套件固件构建
  • 测试用例1固件构建
  • 执行测试用例1主程序
  • 测试用例1固件销毁
  • 测试用例2固件构建
  • ……
  • 测试用例n固件销毁
  • 测试套件固件销毁

测试套件执行过程

常用的单元测试工具

Python下常用的单元测试工具有unittest,PyUnit,nose。这三种工具的实现原理和使用方式大致相同。下面以unittest为例,来观察这些工具的特点。

import unittest
class CaculateTestCase(unittest.TestCase):
    def setUp(self):
        self.a = 1
        self.b = 2
        print 'set up the fixture'
    def tearDown(self):
        self.a = None
        self.b = None
        print 'tear down the fixture'
    def testAdd(self):
        assert (self.a+self.b) == 3, 'add function error'
    def testMinus(self):
        assert (self.a-self.b) == -1, 'minus function error'
if __name__ == '__main__':
    unittest.main()

在上面的代码中,CaculateTestCase是有关运算的TestCase,它继承自unittest.TestCase类。CaculateTestCase重写了setUp和tearDown方法,分别对应fixture的构建和销毁代码。
在加载测试任务时,unittest会根据目标文件寻找unittest.TestCase的子类,并寻找以“test”开头的方法,这些方法分别对应一个测试用例主程序。
因此,CaculateTestCase的具体执行流程是:

  • 执行CaculateTestCase中的setUp方法,进行固件构建
  • 依次执行CaculateTestCase中以“test”开头的方法,即testAdd和testMinus方法
  • 执行CaculateTestCase中的tearDown方法,进行固件销毁

分离数据与逻辑

在CaculateTestCase实例中,a和b变量是写死在程序中,脚本显得相当死板,没有什么可重用性。若让数据在以单一的文件存在,CaculateTestCase中只包含逻辑程序,测试工具在执行用例时,会根据数据文件初始化实例变量。如此一来,分离了数据和逻辑,逻辑部分变得可重用,可定制。在经过这般处理后,一个测试用例划分为测试用例逻辑程序测试用例数据两个部分。在这里,我将测试用例逻辑程序抽象为TestDriver,即测试驱动器,而测试用例数据称作TestParam,即测试驱动数据。

TestDriver

与unittest等单元测试工具不同的是,我并没有将测试固件和主程序代码放在一个文件中或类中,而是用三个文件分别存放————setUp.py,tearDown.py,exec.py,并通过传递实例变量来共享TestCase实例。
因此,TestDriver的示例代码如下:
setUp.py代码:

def  setUp(tc):
    #固件构建代码
    Pass

tearDown.py代码:

def  tearDown (ts):
    #固件构建代码
    Pass

exec.py 代码:

def  tc_case1 (tc):
    #case1的测试逻辑代码
    pass
def  tc_case2(tc):
    #case2的测试逻辑代码
    pass
#……
def  tc_caseN(tc):
    #caseN的测试逻辑代码
    pass

此外,TestDriver还包括默认的驱动参数信息,param.json,示例如下:

{
    "key1":"value1",
    "key2":"value2",
    "key3":"value3"
}

TestCase

抽象出TestDriver后,在定义一个TestCase时不需要再写测试程序,而是指定一个TestDriver和TestParam,TestCase的示例文件如下:

{
    "TestCase":"caseName",
    "TestDriver": "dirverName",
    "TestParam": {
        "key1": "value1",
        "key2": "value2",
        "key3": "value3"
    }
}

TestPlan

为了更好地组织TestCase故提出TestPlan这一概念,在执行完TestPlan后,会生成测试报告,发送测试通知。示例如下:

{
    "TestPlan":"planName",
    "CaseList":"tc_case1,tc_case2,tc_case3",
    "EmailList":"xx@xx,xxx@xx",
    "Crontab":"xxx"
}

CaseList即TestPlan所包含的TestCase,EmailList即TestPlan执行完毕后接收通知的邮箱信息,Crontab即TestPlan的定时器执行的crontab配置。

TestLib

当测试逻辑比较复杂时,TestDriver中代码可能也随之变得复杂。这时需要提供一完善的Python测试库,所有的业务操作均在测试库中,TestDriver只需要引用测试库进行业务操作,不包含业务逻辑的具体实现程序。随着TestLib的不断丰富,TestDriver中内容越来简明清晰,构建一个TestDriver也变得简单。于此,测试工作变得简单和更容易维护。

总结

“driver-case-plan”这种测试概念模型是对传统的单元测试框架的一种泛化抽象,它使得case在一定程度上更将通用,不受测试类型和业务类型的限制。此外,由于TestDriver的setUp、tearDown和exec是三个独立的文件,而不是像传统的单元测试工具这三部分是融合在一个文件中,在Web化时很难用一个单一的字段存储各部分的代码。再加上,TestCase和TestPlan也是JSON格式的文件,这是不是意味着该工具更容易Web化?在添加TestDriver后,TestCase和TestPlan的相关操作(添加用例计划、执行用例计划等)只需要在Web端处理即可?是不是可以依据该工具很容易地实现一个通用的CASE管理平台?

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!