如有任何學習問題,可以添加作者微信:superz-han
課程目錄
Python接口測試實戰1(上)- 接口測試理論
Python接口測試實戰1(下)- 接口測試工具的使用
Python接口測試實戰2 - 使用Python發送請求
Python接口測試實戰3(上)- Python操作資料庫
Python接口測試實戰3(下)- unittest測試架構
Python接口測試實戰4(上) - 接口測試架構實戰
Python接口測試實戰4(下) - 架構完善:用例基類,用例标簽,重新運作上次失敗用例
Python接口測試實戰5(上) - Git及Jenkins持續內建
Python接口測試實戰5(下) - RESTful、Web Service及Mock Server
更多學習資料請加QQ群: 822601020擷取
PDF下載下傳:連結:https://pan.baidu.com/s/1OwAa8nl1eeBj8fcrgd3sBA 密碼:e9d8
本節内容
- unittest簡介
- 用例編寫
- 用例組織及運作
- 生成測試報告
unitttest簡介
參考:unittest官方文檔 翻譯版
為什麼要使用unittest?
在編寫接口自動化用例時,我們一般針對一個接口建立一個.py檔案,一條測試用例封裝為一個函數(方法),但是在批量執行的過程中,如果其中一條出錯,後面的用例就無法執行。使用測試架構可以互不影響的用例執行及更靈活的執行控制
unittest特點
- python自帶的單元測試架構,無需安裝
- 用例執行互不幹擾
- 提供不同範圍的setUp(測試準備)和tearDown(測試清理)方法
- 提供豐富的斷言方法
- 可以通過discover批量執行所有子產品的用例
- 可以通過TestSuite(測試集)靈活的組織用例
unittest幾大組成部分
- TestCase: 用例對象,編寫測試用例時要繼承該類,以具有TestCase的屬性和方法
- TestSuite: 測試集或測試套件,測試用例的集合,用來組織用例,支援嵌套
- TestLoader: 用例加載器,用于向TestSuite中添加用例
- TextTestRunner: 用例執行器(輸出文本結果),一般以TestSuite為機關執行用例
- TestResult: 測試結果
- 建立一個test_開頭(必須)的.py檔案,如
test_user_login.py
- 導入unittest
- 編寫一個Test開頭(必須)的類,并繼承unittest.TestCase,做為測試類
- 在類中編寫一個test_開頭(必須)的方法,作為用例
test_user_login.py
# 檔案必須test_開頭
import unittest # 導入unittest
import requests
class TestUserLogin(unittest.TestCase): # 類必須Test開頭,繼承TestCase才能識别為用例類
url = 'http://115.28.108.130:5000/api/user/login/'
def test_user_login_normal(self): # 一條測試用例,必須test_開頭
data = {"name": "張三", "password": "123456"}
res = requests.post(url=self.url, data=data)
self.assertIn('登入成功', res.text) # 斷言
def test_user_login_password_wrong(self):
data = {"name": "張三", "password": "1234567"}
res = requests.post(url=self.url, data=data)
self.assertIn('登入失敗', res.text) # 斷言
if __name__ == '__main__': # 如果是直接從目前子產品執行(非别的子產品調用本子產品)
unittest.main(verbosity=2) # 運作本測試類所有用例,verbosity為結果顯示級别
用例執行順序:并非按書寫順序執行,而是按用例名ascii碼先後順序執行
用例斷言
unittest提供了豐富的斷言方法,常用為以下幾種:
- 判斷相等
- assertEqual(a,b)/assertNotEqual(a,b): 斷言值是否相等
- assertIs(a,b)/assertIsNot(a,b): 斷言是否同一對象(記憶體位址一樣)
- assertListEqual(list1, list2)/assertItemNotEqual(list1, list2): 斷言清單是否相等
- assertDictEqual(dict1, dict2)/assertDictNotEqual(dict1, dict2): 斷言字典是否相等
- 是否為空
- assertIsNone(a)/assertIsNotNone(a)
- 判斷真假
- assertTrue(a)/assertFalse(a)
- 是否包含
- assertIn(a,b)/assertNotIn(a,b) # b中是否包含a
- 大小判斷
- assertGreater(a,b)/assertLess(a,b) : 斷言a>b / 斷言a<b
- assertGreaterEqual(a,b)/assertLessEqual: 斷言a>=b / 斷言a<=b
- 類型判斷
- assertIsInstance(a,dict)/assertNotIsInstance(a,list) # 斷言a為字典 / 斷言a非清單
示例:
import unittest
case = unittest.TestCase()
case.assertEqual(1,2.0/2) # 通過1=2.0/2
case.assertEqual(1, True) # 通過
case.assertIs(1.0, 2.0/2) # 失敗,不是同一對象
case.assertListEqual([1,2],[1,2]) # 通過(順序要一緻)
case.assertDictEqual({"a":1,"b":2},{"b":2,"a":1}) # 通過,字典本無序
case.assertIsNone({}) # 失敗
case.assertFalse({}) # 通過,空字典為False
case.assertIn("h","hello") # 通過
case.assertGreater(3,2) # 通過,3>2
case.assertIsInstance({"a":1}, dict) # 通過
斷言是unittest.TestCase的一種方法,通過斷言判斷用例是否通過(Pass/Fail)
Test Fixtures(用例包裹方法)
Test Fixtures即setUp(用例準備)及tearDown(測試清理)方法,用于分别在測試前及測試後執行
按照不同的作用範圍分為:
- setUp()/tearDown(): 每個用例執行前/後執行一次
- setUpClass()/tearDownClass(): 每個測試類加載時/結束時執行一次
- setUpMoudle()/tearDownMoudle(): 每個測試子產品(一個py檔案為一個子產品)加載/結束時執行一次
import unittest
def setUpModule(): # 目前子產品執行前隻執行一次
print('=== setUpModule ===')
def tearDownModule(): # 目前子產品執行後隻執行一次
print('=== tearDownModule ===')
class TestClass1(unittest.TestCase):
@classmethod # 聲明為類方法(必須)
def setUpClass(cls): # 類方法,注意後面是cls,整個類隻執行一次
print('--- setUpClass ---')
@classmethod
def tearDownClass(cls):
print('--- tearDownClass ---')
def setUp(self): # 該類中每個測試用例執行一次
print('... setUp ...')
def tearDown(self):
print('... tearDown ...')
def test_a(self): # 測試用例
print("a")
def test_B(self): # 大寫B的ascii比小寫a靠前,會比test_a先執行
print("B")
class TestClass2(unittest.TestCase): # 該子產品另一個測試類
def test_A(self):
print("A")
if __name__ == '__main__':
unittest.main()
執行結果:
=== setUpModule ===
--- setUpClass ---
... setUp ...
B
... tearDown ...
... setUp ...
a
... tearDown ...
--- tearDownClass ---
A
=== tearDownModule ===
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
完整的接口測試用例
一條完整的測試接口用例需要包含:
- 資料準備:準備測試資料,可手工準備,也可使用代碼準備(通常涉及資料庫操作)
- 環境檢查:如果手工準備的資料,連接配接資料庫進行環境檢查會使用例更健壯
- 發送請求:發送接口請求
- 響應斷言/資料庫斷言:響應斷言後,還需要進行資料庫斷言,以確定接口資料庫操作的正确性
- 資料清理:如果接口有更資料庫操作,斷言結束後需要還原更改
test_user_reg.py
import unittest
import requests
from db import * # 導入db.py檔案,源碼見上篇
# 資料準備
NOT_EXIST_USER = '範冰冰'
EXIST_USER = '張三'
class TestUserReg(unittest.TestCase):
url = 'http://127.0.0.1:5000/api/user/reg/'
def test_user_reg_normal(self):
# 環境檢查
if check_user(NOT_EXIST_USER):
del_user(NOT_EXIST_USER)
# 發送請求
data = {'name': NOT_EXIST_USER, 'password': '123456'}
res = requests.post(url=self.url, json=data)
# 期望響應結果,注意字典格式和json格式的差別(如果有true/false/null要轉化為字典格式)
except_res = {
"code": "100000",
"msg": "成功",
"data": {
"name": NOT_EXIST_USER,
"password": "e10adc3949ba59abbe56e057f20f883e"
}
}
# 響應斷言(整體斷言)
self.assertDictEqual(res.json(), except_res)
# 資料庫斷言
self.assertTrue(check_user(NOT_EXIST_USER))
# 環境清理(由于注冊接口向資料庫寫入了使用者資訊)
del_user(NOT_EXIST_USER)
def test_user_reg_exist(self):
# 環境檢查
if not check_user(EXIST_USER):
add_user(EXIST_USER)
# 發送請求
data = {'name': EXIST_USER, 'password': '123456'}
res = requests.post(url=self.url, json=data)
# 期望響應結果,注意字典格式和json格式的差別(如果有true/false/null要轉化為字典格式)
except_res = {
"code": "100001",
"msg": "失敗,使用者已存在",
"data": {
"name": EXIST_USER,
"password": "e10adc3949ba59abbe56e057f20f883e"
}
}
# 響應斷言(整體斷言)
self.assertDictEqual(res.json(), except_res)
# 資料庫斷言(沒有注冊成功,資料庫沒有添加新使用者)
# 環境清理(無需清理)
if __name__ == '__main__':
unittest.main(verbosity=2) # 運作所有用例
除了使用unittest.main()運作整個測試類之外,我們還可以通過TestSuite來靈活的組織要運作的測試集
- 建立TestSuite并添加測試用例
import unittest
from test_user_login import TestUserLogin
from test_user_reg import TestUserReg # 從上面兩個例子裡導入測試類
suite = unittest.TestSuite()
suite.addTest(TestUserLogin('test_user_login_normal')) # 添加單個用例
suite.addTests([TestUserReg('test_user_reg_normal'),TestUserReg('test_user_reg_exist')]) # 添加多個用例
# 運作測試集
unittest.TextTestRunner(verbosity=2).run(suite) # verbosity顯示級别,運作順序為添加到suite中的順序
- 使用makeSuite來制作用例集
import unittest
from test_user_login import TestUserLogin
suite1 = unittest.makeSuite(TestUserLogin, 'test_user_login_normal') # 使用測試類的單條用例制作測試集
suite2 = unittest.makeSuite(TestUserLogin) # 使用整個測試類制作測試集合(包含該測試類所有用例)
unittest.TextTestRunner(verbosity=2).run(suite1)
- 使用TestLoader(用例加載器)生成測試集
improt unittest
from test_user_login import TestUserLogin
suite = unittest.TestLoader().loadTestsFromTestCase(TestUserLogin) # 加載該測試類所有用例并生成測試集
unittest.TextTestRunner(verbosity=2).run(suite)
- 使用discover(用例發現)周遊所有的用例
import unittest
suite = unittest.defaultTestLoader.discover("./") # 周遊目前目錄及子包中所有test_*.py中所有unittest用例
unittest.TextTestRunner(verbosity=2).run(suite)
注意:
- 子目錄中需要包含
檔案,及應為的Python包__init__.py
- 所有用例因為test_*.py,包含測試類應以Test開頭,并繼承unittest.TestCase, 用例應以test_開頭
- 測試集嵌套
import unittest
from test_user_login import TestUserLogin
suite1 = unittest.TestSuite()
suite1.addTest(TestUserLogin('test_user_login_normal'))
suite2 = makeSuite(TestUserLogin, 'test_user_login_password_wrong')
suite = unittest.TestSuite([suite1, suite2]) # 将兩個測試集組合為一個
unittest.TextTestRunner(verbosity=2).run(suite)
生成文本報告
import unittest
suite = unittest.defaultTestLoader.discover("./")
# 輸出測試結果到文本檔案
with open("result.txt","w") as f:
unittest.TextTestRunner(stream=f,verbosity=2).run(suite) # 将輸出流stream輸出到檔案
生成HTML報告
- 下載下傳HTMLTestRunnerCN
- 解壓并将解壓包中python3x檔案夾下的HTMLTestRunnerCN.py拷貝到項目目錄
- 在目錄下建立腳本
run_all.py
import unittest
from HTMLTestReportCN import HTMLTestRunner
suite = unittest.defaultTestLoader.discover("./")
f = open("report.html", 'wb') # 二進制寫格式打開要生成的報告檔案
HTMLTestRunner(stream=f,title="Api Test",description="測試描述",runner="卡卡").run(suite)
f.close()
- 運作腳本,會在目前檔案夾下生成report.html,用浏覽器打開即可
源碼下載下傳請聯系作者