Python:Python 自动化测试框架 unittest【转载】

转载至https://www.cnblogs.com/webDepOfQWS/p/10725194.html

目录


1、概念介绍

unit test:单元测试,可以简单粗暴地理解成用一段代码去测试另外一段代码。unittest作为Python单元测试框架之一,除了用来做单元测试之外,还可以用来完成接口自动化,UI自动化(配合Selenium使用),自动化框架开发等。

test fixture:测试用例执行前的准备工作以及测试用例执行完成后的清理工作。比如数据库测试前要建立连接,测试后要关闭连接。

test case:单元测试中最小的单元。

test suite:测试套件是测试用例,测试套件或者两者的集合。通常被用来把测试用例组织起然后交给test runner执行。

test runner:测试执行器是执行用例并向用户展示结果的组件。

unittest 官方文档链接

2、准备工作

2.1、开发环境

  • 操作系统:Ubuntu 18.04.1 LTS
  • Python版本:3.7.0
  • 开发工具:PyCharm Edu
  • 本机已安装MySQL
  • 代码结构

MySQ安装的链接

通过Python访问MySQL数据库的链接

2.2、创建数据库和表

登录数据库创建数据库ums,在数据库中创建表user_info,SQL语句如下:

create database ums;

#status分为active和inactive两种
create table user_info(
id int(10)  primary key auto_increment,
name char(15) not null,
password char(100) not null,
status char(10) not null)
ENGINE=InnoDB DEFAULT CHARSET=utf8;

MySQ常用命令的链接

2.3、编写简单的注册登录代码

在Project下新建Python包 userManage ,在该包下创建Python文件userRegLogin.py。注册时先判断是否存在状态为active的用户,不存在则更新信息到数据库中。登录时先判断用户状态,如果为active则去数据库中查询其密码并比较密码是否正确。userRegLogin.py的代码如下:

#coding:utf-8
#导入MySQL驱动
import   mysql.connector
import warnings

#定义类user_ange
class user_manage():
    #初始化 传入两个参数:用户名和密码
    def __init__(self,name,passwd):
        self.name = name
        self.passwd = passwd

    #执行select SQL语句并返回结果
    def execQuerySql(self,sql,arg):
        #临时屏蔽告警ResourceWarning
        warnings.simplefilter("ignore", ResourceWarning)
        try:
            self.conn =  mysql.connector.connect(host="127.0.0.1",user='root',database='ums',password='password')
            self.cursor = self.conn.cursor()
            self.cursor.execute(sql,[arg])
            val = self.cursor.fetchone()[0]
            return val
        except Exception as e:
            print(e)
        finally:
            self.cursor.close()
            self.conn.close()


    #执行insert语句
    def execUpdateSql(self,sql,args):
        warnings.simplefilter("ignore", ResourceWarning)
        try:
            self.conn =  mysql.connector.connect(host="127.0.0.1",user='root',database='ums',password='password')
            self.cursor = self.conn.cursor()
            self.cursor.execute(sql,args)
            self.conn.commit()
        except Exception as  e:
            print(e)
        finally:
            self.cursor.close()
            self.conn.close

    #判断用户是否存在
    def userIsExist(self):
        sql1 = '''select count(*) from user_info where status='active' and name = %s'''
        userCount = self.execQuerySql(sql1,self.name)
        if userCount:
            return False
        else:
            return True

    #用户注册
    def userReg(self):
        lenFlag = len(self.passwd) >=6 and len(self.passwd)<=10
        #判断是否存在同名用户
        if self.userIsExist():
            #判断密码长度是否符合要求
            if lenFlag:
                sql2 = '''insert into user_info values (null,%s,%s,'active');'''
                # self.cursor.execute(sql2,[self.name,self.passwd])
                # self.conn.commit()
                args = [self.name,self.passwd]
                self.execUpdateSql(sql2,args)
                return "regSucess"
            else:
                return "passwordLenError"
        else:
            return "SameNameError"

    def isActive(self):
        sql3 = '''select status from user_info where  name=%s;'''
        # self.cursor.execute(sql3,[self.name])
        # ustatus = self.cursor.fetchone()[0]
        ustatus = self.execQuerySql(sql3,self.name)
        if ustatus == "active":
            return True
        else:
            return  False

    #用户登录
    def userLogin(self):
        '''
        用户状态为active则校验密码是否正确
        反之则抛出异常
        '''
        if self.isActive():
            sql4 = '''select password from user_info where name=%s and status="active";'''
            pwdInDB = self.execQuerySql(sql4,self.name)
            if self.passwd == pwdInDB:

                return "loginSucess"
            else:
                return "passwordError"
        else:
            return "UserStatusError"

