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運行結果 ]: