首先創建case_set.py文件繼承 unittest.TestCase 類,
case_set.py
class MyTest(unittest.TestCase):
# 整個Test類的開始和結束執行
driver = None
@classmethod
def setUpClass(cls):
print("test class start =======>")
cls.hp = help()
cls.driver = cls.hp.run()
@classmethod
def tearDownClass(cls):
print("test class end =======>")
cls.driver.quit()
def setUp(self):
pass
def tearDown(self):
pass
在MyTest類中實現setUpClass,tearDownClass,用於整個Test類的開始和結束執行,
在setUpClass中實現啓動app driver並獲得driver,在tearDownClass中實現當一個Test類執行完畢後退出driver,然後後面寫的每一個Test類(具體的case)都繼承自MyTest。
起初的想法是這樣做的,好處很明顯,就是不用每次執行case都重啓driver,節省了大量的時間。
但是這樣做有2個很明顯的問題,第一個就是用例的耦合度變高了,case與case之間必須有上下級關聯關係
還有一個致命問題:比如登錄頁面和登錄成功後的認證頁面,如果上一條登錄case執行失敗或者程序報錯後,會停留在當前的登錄頁面,繼續執行下一條認證的case的時候,因爲登錄case失敗,所以認證的case會因爲找不到元素報錯。這樣就導致一旦某一條case執行失敗,那麼所有的case都會執行失敗
第一個問題好解決,有上下級關聯就有關聯,因爲有的case與case之間也是存在上下級關聯的,
第二個問題目前的想法就是,一旦某一條case執行失敗或者報錯,那麼就直接退出driver,重新打開driver,並在執行下一條case的時候設置前置的操作,比如登錄頁面的case執行失敗後,執行下一條認證的case的時候,首先判斷是否需要前置操作(其實就是判斷app是否重新打開過了,可以通過獲取頁面元素來判斷),如果重開了,就需要就執行前置操作登錄操作。這樣就完美解決了目前的問題,以後所有的case都寫一些前置的操作
那麼如何來判斷程序是如何出錯的呢?怎麼獲得異常?這裏我參考了一下別人的博客內容
https://www.cnblogs.com/ywhyme/p/10657345.html 感謝大神分享,
完整的實現方法如下
import contextlib
import sys
import unittest
from help.db_help import help
from unittest.case import SkipTest, _ShouldStop, _Outcome
from help.is_open_driver import Singleton
@contextlib.contextmanager
def testPartExecutor(self, test_case, isTest=False):
old_success = self.success
self.success = True
try:
yield
except Exception:
try:
# if error
getattr(test_case, test_case._testMethodName).__func__._error = True
raise
except KeyboardInterrupt:
raise
except SkipTest as e:
self.success = False
self.skipped.append((test_case, str(e)))
except _ShouldStop:
pass
except:
exc_info = sys.exc_info()
if self.expecting_failure:
self.expectedFailure = exc_info
else:
self.success = False
self.errors.append((test_case, exc_info))
# explicitly break a reference cycle:
# exc_info -> frame -> exc_info
exc_info = None
else:
if self.result_supports_subtests and self.success:
self.errors.append((test_case, None))
finally:
self.success = self.success and old_success
_Outcome.testPartExecutor = testPartExecutor
# 整個文件的開始和結束執行
def setUpModule():
print("test module start >>>>>>>>>>>>>>")
pass
def tearDownModule():
print("test module end >>>>>>>>>>>>>>")
pass
class MyTest(unittest.TestCase):
# 整個Test類的開始和結束執行
driver = None
@classmethod
def setUpClass(cls):
print("test class start =======>")
cls.hp = help()
cls.driver = cls.hp.run()
@classmethod
def tearDownClass(cls):
print("test class end =======>")
cls.driver.quit()
pass
def setUp(self):
if(Singleton().is_need_open_driver):
self.setUpClass()
Singleton().change_not_need_open_driver()
def tearDown(self):
#如果用例執行失敗,則退出driver
if hasattr(getattr(self, self._testMethodName), "_error"):
# dosomething 可以進行比如打印日誌,截圖,等等操作
self.driver.quit()
Singleton().change_need_open_driver()
pass
當程序出錯或者case失敗時,則一定會進入tearDown方法中,這裏實現的就是判斷是否case執行成功,(ps:查看源碼後發現每次case執行失敗後會清楚掉erroes,所以不會出現下一個case運行成功時tearDown方法的錯誤判斷)
如果有錯誤則執行driver的退出操作,並設置標註狀態需要重新打開driver,Singleton類是單例模式來實現的,用來保存is_need_open_driver屬性的值,
實現邏輯
#單例模式,每次初始化的時候,不改變類中屬性的值,沿用同一套屬性值
class Singleton(object):
instance = None
init_flag = False
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
if Singleton.init_flag:
return
Singleton.init_flag = True
#添加是否需要重新啓動driver的判斷
self.is_need_open_driver = False
#改變是否需要重新啓動driver的判斷的值
def change_need_open_driver(self):
self.is_need_open_driver = True
def change_not_need_open_driver(self):
self.is_need_open_driver = False
爲什麼is_need_open_driver這個屬性值不直接寫在MyTest類中呢?而是通過單例模式來保存下來?
因爲我發現如果直接寫在MyTest類中,上一條case執行失敗,在執行下一條case的時候,即使在tearDown方法中改變了該屬性的值,但是在setUp重新去獲取該值時,還是獲取的改變前的值,很奇怪,不曉得是什麼情況導致的,我感覺也許是在執行下一條case的時候重新初始化了該MyTest類導致的,不曉得是不是這個原因,所以就想到了單例模式。有知道原因的請留言哈。。
最後,最最重要的是在setUp中重新啓動driver,千萬不要在setUp方法中重新去打開一個driver,否則程序執行會報錯,正確的做法是需要重新調用實現setUpClass()方法
因爲setUpClass()該方法的實現是整個Test類的執行開始和結束,Test類中的case未全部執行完成時,如果你在setUp方法中重新去打開一個driver,即使你重新賦值driver也不能覆蓋原來的driver屬性(雖然我也不曉得是個什麼原因),那麼你這次執行的case其實就是使用的setUp中的driver,但是你的下下條case,使用的卻是setUpClass()方法中的driver,此時setUpClass()中的driver因爲上個異常case關閉了,一定會報錯。
所以這裏只能重新調用setUpClass()中的方法,並重新覆蓋driver。
最後附上測試後的case截圖,如果有知道上面我說的2個原因的,請留言解答下哈
如果大家有更加合適的解決辦法,也請留言,我覺得我這樣做雖然解決了問題,但是繞了一圈