2.4、运行结果

在userRegLogin.py文件末尾插入代码并执行

if __name__ == '__main__':
    #实例化
    user1 = user_manage("TestUser1","1234User1")
    user1.userReg()

登录数据库查看结果

mysql> select  *  from  user_info;
+----+-----------+-----------+--------+
| id | name      | password  | status |
+----+-----------+-----------+--------+
|  3 | TestUser1 | 1234User1 | active |
+----+-----------+-----------+--------+
1 row in set (0.00 sec)

点击这里返回目录

2.5、测试场景

  1. 注册时存在同名状态为active的用户,返回SameNameError
  2. 注册成功,返回regSucess
  3. 注册密码长度不符合要求,返回passwordLenError
  4. 登录成功,返回loginSucess
  5. 登录密码不对,返回passwordError
  6. 登录状态为inactive,返回UserStatusError

3、一个简单的例子

在Project下新建Python包testCases,在testCases下新建Python文件userRegTest.py,用来编写测试用户注册功能的代码。
[示例1]:userRegTest.py

#coding:utf-8
#导入unittest模块
import unittest
#从模块userRegLogin中导入类user_manage
from userManage.userRegLogin import user_manage
#定义测试类,继承于unittest.TestCase
class regTest(unittest.TestCase):
    #测试方法或者叫测试用例必须以test开头
    #测试场景:密码长度小于6
    def test_pwdlen(self):
        user2 = user_manage("TestUser4","1234")
        self.assertEqual(user2.userReg(),"passwordLenError")
    #测试场景:正常注册
    def test_normalreg(self):
        user2 = user_manage("TestUser5","1234User")
        self.assertEqual(user2.userReg(),"regSucess")

#执行test开头的方法
if __name__ == '__main__':
    unittest.main()

[示例1运行结果]:

  1. 测试类继承与unittest.TestCase
  2. 一个测试用例就是Python中的一个函数。
  3. 测试用例必须要以test开头     

点击这里返回目录

4、test fixture

test fixture包含两个方法,setUp(self)和tearDown(self)分别用于执行测试用例前的准备工作和执行完测试用例后的清理工作。这个两个方法会在每个用例的执行前或执行后被调用。把准备和清理工作和测试用例放在会导致很多重复代码且不易维护。test fixture的使用场景到底是什么样的呢?

  1. 场景一:比如说用户注册时会先校验数据库中是否存在状态为active的同名用户,那么该用例执行过之后,需要清理user_info表中的记录,这个动作就是在tearDown(self)方法中完成的,否则再次执行就会报错。

  2. 场景二:要验证同名用户名无法注册的异常场景,需要先注册一次或者手动在user_info表里面插入数据,这个动作就是在setUp(self)中完成的。

    4.1、setUp和tearDown示例

    把被测类实例化放在setUp()f方法里面。正常注册场景的清理动作:删除表user_info中对应的记录放在tearDown()方法中。接下来完善一下示例1中的代码:
    [示例2]:userRegTest.py
 
#coding:utf-8
#导入unittest模块
import unittest
#从模块userRegLogin中导入类user_manage
from userManage.userRegLogin import user_manage

#定义测试类,继承于unittest.TestCase
class regTest(unittest.TestCase):
    #测试方法或者叫测试用例必须以test开头
    #测试场景:密码长度小于6
    def setUp(self):
        print("setUp run before test case")
        self.user1 = user_manage("TestUser1","1234")
        self.user2 = user_manage("TestUser2","TestUser2")
        self.user3 = user_manage("TestUser3","TestUser3")

        #注册TestUser3
        self.user3.userReg()

    def test_pwdlenerr_L1(self):
        print("test case:test_pwdlenerr_L1")
        res = self.user1.userReg()
        self.assertEqual(res,"passwordLenError")


