python+unittest實現當case出錯或者程序報錯時,退出driver,並重新打開driver,並執行下一條case

首先創建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個原因的,請留言解答下哈

如果大家有更加合適的解決辦法,也請留言,我覺得我這樣做雖然解決了問題,但是繞了一圈

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章