1、概念介绍
unit test:单元测试,可以简单粗暴地理解成用一段代码去测试另外一段代码。unittest作为Python单元测试框架之一,除了用来做单元测试之外,还可以用来完成接口自动化,UI自动化(配合Selenium使用),自动化框架开发等。
test fixture:测试用例执行前的准备工作以及测试用例执行完成后的清理工作。比如数据库测试前要建立连接,测试后要关闭连接。
test case:单元测试中最小的单元。
test suite:测试套件是测试用例,测试套件或者两者的集合。通常被用来把测试用例组织起然后交给test runner执行。
test runner:测试执行器是执行用例并向用户展示结果的组件。
2、准备工作
2.1、开发环境
- 操作系统:Ubuntu 18.04.1 LTS
- Python版本:3.7.0
- 开发工具:PyCharm Edu
- 本机已安装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;
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、测试场景
- 注册时存在同名状态为active的用户,返回SameNameError
- 注册成功,返回regSucess
- 注册密码长度不符合要求,返回passwordLenError
- 登录成功,返回loginSucess
- 登录密码不对,返回passwordError
- 登录状态为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运行结果]:
- 测试类继承与unittest.TestCase
- 一个测试用例就是Python中的一个函数。
- 测试用例必须要以test开头
4、test fixture
test fixture包含两个方法,setUp(self)和tearDown(self)分别用于执行测试用例前的准备工作和执行完测试用例后的清理工作。这个两个方法会在每个用例的执行前或执行后被调用。把准备和清理工作和测试用例放在会导致很多重复代码且不易维护。test fixture的使用场景到底是什么样的呢?
-
场景一:比如说用户注册时会先校验数据库中是否存在状态为active的同名用户,那么该用例执行过之后,需要清理user_info表中的记录,这个动作就是在tearDown(self)方法中完成的,否则再次执行就会报错。
-
场景二:要验证同名用户名无法注册的异常场景,需要先注册一次或者手动在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运行结果]:
- 如果setUp()执行成功,那么不论用例是否执行成功,tearDown()都会执行。
- 如果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把测试套迭代,然后执行每一个测试用例。 一些方法可以将用例添加到测试套中。
- addTest(test):添加一个TestCase或TestSuite到套件中。
- addTests(tests):把TestCase和TestSuite中给的所有的测试实例添加到套件中。
TestSuite和TestCase都有如下方法:
- countTestCases():返回测试用例的数量。
- 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运行结果 ]: