學習筆記-unittest
筆記目錄:https://blog.csdn.net/weixin_42717928/article/details/114343085
昨天下了雨,今天刮了風,明天太陽就出來了
- 簡介
- 概念
- 斷言
- 其他相關
- web自動化小案例
一:簡介
單元測試适合白盒或者開發人員做。
單元測試架構可以用來寫測試用例,也适合做Web自動化測試、App自動化測試、接口自動化測試等。
Python中有doctest、unittest、pytest、nose等單元測試架構。
(1)什麼是單元測試
本質是通過一段代碼去驗證另一段代碼,是以不要單元測試架構也可以寫單元測試。
class Calculation:
""" 完成加法計算 """
def __init__(self, a, b):
self.a = int(a)
self.b = int(b)
# 加法
def add(self):
return self.a + self.b
from test.test2 import Calculation
# 測試加法
def test_add():
c = Calculation(1, 1)
rs = c.add()
# 這裡我特意斷言失敗
assert rs == 3, '加法執行錯誤'
if __name__ == '__main__':
test_add()
但這種方法有問題:1是要自己定義失敗的提示,2是如果某個測試函數執行失敗,後面的測試函數不再執行,3是無法統計結果(編寫更多代碼可以解決,但偏離測試的初衷)
使用unittest編寫測試用例:
import unittest
from test.test2 import Calculation
class TestCalculation(unittest.TestCase):
def test_add(self):
c = Calculation(1, 1)
rs = c.add()
self.assertEqual(rs, 3)
if __name__ == '__main__':
unittest.main()
這裡有個坑,就是比如我想使用unittest運作單個用例,滑鼠在方法名右鍵執行就行,運作整個的,就在main那裡右鍵執行。(這種方式運作是如python tests那樣,因為使用了unittest,預設是使用這種方式運作)
但是這種方式是沒有測試報告的,必須這樣(如果不知道路徑,搞個簡單的輸出檔案複制一下就行)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB9UerpnT4NmaNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5ATN0ITO1cTM2ETNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這樣輸出就有對應的結果了(也可以吧python tests的删除了,滑鼠在main後,就能看到test的run了)
這裡結果的含義:
- .:表示通過
- E:表示運作錯誤
- s:表示運作跳過
- F:表示運作失敗
二:概念
Test Case:最小的測試單元,Unittest提供了TestCase用于建立測試用例
Test Suite:測試套件,用于組裝一組要運作的測試,Unittest提供了TestSuite用于建立測試套件
Test Runner:用于協調測試的執行并向使用者提供結果,Unittest提供了TextTestRunner用于運作測試用例,提供HTMLTestRunner用于生成HTML測試報告
Test Fixture:代表執行一個或多個測試所需的環境準備,以及關聯的清理動作。如setUp、tearDown
import unittest
from test.test2 import Calculation
# 必須繼承unittest子產品的TestCase類
class TestCalculation(unittest.TestCase):
# 測試用例前置動作
def setUp(self):
print("測試開始")
# 測試用例後置動作
def tearDown(self):
print("測試結束")
# 測試方法以test開頭
def test_add(self):
print("第一條測試用例")
c = Calculation(1, 1)
rs = c.add()
# 由于繼承了TestCase類,可以通過self直接調用斷言方法
self.assertEqual(rs, 3)
def test_sub(self):
print("第二條測試用例")
c = Calculation(1, 1)
rs = c.sub()
# 由于繼承了TestCase類,可以通過self直接調用斷言方法
self.assertEqual(rs, 0)
if __name__ == '__main__':
# 抛棄了這個方法
# unittest.main()
# 建立測試套件
suit = unittest.TestSuite()
suit.addTest(TestCalculation("test_add"))
suit.addTest(TestCalculation("test_sub"))
# 建立測試運作器
runner = unittest.TextTestRunner()
runner.run(suit)
好處是可以控制執行順序,還有需要運作的用例。
三:斷言
方法 | 檢查 | 版本 |
assertEqual(a,b) | a==b | |
assertNotEqual(a,b) | a!=b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a,b) | a is b | 3.1 |
assertIsNot(a,b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a,b) | a in b | 3.1 |
assertNotIn(a,b) | a not in b | 3.1 |
assertIsInstance(a,b) | Isinstance(a,b) | 3.1 |
assertNotIsInstance(a,b) | Not Isinstance(a,b) | 3.1 |
可以通過這種方式推出斷言的使用:
import unittest
class TestAssert(unittest.TestCase):
def test_equal(self):
self.assertEqual(1+1, 2)
if __name__ == '__main__':
unittest.main()
四:其他相關
(1)一個測試類對應一個被測試功能,如加法類,裡面有很多方法,如整數相加、小數相加等
格式:
unittest中的TestLoader類提供了discover()方法可以從多個檔案中查找測試用例,
不需要建立該類執行個體,unittest提供了可共享的defaultTestLoader類,可以使用其子類或方法建立執行個體,再調用discover方法
import unittest
# 測試用例目錄
test_dir = './test'
# 測試用例目錄、比對以test開頭的多個檔案、還有一個參數是top_level_dir=None:測試子產品的頂層目錄,若沒有則預設是None
suits = unittest.defaultTestLoader.discover(test_dir, pattern='test*.py')
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suits)
(2)測試用例的執行順序
測試用例的執行順序涉及多個層級:多個測試目錄>多個測試檔案>多個測試類>多個測試方法(測試用例)
unittest預設根據ASCII碼的順序加載測試用例(0~9,A~Z,a~z),這是main()方法執行的規律
discover()方法和main()一樣,對于測試目錄、測試檔案同樣适用
也可以通過測試套件TestSuite類的addTest()方法按照順序來加載執行
(3)執行多級目錄的測試用例
多個目錄,如果discover()方法中的start_dir參數定義為“./test”目錄,為了能查到更下層的目錄,可以在每個子目錄放一個__init__.py檔案。作用是将一個目錄記為一個标準的python子產品。
(4)跳過測試和預期失敗
不僅僅可以作用于方法,類也可以,下面是作用于方法
import unittest
class MyTest(unittest.TestCase):
# 無條件跳過,需要說明跳過測試的原因
@unittest.skip("直接跳過測試")
def test_skip(self):
print("1")
@unittest.skipIf(2 > 1, "條件為真時跳過測試")
def test_skip_if(self):
print("2")
@unittest.skipUnless(2 > 1, "條件為真時執行")
def test_skip_unless(self):
print("3")
# 不管執行結果是否失敗,都将測試标為失敗,但不會抛出失敗資訊(這裡我看結果的符号是u)
@unittest.expectedFailure
def test_expected_failure(self):
print("4")
if __name__ == '__main__':
unittest.main()
(5)Fixture
夾心餅幹的餅幹,setIp和tearDown,還有更大範圍的,如測試類和子產品的fIxture
import unittest
# setUpModule和tearDownModule在整個子產品的開始與結束執行
def setUpModule():
print("1")
def tearDownModule():
print("1-end")
class MyTest(unittest.TestCase):
# setUpClass和tearDownClass在測試類的開始和結束執行
# 都是類方法,使用classmethod裝飾,cls可以了解為和self一樣的表示第一個參數的意思
@classmethod
def setUpClass(cls):
print("2")
@classmethod
def tearDownClass(cls):
print("2-end")
# setUp和tearDown在測試用例的開始和結束執行,是以先2-測試1-2-end,再2-測試2-2-end
def setUp(self):
print("3")
def tearDown(self):
print("3-end")
def test_case1(self):
print("測試1")
def test_case2(self):
print("測試2")
if __name__ == '__main__':
unittest.main()
五:web自動化小案例
import unittest
from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestBaiDu(unittest.TestCase):
# 使用這個比setUp好,不用每執行一次用例就重新開機和關閉一次浏覽器,提高效率
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
cls.test_url = "https://www.baidu.com"
# 封裝重複代碼(不是test開頭,不會當成用例去執行)
def baidu_search(self, search_key):
self.driver.get(self.test_url)
self.driver.find_element(By.ID, "kw").send_keys(search_key)
self.driver.find_element(By.ID, "su").click()
sleep(2)
# 用例1
def test_search_haha(self):
search_key = "haha"
self.baidu_search(search_key)
title = self.driver.title
self.assertEqual(title, search_key+"_百度搜尋")
# 用例2
def test_search_hehe(self):
search_key = "hehe"
self.baidu_search(search_key)
title = self.driver.title
self.assertEqual(title, search_key+"_百度搜尋")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main()