#测试场景:正常注册
    def test_regsucess_L0(self):
        print("test case:test_regsucess_L0")
        res = self.user2.userReg()
        self.assertEqual(res,"regSucess")

    #测试场景:用户名重名
    def test_regagain_L1(self):
        print("test case:test_regagain_L1")
        res = self.user3.userReg()
        self.assertEqual(res,"SameNameError")

    def tearDown(self):
        print("tearDown run after test case")
        sql = '''delete from  user_info  where name = %s'''
        self.user2.execUpdateSql(sql,[self.user2.name])
        self.user3.execUpdateSql(sql,[self.user3.name])

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

[示例2运行结果]:

  1. 如果setUp()执行成功,那么不论用例是否执行成功,tearDown()都会执行。
  2. 如果setUp()执行失败,那么用例及tearDown()方法都不会被执行。

如果只想在所有用例执行之前只执行一次准备工作怎么操作呢?那就需要用到setUpClass() 和 tearDownClass()了。在这两个方法内部可以自己编写函数实现准备工作或清理动作。

4.2、setUpClass 和 tearDownClass

[示例3]:

#定义测试类,继承于unittest.TestCase
class regTest(unittest.TestCase):
    #测试方法或者叫测试用例必须以test开头
    #测试场景:密码长度小于6
    @classmethod
    def setUpClass(cls):
        print("setUpClass run before test case")

    def test_pwdlen(self):
        print("test case:test_pwdlen")
        self.user1 = user_manage("TestUser8","1234")
        res = self.user1.userReg()
        self.assertEqual(res,"passwordLenError")

    #测试场景:正常注册
    def test_normalreg(self):
        print("test case:test_normalreg")
        self.user2 = user_manage("TestUser10","123456")
        res = self.user2.userReg()
        self.assertEqual(res,"regSucess")

    @classmethod
    def tearDownClass(cls):
        # sql = '''delete from  user_info  where name = %s'''
        # sef.user2.execUpdateSql(sql,[self.user2.name])
        print("tearDownClass run after test case")
#执行test开头的方法
if __name__ == '__main__':
    unittest.main()

注意:没有清理动作如果想要用例跑成功的话需要手动删除表里对应的用户信息或者修改注册时传入的name。
[示例3运行结果]::

点击这里返回目录

5、测试套

5.1、登录功能测试

在包testcases下新建Python文件userLoginTest.py,编写测试登录功能的代码。
[ 示例4 ]:userLoginTest.py

#coding:utf-8
import unittest
from userManage.userRegLogin import user_manage

class loginTest(unittest.TestCase):
    #准备工作
    def setUp(self):
        self.user4 = user_manage("TestUser4","TestUser4")
        self.user5 = user_manage("TestUser4","TestUser5")
        self.user6 = user_manage("TestUser6","TestUser6")

        #验证登录功能前需要先注册
        self.user4.userReg()
        #构造一个状态为inactive的用户
        self.user6.userReg()
        setStatus = '''update user_info set status="inactive" where name=%s;'''
        self.user6.execUpdateSql(setStatus,[self.user6.name])
    #登录成功测试
    def test_loginsucess_L0(self):
        res = self.user4.userLogin()
        self.assertEqual(res,"loginSucess")
    #密码错误测试
    def test_pwdwrong_L0(self):
        res = self.user5.userLogin()
        self.assertEqual(res,"passwordError")
    #用户状态异常测试
    def test_statuserr_L1(self):
        res = self.user6.userLogin()
        self.assertEqual(res,"UserStatusError")
    #清理工作
    def tearDown(self):
        print("tearDown run after test case")
        #删除用户TestUser4,TestUser6
        sql = '''delete from  user_info  where name = %s'''
        self.user4.execUpdateSql(sql,[self.user4.name])
        self.user6.execUpdateSql(sql,[self.user6.name])

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

[ 示例4运行结果 ]:

5.2、组织用例

unittest通过类unittest.TestSuite来组织测试用例。这个类返回测试用例或测试套的集合,它可以被test runner执行。运行一个测试套相当于test runner把测试套迭代,然后执行每一个测试用例。 一些方法可以将用例添加到测试套中。

  1. addTest(test):添加一个TestCase或TestSuite到套件中。
  2. addTests(tests):把TestCase和TestSuite中给的所有的测试实例添加到套件中。

TestSuite和TestCase都有如下方法:

  1. countTestCases():返回测试用例的数量。
  2. run(result):运行套件相关的测试用例,收集测试结果到result对象中并传给result。

在Project下创建Python文件run.py,通过TestSuite来组织注册登录所有的用例并运行。
[ 示例5 ]:run.py

#coding:utf-8
import  unittest

#从testCase包里面导入测试类
from testCases.userLoginTest import loginTest
from testCases.userRegTest import regTest

#构造测试套
def suite():
    suite = unittest.TestSuite()
    suite.addTest(loginTest("test_loginsucess_L0"))
    suite.addTest(loginTest("test_pwdwrong_L0"))
    suite.addTest(loginTest("test_statuserr_L1"))
    suite.addTest(regTest("test_pwdlenerr_L1"))
    suite.addTest(regTest("test_regsucess_L0"))
    suite.addTest(regTest("test_regagain_L1"))
    return suite

#运行测试用例
if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    #调用test runner的run方法执行用例
    runner.run(suite())

[ 示例5运行结果 ]:

 点击这里返回目录

6、生成测试报告

6.1、下载HTMLTestRunner.py

原版下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html 。原版的只支持Python 2.x版本,Python 3.x版本需要做适配。
适配后的下载地址:https://github.com/Slience007/pyunitest/blob/master/untils/HTMLTestRunner.py

6.2、安装HTMLTestRunner.py

安装方法比较简单,将HTMLTestRunner.py放到sys.path路径下即可。ubuntu下,我放到了如下路径:/usr/lib/python3.7。

6.3、生成报告

HTMLTestRunner.py提供HTMLTestRunner()类来代替unittest.TextTestRunner()执行用例,修改后的run.py的代码如下:

#coding:utf-8
import  unittest
#导入HTMLTestRunner
from HTMLTestRunner import  HTMLTestRunner
#从testCase包里面导入测试类
from testCases.userLoginTest import loginTest
from testCases.userRegTest import regTest

#构造测试套
def suite():
    suite = unittest.TestSuite()
    suite.addTest(loginTest("test_loginsucess_L0"))
    suite.addTest(loginTest("test_pwdwrong_L0"))
    suite.addTest(loginTest("test_statuserr_L1"))
    suite.addTest(regTest("test_pwdlenerr_L1"))
    suite.addTest(regTest("test_regsucess_L0"))
    suite.addTest(regTest("test_regagain_L1"))
    return suite

#运行测试用例
if __name__ == '__main__':
    # runner = unittest.TextTestRunner()
    # #调用test runner的run方法执行用例
    # runner.run(suite())
    #以二进制格式打开TestReport.html用于写入数据
    with open("./TestReport.html","wb") as f:
        runner = HTMLTestRunner(stream=f,title="Reg And Login Test Report")
        runner.run(suite())

运行run.py后,打开TestReport.html,查看生成的测试报告。

点击这里返回目录

7、编写邮件发送工具

在Project下新建包utils用来封装一些常用的工具,在utils下新建Python文件emailUtil.py。定义sendEmail类。这个类主要包含3个方法:

  1. init():初始化
  2. writeEmail():构造邮件主题,邮件正文,添加邮件附件。
  3. sendEmail():连接邮件服务器,认证,发送邮件。我采用的是网易邮件服务器,其地址是smtp.126.com。收件地址为QQ邮箱。

[ 代码如下:]emailUtil.py

#coding:utf-8
'''
email模块负责构造邮件内容
smtplib模块负责发送邮件
'''
from email.mime.text import  MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
from email.header import Header

class sendEmail():
    #定义全局变量邮件服务器地址,登录用户,授权码
    global MAILHOST,MAILUSER,MAILPWD
    MAILHOST = "smtp.126.com"
    MAILUSER = "××××@126.com"
    MAILPWD = "×××"

    def __init__(self,subject,content,reveiver,attachPath=""):
        self.subject = subject
        self.content = content
        self.receiver = reveiver
        self.attachPath = attachPath
    #写邮件,返回msg.as_string()
    def writeEmail(self):
        msg = MIMEMultipart()
        #邮件正文
        msg.attach(MIMEText(self.content, 'plain', 'utf8'))
        receiverName = ",".join(self.receiver)
        msg['from'] = Header(MAILUSER,'utf-8')
        #msg['to'] =  Header(",".join(self.receiver)).encode()
        msg['to'] = Header(receiverName).encode()
        #邮件主题
        msg['Subject'] = Header(self.subject,'utf-8').encode()
        #print("msg is:",msg)
        #attachPath不为空则添加附件到邮件中
        if self.attachPath != "":
            with open(self.attachPath, 'rb') as f:
                attach1  = MIMEText(f.read(), 'base64', 'utf-8')
                attach1["Content-Type"] = 'application/octet-stream'
                #filename可以随便写
                attach1["Content-Disposition"] = 'attachment; filename="Result.html"'
                msg.attach(attach1)

        return msg.as_string()

    #发送邮件
    def sendEmail(self):
        receiver = ";".join(self.receiver)
        try:
            #连接邮件服务器
            server = smtplib.SMTP()
            server.connect(MAILHOST,25)
            #打开debug模式可以看到握手过程
            #server.set_debuglevel(1)
            #登录,MAILPWD为网易邮件的授权码
            server.login(MAILUSER,MAILPWD)
            #发送邮件
            server.sendmail(MAILUSER,receiver,self.writeEmail())
            server.quit()
            print("Email send sucess.")
        except Exception as  e:
            print("Email send fail.")
            print(e)

在编写邮件工具的时候,碰到了一个错误:smtplib.SMTPDataError: (554, b'DT:SPM。原因可能是:邮件被网易邮件服务器当成了垃圾邮件。解决办法:邮件主题不能包含test,另外msg[from"],msg['to']要和server.sendmail(MAILUSER,receiver,self.writeEmail())中的MAILUSER和receiver保持一致。

点击这里返回目录

8、发送邮件

在发送邮件之前,先获取本次执行用例总数,失败用例数,成功用例数,跳过的用例数。并计算出用例通过率。

  1. suite().countTestCases():获取用例总数。
  2. runner.run(suite()).success_count:运行通过的用例数。
  3. runner.run(suite()).failure_count:失败的用例数。
  4. runner.run(suite()).skipped:返回的是跳过的用例list。

接下来来修改run.py ,需要先从utils模块导入sendEmail类,构造主题,邮件正文,指定收件人列表,指定测试报告的路径,之后调用sendEmail方法发送邮件。修改后的run.py代码如下:

#coding:utf-8
import  unittest
#导入HTMLTestRunner
from HTMLTestRunner import  HTMLTestRunner
#从testCase包里面导入测试类
from testCases.userLoginTest import loginTest
from testCases.userRegTest import regTest

from utils.emailUtil import sendEmail
#构造测试套
def suite():
    suite = unittest.TestSuite()
    suite.addTest(loginTest("test_loginsucess_L0"))
    suite.addTest(loginTest("test_pwdwrong_L0"))
    suite.addTest(loginTest("test_statuserr_L1"))
    suite.addTest(regTest("test_pwdlenerr_L1"))
    suite.addTest(regTest("test_regsucess_L0"))
    suite.addTest(regTest("test_regagain_L1"))
    return suite

#运行测试用例
if __name__ == '__main__':
    # runner = unittest.TextTestRunner()
    # #调用test runner的run方法执行用例
    # runner.run(suite())
    #以二进制格式打开TestReport.html用于写入数据
    with open("./TestReport.html","wb") as f:
        runner = HTMLTestRunner(stream=f,title="Reg And Login Test Report")
        result = runner.run(suite())
        totalNums = suite().countTestCases()
        passedNums = result.success_count
        failedNums = result.failure_count
        skippedNums = len(result.skipped)
        #通过率,保留两位小数
        passRate = round(passedNums * 100/  totalNums)
        emailBody = "Hi,all:
 	本次构建一共运行:{totalNums}个用例,通过{passedNums}个,失败{failedNums}个,跳过{skippedNums}个。通过率:{passRate}%.
 	详细信息请查看附件。"
        content = emailBody.format(totalNums=totalNums,passedNums=passedNums,failedNums=failedNums,skippedNums=skippedNums,passRate=passRate)
        #收件人列表
        receiver = ['××××@qq.com',"×××××@126.com"]
        #测试报告的路径
        path1 = "/home/stephen/PycharmProjects/unitTestDemo/TestReport.html"
        subject = "登录注册功能每日构建"
        e = sendEmail(subject,content,receiver,attachPath=path1)
        #发送邮件
        e.sendEmail()

运行run.py。登录邮箱查看已经发送成功的邮件。

 点击这里返回目录

9、用例结果校验

用unittest来完成自动化测试时,结果校验无疑是非常重要的。用例校验不完整或者校验过于简单,是无法发现产品的缺陷或Bug的。就自动化测试来而言,其目的是代替部分手动测试,将测试人员从繁重重复的功能测试中解放出来。试想一下,一些缺少校验或简单校验的用例运行完后通过率即使达到100%,这能说明产品或版本没有任何问题吗?能够增强版本发布的信心吗?unittest提供了很多断言方法用于测试结果的校验。常用的断言方法如下:


所有的断言方法都提供一个可选参数msg,用于在测试失败时显示。

  • assertEqual(a, b,msg=None):校验a是否等于b,如果相等,测试通过,反之则测试失败。msg为可选参数,如果传入了该参数,在测试失败时会打印。
>>> class assertTest(unittest.TestCase):
...     pass
... 
>>> ast1 = assertTest() 

>>> ast1.assertEqual(1,2,msg="1 is not equal to 2")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/unittest/case.py", line 839, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/usr/local/lib/python3.7/unittest/case.py", line 832, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 1 != 2 : 1 is not equal to 2
  • assertNotEqual(a, b, msg=None):校验a是否不等于b,如果不相等,测试通过。反之则测试失败。
  • assertTrue(x,msg=None):验证x是否为True,x可以为值或表达式,x为True则测试通过。反之,则测试失败,msg为可选参数,如果传入了该参数,在测试失败时会打印。
>>> ast1.assertTrue(1=2,msg="1=2,False")
  File "<stdin>", line 1
SyntaxError: keyword can't be an expression
>>> ast1.assertTrue(1==2,msg="1=2,False")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/unittest/case.py", line 692, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true : 1=2,False
  • assertIs(a, b):判断a is b是是否成立。等价于判断id(a) == id(b)是否成立,如果成立,则测试通过。反之,测试失败。
  • assertIsNone(x):判断x是否为None。如果为None则测试通过,反之,测试失败。
  • assertIn(a, b):判断a是否在b中。如果在,则测试通过,反之,测试失败。
  • assertIsInstance(a, b):判断a是否为b的实例。如果是,则测试通过,反之,测试失败。

unittest还提供了如下方法用于检查异常,警告,日志信息。图片来自unittest官方文档。

检查异常,警告,日志的使用方法大同小异。在此不一一说明了,下面举例说明assertRaises(exc, fun, *args, **kwds)和assertRaises(exc, fun, *args, **kwds)的用法。
 [ 示例1 ] assertRaises()的用法

#coding:utf-8
'''
定义一个除法的函数
当y==0时,会抛出错误ZeroDivisionError
'''
import unittest
def div(x,y):
        return  x/y

#定义一个测试类assertTest,继承于unittest.TestCase
class assertTest(unittest.TestCase):
    #pass表示什么也不做
    pass
ast2 = assertTest()
'''
第一个参数为异常的类型
第二个为方法名称
后面为方法的参数
'''
ast2.assertRaises(ZeroDivisionError,div,1,0)
print("---------------------------------------")
ast2.assertRaises(ZeroDivisionError,div,1,1)

调用div(1,1)不会抛出异常ZeroDivisionError,所以assertRaises()测试失败,上述代码运行结果如下:

如果只传入了expetion或msg参数,就可以在with上下文管理器中测试一段代码而不仅仅是测试函数,就像下面这样。

with ast2.assertRaises(ZeroDivisionError):
     div(1,1)

with ast2.assertRaises(ZeroDivisionError):
    div(1,0)

运行结果如下:

 [ 示例2 ] assertRaises()的用法

#导入warnings模块
import warnings
#定义一个简单的函数,打印级别为UserWarning的告警
def warnTest():
     warnings.warn("This is warning test")

ast2.assertWarns(UserWarning,warnTest)
print("--------------------------------")
with ast2.assertWarns(UnicodeWarning):
    warnTest()

warnTest()函数不会打印UnicodeWarning级别的告警,所以第二个告警校验会失败,运行结果如下:


点击这里返回目录

10、跳过用例

在一些场景下并不会执行所有的用例,而是选择性跳过一部分用例。unittest支持跳过单个用例或整个测试类。跳过用例需要用到unittest.skip()装饰器。

  • @unittest.skip(reason):无条件跳过单个用例或测试类,reason是跳过的原因。
  • @unittest.skipIf(condition, reason):if条件成立则跳过单个用例或测试类,reason是跳过的原因。
  • @unittest.skipUnless(condition, reason):条件为False则跳过单个用例或测试类,reason是跳过的原因。
  • @unittest.expectedFailure:标记测试用例为失败,不会出现在统计结果中。
  • exception unittest.SkipTest(reason):跳过用例并抛出异常。

[ 示例3 ]:直接跳过注册功能的用例
对userRegTest.py做如下修改,并执行userRegTest.py:

@unittest.skip("skip test case of user reg")
    def test_pwdlenerr_L1(self):
        print("test case:test_pwdlenerr_L1")
        res = self.user1.userReg()
        self.assertEqual(res,"passwordLenError")

    #测试场景:正常注册
    @unittest.skipIf(2>1,"skip if condiction")
    def test_regsucess_L0(self):
        print("test case:test_regsucess_L0")
        res = self.user2.userReg()
        self.assertEqual(res,"regSucess")

    #测试场景:用户名重名
    @unittest.skipUnless(1<0,"skip unless.")
    def test_regagain_L1(self):
        print("test case:test_regagain_L1")
        res = self.user3.userReg()
        self.assertEqual(res,"SameNameError")

执行结果如下:

点击这里返回目录

11、Test Discovery

unittest支持用例发现功能,在unittest.defaultTestLoader中实现。使用的时候需要传入两个参数:寻找的起始目录,匹配的测试文件的格式(默认test*.py)。用例发现也支持命令行模式。

[ 示例4 ]:testDiscover

    startPath = './testCases'
    discover = unittest.defaultTestLoader.discover(start_dir=startPath,pattern='*Test.py')
    print(discover)

运行结果:

12、加载用例

unittest支持从模块和测试类中提取测试用例创建测试套。在class unittest.TestLoader中实现。TestLoader常见的加载方法如下:

  • loadTestsFromTestCase(testCaseClass):从unittest.TestCase的子类即测试类中加载用例并返回测试套。
  • loadTestsFromName(name,module=None):从特定的字符串说明符中加载用例并返回测试套。
  • loadTestsFromNames(names,module=None):用法和 loadTestsFromName(name,module=None)类似,不同的是它可以接受字符串说明符列表,而不是一个。
  • loadTestsFromModule(module, pattern=None):从模块中加载所有测试用例,返回一个测试套件。
  • getTestCaseName(testCaseClass):返回一个有序的包含在testCaseClass中的方法名列表。

[示例5 ] :TestLoader用法举例

#coding:utf-8

import unittest
import testCases.userLoginTest,testCases.userRegTest
from testCases.userRegTest import regTest
from testCases.userLoginTest import loginTest

loader = unittest.TestLoader()

print("从测试类loginTest加载所有的用例:")
caseInLoginTest = loader.getTestCaseNames(loginTest)
print(caseInLoginTest)

print("从测试模块中加载用例:")
loadByModule = loader.loadTestsFromModule(testCases.userLoginTest)
print(loadByModule)

print("从测试类中加载用例:")
loadByTestClass = loader.loadTestsFromTestCase(regTest)
print(loadByTestClass)
print("从特定的字符串标识符中加载用例")

loadBySpecifier = loader.loadTestsFromName("regTest.test_regagain_L1")
print(loadBySpecifier)

print("从字符符标识符列表中加载用例")
specStrs = ["regTest.test_regagain_L1","test_regsucess_L0"]
loadBySpecifiers = loader.loadTestsFromNames(specStrs)
print(loadBySpecifiers)

运行结果如下:

在发布时间紧张,产品迭代频繁的情况下,很多时候上线一个新特性,对已发布的老特性采用的回归策略是:只执行L0即(Level 0)级别的测试用例。这个在unittest里面怎么实现呢?解决方案是:

    1. 规范特性L0用例的命名格式,比如:test_xxx_L0.
    2. 加载用例
    3. 通过正则表达式匹配出L0级别的用例集合,然后交给test runner执行。

点击这里返回目录

每天努力一点,每天学习一点。 Keep Moving...
原文地址:https://www.cnblogs.com/channy14/p/11724386.html