一篇文章帶你瞭解Python基礎測試工具——UnitTest
測試人員一般使用Python作為主語言指令碼來進行自動化開發,而Python自帶的UnitTest指令碼通常就是測試人員首先掌握的
那麼本篇文章我們將來介紹Python的最基本自動化工具UnitTest來開始我們自動化的第一步
我們這篇文章將從以下角度進行講解:
-
UnitTest基本介紹
-
UnitTest組成部分
-
UnitTest進階部分
UnitTest基本介紹
首先我們將會對自動化和UnitTest進行一個基本的介紹
自動化指令碼介紹
首先我們需要知道什麼叫做自動化指令碼:
- 採用程式碼的形式書寫專案,採用專案執行的方式來代替人工測試
自動化指令碼的作用主要有以下幾點:
- 解放人力資源,杜絕人為性錯誤,加快測試速度
- 使沒有程式碼基本的人可以藉助工具快速上手進行自動化測試
UnitTest基本介紹
下面我們來簡單介紹一下UnitTest:
- UnitTest是Python自帶的一款單元測試框架,無需安裝即寫即用
我們來針對上面幾個詞彙進行講解:
# 自帶框架(官方框架)和第三方框架
# 自帶框架:跟隨Python官方一共上架的框架,只需要下載Python就可以直接使用,例如Python的UnitTest,OS,requests等
# 第三方框架:由非官方開發人員所開發的可以在Python上匯入並使用的框架,例如我們後來會學到的pytest,httpRunner等
# 單元測試
# 單元測試一般是隻單單針對後端功能進行測試,而自測環節通常是由後端進行的
# 但是我們測試人員也可以在後端開發期間或前後端聯調階段去進行單元測試以確定後端功能來減少提測時的錯誤率
最後我們簡單介紹一下UnitTest的優點:
- 能夠組織多個⽤例去執⾏
- 提供豐富的斷⾔⽅法
- 能夠⽣成測試報告
UnitTest組成部分
下面我們來正式學習UnitTest的組成部分,來真正學習這個框架
UnitTest組成概述
我們首先在這裡簡單介紹一下UnitTest的整體組成部分:
# TestCase
# 簡稱:測試用例(核心模組)
# TestCase是UnitTest裡面的一個類名,我們需要建立一個類去繼承該TestCase
# 然後我們需要在該型別中書寫測試方法def,後續我們就可以直接去執行該TestCase中的Case或採用其他方法集中執行
# TestSuite
# 簡稱:測試套件
# 正常來說我們的TestCase只能在當前類中去進行執行,如果我們的Case劃分寫在不同TestCase下,那麼我們就不能一同執行
# 但是我們可以採用TestSuite將某個TestCase中的Case加入到該TestSuite中,然後採用執行器一同執行
# TestLoader
# 簡稱:測試載入器
# TestLoader其實和TestSuite是一樣的作用
# 但不同的是TestSuite一次只能新增一個TestCase中的一個Case或一個Testcase的所有Case
# 但是TestLoader可以根據匹配定理來一次性新增多個同一資料夾下的Testcase來實現快速新增的功能
# TestRunner
# 簡稱:測試執行器
# TestRunner的主要功能就是為了執行TestSuite和TestLoader從而獲得對應的測試資料
# Fixture
# 簡稱:測試夾具
# Fixture的主要功能就是在測試方法執行的前後去新增一些前置條件,比如開啟瀏覽器,開啟某個網頁等
# Fixture主要被劃分為三個級別:檔案級別,類級別,方法級別,我們都在下面一一講述的
TestCase
首先我們來學習UnitTest的核心點TestCase:
# 首先我們來概述TestCase
# TestCase就是一個類,一個需要被我們所繼承的類
# TestCase的使用我們下面將採用一個用例來講解
# 首先我們如果想要TestCase,我們需要匯入UnitTest
import unittest
# 然後我們需要自定義一個類,這個類需要繼承Testcase
class TestDemo(unittest.TestCase):
# 下面我們在該類中所書寫的每個方法都會變成一個case,在執行過程中被我們所執行
# 此外我們還需要注意:我們的每個方法都必須以test_開頭,否則系統不會將他判斷為case
def test_method1(self):
print('測試⽅法 1')
def test_method1(self):
print('測試⽅法 2')
# 正常來說我們的TestCase是可以單獨去執行的
# 如果我們想執行該TestCase下的所有方法,我們可以將游標對準Class行,右鍵run執行,從而獲得結果
# 如果我們想執行該TestCase下的某個方法,我們可以將游標對準該def行,右鍵run執行,從而獲得結果
TestRunner&TestSuite&TestLoader
下面我們來一起介紹這三個元件,因為他們之間有非常緊密的聯絡:
# 首先我們先來介紹TestRunner
# 該類就是一個單純的執行機器,我們只需要建立對應的實體,然後呼叫該實體的run方法來執行對應的Case元件即可
# 我們先來介紹TestSuite
# 首先我們需要匯入unittest
import unittest
# 我們需要注意,如果我們的TestSuite引用到了其他TestCase類的case方法,那麼我們需要手動import匯入對應類
# 當然這裡推薦alt+enter自動匯入
from testcase1 import TestDemo1
from testcase2 import TestDemo2
# 然後下面的操作我們可以單獨列在一個main方法中,也可以直接書寫
if __name__ == '__main__':
# 首先我們建立一個TestSuite
suite = unittest.TestSuite()
# 然後我們需要採用一種格式來新增case到該suite中:suite.addTest(類名('方法名'))
suite.addTest(TestDemo1('test_method1'))
suite.addTest(TestDemo1('test_method2'))
suite.addTest(TestDemo2('test_method1'))
suite.addTest(TestDemo2('test_method2'))
# 當然我們的suite也是存在一種快速匯入一個TestCase的所有Case的方法:suite.addTest(unittest.makeSuite(類名))
# 但是我們需要注意makeSuite方法是沒有提示資訊的,因為該方法其實是unittest之前版本就想要刪除的方法,但是推薦使用
suite.addTest(unittest.makeSuite(TestDemo1))
# 最後我們建立一個TestRunner去執行
runner = unittest.TestRunner()
runner.run(suite)
# 下面我們來介紹TestLoader
# 首先我們需要匯入unittest
import unittest
# 然後下面的操作我們可以單獨列在一個main方法中,也可以直接書寫
if __name__ == '__main__':
# 同理我們需要建立一個TestLoader物件,當然我們也可以直接在建立過程中使用方法然後直接返回TestLoader物件
# TestLoader有這麼一個方法:unittest.TestLoader().discover('用例所在的路徑', '用例的程式碼檔名')
# 該用例的程式碼檔名是可以支援*作為任意字元數,也可以使用?來代替單個字元數
suite = unittest.TestLoader().discover('./case', '*case*.py')
# 同理我們需要一個TestRunner來執行run方法,當然也可以化作一步來執行
unittest.TestRunner().run(suite)
Fixture
最後我們來介紹一下Fixture測試夾具:
# 首先我們需要知道Fixture測試夾具的主要用途就是做一些前置條件或者結束條件,它會在某些特定條件下執行
# 首先是方法級別的Fixture測試夾具
# 它是在每個測試方法(用例程式碼) 執行前後都會自動呼叫的結構
# 方法執行之前
def setUp(self):
每個測試方法執行之前都會執行
pass
# 方法執行之後
def tearDown(self):
每個測試方法執行之後都會執行
pass
# 然後是針對類級別的Fixture測試夾具
# 它是在每個測試類中所有方法執行前後 都會自動呼叫的結構(在整個類中執行之前或之後執行一次)
# 需要注意:類級別的Fixture 方法, 是一個 類方法
# 類中所有方法之前
@classmethod
def setUpClass(cls):
pass
# 類中所有方法之後
@classmethod
def tearDownClass(cls):
pass
# 最後是針對模組級別的Fixture測試夾具
# 在每個程式碼檔案執行前後執行的程式碼結構
# 需要注意:模組級別的需要寫在類的外邊直接定義函式即可
# 程式碼檔案之前
def setUpModule():
pass
# 程式碼檔案之後
def tearDownModule():
pass
# 下面我們採用一個使用者賬戶登入的用例來簡單展示一下Fixture
import unittest
# 這些Fixture並沒有必要一次性全部書寫執行,只需要書寫自己所需要的部分即可
# 例如下述的這個Moudle層級的Fixture其實是沒有必要執行的,我們可以直接將他省略掉
"""""
# 程式碼檔案之前
def setUpModule():
print("檔案執行前")
# 程式碼檔案之後
def tearDownModule():
print("檔案執行後")
"""""
class TestLogin(unittest.TestCase):
# 在執行該類前所需要呼叫的方法
@classmethod
def setUpClass(cls) -> None:
print('------開啟瀏覽器')
# 在執行該類後所需要呼叫的方法
@classmethod
def tearDownClass(cls) -> None:
print('------關閉瀏覽器')
# 每個測試方法執行之前都會先呼叫的方法
def setUp(self):
print('輸入網址......')
# 每個測試方法執行之後都會呼叫的方法
def tearDown(self) -> None:
print('關閉當前頁面......')
# 測試Case1
def test_1(self):
print('輸入正確使用者名稱密碼驗證碼,點選登入 1')
# 測試Case2
def test_2(self):
print('輸入錯誤使用者名稱密碼驗證碼,點選登入 2')
UnitTest進階部分
最後我們介紹UnitTest中的一些進階知識點
斷言
既然講到Python,那麼斷言必然是我們所需要了解的部分:
- 斷言就是為了讓程式代替人工自動的判斷預期結果和實際結果是否相符
斷言的結果只分為兩種:
- True:用例透過
- False:用例不透過,並且丟擲異常
我們在這裡給出Python中所有經常使用到斷言語句:
斷言語法 | 斷言意義 |
---|---|
assertEqual(預期結果,實際結果) | 判斷是否相等 |
assertNotEqual(預期結果,實際結果) | 判斷是否不等 |
assertTrue(實際結果) | 判斷是否為True |
assertFalse(實際結果) | 判斷是否為False |
assertIsNone (實際結果) | 判斷是否為None |
assertIsNotNone(實際結果) | 判斷是否不為None |
assertIn(預期結果,實際結果) | 判斷是否包含 |
我們給出一個簡單的案例來展示斷言功能:
# 首先我們需要知道斷言其實就是一個判斷語句,用來判斷assert後面跟隨的內容是否為True
# 例如我們簡單的計算或比較其實也是一個斷言,不過上面的斷言語句是我們封裝後的結果
# 我們需要注意:如果我們使用到上面封裝的assert方法,那麼我們需要採用self進行呼叫
import unittest
from tools import login
class TestLogin(unittest.TestCase):
# 其實下面的assert就是一個最基本的斷言判斷
def test_username_password_assert_ok(self):
res = login('admin', '123456')
assert res == "登入成功"
# 判斷登入:正確的使用者名稱和密碼: admin, 123456, 登入成功
def test_username_password_ok(self):
self.assertEqual('登入成功', login('admin', '123456'))
# 判斷登入:錯誤的使用者名稱: root, 123456, 登入失敗
def test_username_error(self):
self.assertEqual('登入失敗', login('root', '123456'))
# 判斷登入結果是否包含“失敗”,若包含即為成功
def test_username_password_error(self):
self.assertIn('失敗', login('aaa', '123123'))
引數化
下面我們來介紹UnitTest中的引數化:
-
在測試方法中, 使用變數來代替具體的測試資料, 然後使用傳參的方法將測試資料傳遞給方法的變數
-
用於減少程式碼的重複書寫,減少我們的複雜操作
我們在工作中一般會將資料存放在JSON檔案中或者從網頁中直接匯出對應的JSON檔案
下面我們來介紹引數化的兩種方法:
# unittest 框架本身是不支援 引數化, 想要使用引數化,需要安裝外掛來完成
# console控制檯下載:pip install parameterized
# 引數化方法1
import unittest
from parameterized import parameterized
# 首先我們的引數可以存在放類中
# 我們一般採用列表中存在元組或列表來進行儲存:[(),(),()]或[[],[],[]]
data = [
('admin','123456','登陸成功'),
('admin','123123','登陸失敗'),
('errorAdmin','123456','登陸失敗'),
]
# 然後我們就可以定義引數化直接呼叫該data
class TestLogin(unittest.TestCase):
# 採用註解進行注入
@parameterized.expand(data)
def test_login(self,username,password,expect):
self.assertEqual(expect, login(username, password))
# 引數化方法2
# 首先我們需要單獨建立一個JSON型別的檔案進行資料儲存
[
{
"desc": "正確的使用者名稱和密碼",
"username": "admin",
"password": "123456",
"expect": "登入成功"
},
{
"desc": "錯誤的的使用者名稱",
"username": "root",
"password": "123456",
"expect": "登入失敗"
},
{
"desc": "錯誤的的密碼",
"username": "admin",
"password": "123123",
"expect": "登入失敗"
}
]
# 然後我們就可以到TestCase中去書寫引數化
# 首先我們需要匯入該Json檔案以及相關類
import json
import unittest
from parameterized import parameterized
from tools import login
# 然後我們需要有一個單獨的方法來獲取json檔案並返回一個類似於我們前面所定義的date:
def build_data():
with open('data.json', encoding='utf-8') as f:
result = json.load(f) # [{}, {}, {}]
data = []
for i in result: # i {}
data.append((i.get('username'), i.get('password'), i.get('expect')))
return data
# 剩下的部分其實就和引數化1的操作相同的
class TestLogin(unittest.TestCase):
# 採用註解進行注入
@parameterized.expand(build_data())
def test_login(self,username,password,expect):
self.assertEqual(expect, login(username, password))
跳過測試
我們在前面的TestSuite或TestLoader裡面一次性獲得所有Case並執行,但有時我們也許不需要執行某些Case:
- 對於一些未完成的或者不滿足測試條件的測試函式和測試類, 不想執行,可以使用跳過
我們下面直接來介紹相關語法:
# 關於跳過測試其實就是一條註解我們就可以實現
# 其具體語法如下
# 直接將測試函式標記成跳過
@unittest.skip('跳過原因')
# 根據條件判斷測試函式是否跳過 , 判斷條件成立, 跳過
@unittest.skipIf(判斷條件, '跳過原因')
# 我們給出一條簡單的案例
import unittest
money = 29
class TestDemo(unittest.TestCase):
@unittest.skip('無跳過原因')
def test_1(self):
print('跳過測試')
@unittest.skipIf(version >= 30, '剩餘金錢不足,無法購買')
def test_2(self):
money -= 30
print('剩餘金錢足夠,可以購買')
def test_3(self):
print('可執行方法')
結束語
這篇文章中詳細介紹了Python自帶的測試框架,因為是自帶框架,內容相對比較簡單,後續我們會學習pytest來加強自動化開發能力
附錄
下面給出我學習和書寫該篇文章的一些參考文章,大家也可以去查閱: