深刻解讀Python的unittest並拓展HTMLTestRunner

unnitest是Python的一個重要的單元測試框架,對於用Python進行開發的同事們可能不須要對他有過深刻的瞭解會用就行,可是,對於自動化測試人員我以爲是要熟知unnitest的執行原理以及相關模塊的做用。我這邊提幾個簡單的需求以下:javascript

1.如何利用unnitest執行流程測試而非單元測試。好比咱們可能利用selenium+unnitest來跑一段流程,好比test1裏面咱們實現登錄,test2在test1成功登錄的基礎上,實現一個查詢的測試,test3咱們查詢一些數據後,頁面選擇性提交數據。這個你總不能在test1裏面登錄完而後關閉瀏覽器;在test2裏面再登錄再執行查詢後關閉瀏覽器;test3再登錄執行提交數據。你或者能夠這樣想把這幾個步驟寫在一個test裏面。可是,若是流程過長怎麼辦?本身難道感受不到剪不斷理還亂的糾結嗎....css

2.如何控制unnitest的執行順序。unnitest裏面tests數組裏面存放的TestCase默認是以首字母排序的,這對於test1,test2....test9這樣的執行順序是沒用問題的,可是對於多個test好比test1,test2.........test18,這樣unnitest可不是按照這個順序,我說了是按照首字母排序來的,他會這樣執行test1,test10,....test18,test2,..........test9,固然我說的這些對於單元測試是影響不大的(除非各個test之間有數據依賴關係,後面提到),對於流程多是顛覆性的。html

3.流程測試中如何動態的控制是否跳過某個test的執行對於流程來講這也是常見的一種想法,好比test1我連登錄都沒有成功,還有意義執行後面的test嗎,以後報出來的都是一些NosuchElement的錯誤,這些錯誤沒有任何意義,並且純粹浪費時間....出來的報告也是不"人性的"。那麼咱們如今一個好的想法是:若是test1沒有執行成功,後面的test能動態所有跳過,其實不止是test1沒成功,後面test跳過,準確的說是,test1,test2........testN中若是任意某個test沒有經過,後面的能動態的所有跳過。固然,若是後面的test和前面test沒什麼關係,也可能選擇無論前面是否成功均不跳過;也能夠是隻和test1登陸有關,只要登陸成功了我就不跳過,若是登陸不成功我就跳過...還有不少.....更重要的是,報告中有所展示,不能說skip了某個用例,你報告就不顯示了,這樣老闆認爲你偷懶,用例寫這麼少? 你還要瑟瑟發抖的去解釋,是由於前面的用例沒經過因此,沒顯示了...
咱們高大上的是這樣的,test1執行失敗了,test2.......testN,報告中都體現,標註是skip的case,並且點開還有緣由解釋:"test1沒有執行成功,因此跳過此case"。這就是我拓展HTMLTestRunner的緣由,後面逐行解釋若是拓展它。前端

.......還有不少java

補充一下,爲何往流程上扯呢,由於公司的模塊太多,並且複雜,單元測試機會不太會用,只要保證各個業務的主流程沒問題便可,可是不影響咱們解析unnitest。python

咱們的想法不少,可是如何來實現呢?那就讓咱們來深刻探討下python的unnitest吧!web

關於unnitest看似複雜我給出來就是unnitest=TestCase+TestResult,只要熟知這2個模塊,你就能"隨心所欲"!!可能有人說不對不是有什麼TestSuite嗎還有TextTestRunner等等嗎,不錯確實咱們平時用到的大可能是這些模塊,可是,到其實最終執行的是TestCase中的run方法,並把結果給TestResult(或它的子類)。咱們先來看一個簡單的unnitest例子,並以此來拓展!例子以下:apache

import unittest
class Mydemo(unittest.TestCase):
    def setUp(self):
        self.a=1
    def test1(self):
        print "i am test1 the value of a is {}".format(self.a)
    def test2(self):
        print "i am test2 the value of a is {}".format(self.a)
    def  test3(self):
        print "i am test3 the value of a is {}".format(self.a)
if __name__ == '__main__':
    unittest.main()

運行結果以下:數組

i am test1 the value of a is 1
...
i am test2 the value of a is 1
i am test3 the value of a is 1
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

這個是沒有問題的,那麼咱們可能要想這個unnitest.main()是什麼東西,還有其餘的寫法來執行嗎,能只執行test1,test2,不執行test3嗎(暫時不用skip)?那麼咱們從unittest.main()看起來。debug進入其實最終執行的是TestProgram這類,貼出構造函數部分代碼:瀏覽器

if argv is None:
            argv = sys.argv#獲得當前模塊的絕對路徑

        self.exit = exit
        self.failfast = failfast
        self.catchbreak = catchbreak
        self.verbosity = verbosity
        self.buffer = buffer
        self.defaultTest = defaultTest
        self.testRunner = testRunner
        self.testLoader = testLoader
        self.progName = os.path.basename(argv[0])
        self.parseArgs(argv)#查找當前module的Testsuite
        self.runTests()#執行測試

好了,從上面咱們能夠看出來其實也就2個主要的步驟就是第一:找出要測試的testcase,並加入到Testsuite,第二:運行Testsuite並把結果給TestResult。

首先,第一:瞭解什麼是TestCase?什麼是TestSuite?第二:若是找出這些Testcase,或者TestSuite?

什麼是TestCase?

有人說TesetCase就是以test開頭的就叫一個testcase,我只能這樣說太偏面的,準確的說:是實例了一個TesetCase類的叫一個TestCase,好比這樣:

import unittest
class Mydemo(unittest.TestCase):
    def setUp(self):
        self.a=1
    def Mytest1(self):
        print "i am Mytest1 the value of a is {}".format(self.a)
    def Mytest2(self):
        print "i am Mytest2 the value of a is {}".format(self.a)
    def Mytest3(self):
        print "i am Mytest3 the value of a is {}".format(self.a)
if __name__ == '__main__':
    test_runner=unittest.TextTestRunner()
    test_suit=unittest.TestSuite()
    test_suit.addTests(map(Mydemo,["Mytest1","Mytest2","Mytest3"]))
    test_runner.run(test_suit)

運行結果以下:

...
i am Mytest1 the value of a is 1
----------------------------------------------------------------------
i am Mytest2 the value of a is 1
Ran 3 tests in 0.000s
i am Mytest3 the value of a is 1

OK

 上面3個Testcase可並無以test開頭...那麼爲何你們都要默認以test開頭來寫呢,咱們打開C:\Python27\Lib\unittest\loader.py這個模塊在296行有寫defaultTestLoader = TestLoader(),咱們來看看TestLoader這個類第一行就看見testMethodPrefix = 'test',也就是說若是你使用到defaultTestLoader,那麼默認是以test開頭的方法爲一個用例,具體能夠在TestLoader類中的getTestCaseNames獲得實現,紅字註釋部分爲何testCaseClass要有__call__方法,咱們後面提到。(不知道__call__這個魔法屬性的用法自行百度)

def getTestCaseNames(self, testCaseClass):
        """Return a sorted sequence of method names found within testCaseClass
        """
        def isTestMethod(attrname, testCaseClass=testCaseClass,
                         prefix=self.testMethodPrefix):
            return attrname.startswith(prefix) and \
                hasattr(getattr(testCaseClass, attrname), '__call__')#返回一個testCaseClass有__call__方法且attrname以prefix開頭的爲一個testcase
        testFnNames = filter(isTestMethod, dir(testCaseClass))
        if self.sortTestMethodsUsing:
            testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing))
        return testFnNames

原來是這樣啊,咱們上文提到的unittest.main()其實用的就是defaultTestLoader,固然你把if __name__ == '__main__'下面的代碼換成unittest.main()確定不成功,除非你把上文提到的testMethodPrefix 換成"Mytest"。有了對TestCase的見解,咱們具體來看看這個類。

這個類裏面包含了咱們所能用的方法。我列出來一些主要的吧。

setUp()在每一個test執行前都要執行的方法。

tearDown()在每一個test執行後都要執行的方法。(不論是否執行成功)

setUpClass()在一個測試類中在全部test開始以前,執行一次且必須使用到Testsuite(只有在TestSuite的run方法裏面纔對其調用)

tearDownClass()在一個測試類中在全部test結束以後,執行一次且必須使用到Testsuite(只有在TestSuite的run方法裏面纔對其調用)

run()這是unnitest的核心,邏輯也相對複雜,可是很好理解,具體本身看源碼。全部最終case的執行都會歸結到該run方法。

還有一個重要的_resultForDoCleanups私有變量,存儲TestResult的執行結果,這個在構建後面的skip用到。

咱們要明確TestCase類中全部的測試用例是獨立的,我上面說過了,其實每一個testcase就是一個個TestCase類的實例對象,因此不要企圖在某個test存儲或改變一個變量,下個test中能用到,除非利用到setUpClass。咱們看個例子:

import unittest
class Mydemo(unittest.TestCase):
    def test1(self):
        self.a=1
        print "i am test1 the value of a is {}".format(self.a)
    def test2(self):
        print "i am test2 the value of a is {}".format(self.a)
if __name__ == '__main__':
    unittest.main()

 結果:

C:\Python27\python.exe D:/Moudle/module_1/test4.py
i am test1 the value of a is 1
.E
======================================================================
ERROR: test2 (__main__.Mydemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/Moudle/module_1/test4.py", line 7, in test2
    print "i am test2 the value of a is {}".format(self.a)
AttributeError: 'Mydemo' object has no attribute 'a'

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (errors=1)

上面就是說明TestCase類中全部的測試用例是獨立的,每一個testcase就是由TestCase實例化的一個獨立的實例。那是否是就是每一個TestCase不能共享數據呢?答案是否認的,不能共享的緣由是咱們上面用到的是self(實例對象屬性),能共享咱們就必須使用類屬性,好比下個例子:

import unittest
class Mydemo(unittest.TestCase):
    def test1(self):
        Mydemo.a=1
        print "i am test1 the value of a is {}".format(self.a)
    def test2(self):
        print "i am test2 the value of a is {}".format(Mydemo.a)
if __name__ == '__main__':
    unittest.main()

 運行結果以下:

i am test1 the value of a is 1
..
i am test2 the value of a is 1
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

 這些東西實際上是python類的一些動態行爲,可是既然和unnitest關聯,就隨便提下。咱們運行test1的時候,給Mydemo加了一個新的屬性a(值爲1),當咱們運行test2時,咱們就能拿到Mydemo類的屬性了。說了TaseCase咱們不得不說下TestSuite。TestSuite是有一個個TestCase組成的,固然TestSuite裏面能夠再嵌套TestSuite。咱們打開C:\Python27\Lib\unittest\suite.py找到TestSuite,它繼承於BaseTestSuite,其實主要的一些屬性就那麼幾個:

1.self._tests這個私有變量裏面方的是全部的TestCase或者TestSuite。

2.run()方法,方法以下:

 

def run(self, result, debug=False):
        topLevel = False
        if getattr(result, '_testRunEntered', False) is False:
            result._testRunEntered = topLevel = True

        for test in self:#這個循環會一直遍歷_tests中的變量
            if result.shouldStop:
                break
            if _isnotsuite(test):
                self._tearDownPreviousClass(test, result)
                self._handleModuleFixture(test, result)
                self._handleClassSetUp(test, result)#這一句提到了調用setUpClass的規則
                result._previousTestClass = test.__class__

                if (getattr(test.__class__, '_classSetupFailed', False) or
                    getattr(result, '_moduleSetUpFailed', False)):
                    continue

            if not debug:
                test(result)#若是是TestSuit繼續調用該方法,若是是TestCase則調用TestCase中的run方法
            else:
                test.debug()

        if topLevel:
            self._tearDownPreviousClass(None, result)
            self._handleModuleTearDown(result)
            result._testRunEntered = False
        return result

 

 註釋1:self是個迭代對象,一直遍歷上文提到的self._tests變量

註釋2:咱們看看_handleClassSetUp中的方法,發如今在用例的執行過程當中,每一個TestCase類只會調用一次setUpClass方法,同理tearDownClass。對用這一點咱們舉個例子:

import unittest
class Mydemo(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print "I am setUpClass"
    def test1(self):
        print "i am test1 "
    def test2(self):
        print "i am test2"
    @classmethod
    def tearDownClass(cls):
        print "I am tearDownClass"
if __name__ == '__main__':
    unittest.main()

運行結果是:

C:\Python27\python.exe D:/Moudle/module_1/test4.py
I am setUpClass
..
i am test1 
----------------------------------------------------------------------
i am test2
Ran 2 tests in 0.001s
I am tearDownClass

OK

說明類方法setUpClass與tearDownClass只執行了一遍了,這就回答了咱們第一個問題了:setUpClass中啓動瀏覽器,執行完全部流程後關閉瀏覽器,舉一個簡單的demo就是:

 

#coding=utf-8
import unittest
from  selenium import webdriver
class Mydemo(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.browser=webdriver.Firefox()
    def test1(self):
        '''登陸'''
        browser=self.browser
        #do someting about login
    def test2(self):
        '''查詢'''
        browser = self.browser
        # do someting about search
    def test3(self):
        '''提交數據'''
        browser = self.browser
        # do someting about submmit
    @classmethod
    def tearDownClass(cls):
        browser=self.browser
browser.close()
    
if __name__ == '__main__': unittest.main()

 

 上面就會在全部的case執行以前啓動firefox,由於每一個test中拿到的都是Mydemo類中同一個webdriver對象,因此能保證操做的都是同一個瀏覽器句柄。關於這個setUpClass若是想要動態的改變某個值必定要使用python的可變的對象好比list,dict等...這些其實都是一些python類的一些知識,算我囉嗦吧我仍是想舉個例子,嫌煩的同窗,繞過這一部分吧。

#coding=utf-8
import unittest
from  selenium import webdriver
class Mydemo(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.a=1
    def test1(self):
        print "before update the a in test1 is:{}".format(self.a)
        self.a=self.a+1
        print "after update the a in test1 is:{}".format(self.a)
    def test2(self):
        print "the value in test2 is:{}".format(self.a)
    @classmethod
    def tearDownClass(cls):
        print "I am tearDownClass"
if __name__ == '__main__':
    unittest.main()

 運行結果:

C:\Python27\python.exe D:/Moudle/module_1/test4.py
before update the a in test1 is:1
..
after update the a in test1 is:2
----------------------------------------------------------------------
the value in test2 is:1
I am tearDownClass
Ran 2 tests in 0.001s

OK

 咱們想在test1中改變a的值,可是test2中的結果說明a沒有被改變,這其實也很好理解。若是咱們想要改變怎麼辦,看看下面的例子:

#coding=utf-8
import unittest
from  selenium import webdriver
class Mydemo(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.a=[0]
    def test1(self):
        print "before update the a in test1 is:{}".format(self.a[0])
        self.a[0]=self.a[0]+1
        print "after update the a in test1 is:{}".format(self.a[0])
    def test2(self):
        print "the value in test2 is:{}".format(self.a[0])
    @classmethod
    def tearDownClass(cls):
        print "I am tearDownClass"
if __name__ == '__main__':
    unittest.main()

 運行結果:

C:\Python27\python.exe D:/Moudle/module_1/test4.py
..
before update the a in test1 is:0
----------------------------------------------------------------------
after update the a in test1 is:1
Ran 2 tests in 0.000s
the value in test2 is:1

I am tearDownClass
OK

 咱們把a變成一個list,發現a的值在test2中改變了。好了這一部分就這樣了。

註釋三:這個其實也是python類的一些知識可能有的人沒有關注就是__call__這個魔法屬性,咱們看到在這個循環中test若是是testsuite對象,那麼會調用中TestSuite類中的__call__方法(在其父類BaseTestSuite中),該方法中會再次調用run方法。一直到test是個testcase對象,那麼就會調用咱們上文提到的TestCase中的__call__(這就是咱們上面提到爲何找有__call__屬性類實例的方法),同樣該__call__中的方法也是調用TestCase中的run。因此最終全部的執行其實都是執行TestCase中的run方法。

上面大體講了一些TestCase與TestSuit的知識,可能穿插的比較多。

如何建立這些Testcase或者TestSuite?

1.本身手動實例化TestCase

這個上面已經有例子,與普通類無異,這中在自動化領域用處不大,咱們不能一個個的實例化吧...

2.利用C:\Python27\Lib\unittest\loader.py模塊的TestLoader,該類提供了多種不一樣情境find testcase。

1.loadTestsFromTestCase利用給出的TestCase類名稱返回找到全部的suite。

2.loadTestsFromMoudle利用給出的Moudle返回找到全部的suite。

3.loadTestsFromName利用給出的Moudle名稱返回找到全部的suite。

4.discover返回給定目錄下符合pattern類型(默認test*.py)全部的suite。

其實這些方法最終都要歸結到loadTestsFromTestCase,可能官方不提供咱們也能寫,既然有了就直接用吧。

通過上面的說明,我以爲你們對一TestCase,TestSuite應該有一個比較清楚的認識了,也解決了我本身的提問。問題一:咱們能夠用類方法setUpClass實現。對於問題二:咱們能夠利用TestLoader類中的方法返回suite,而後對這些suite按照本身的想法進行一些排序,而後再調用run方法。說完了TestCase咱們再說下TestResult。

什麼是TestResult?

顧名思義,testresult就是存儲測試結果的,不過經過何種方式調用run函數,最終到Testcase中的run方法時必須傳一個result(若是爲None則本身實例化一個TestResult對象)。這個result就是TestResult對象或者是其子類的對象,咱們每次執行的結果都會調用其addFailure,addSuccess,addSkip....等方法將執行結果保存到TestResult實例屬性中。咱們仍是來看看TestCase的run方法:

def run(self, result=None):
        orig_result = result
        if result is None:#若是沒有傳入result對象本身實例化一個TestResult對象
            result = self.defaultTestResult()
            startTestRun = getattr(result, 'startTestRun', None)
            if startTestRun is not None:
                startTestRun()

        self._resultForDoCleanups = result
        result.startTest(self)

        testMethod = getattr(self, self._testMethodName)
        if (getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)):
            # If the class or method was skipped.
            try:
                skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                            or getattr(testMethod, '__unittest_skip_why__', ''))
                self._addSkip(result, skip_why)#調用addSkip
            finally:
                result.stopTest(self)
            return
        try:
            success = False
            try:
                self.setUp()
            except SkipTest as e:
                self._addSkip(result, str(e))
            except KeyboardInterrupt:
                raise
            except:
                result.addError(self, sys.exc_info())#調用addError
            else:
                try:
                    testMethod()
                except KeyboardInterrupt:
                    raise
                except self.failureException:
                    result.addFailure(self, sys.exc_info())#調用addFailure
                except _ExpectedFailure as e:
                    addExpectedFailure = getattr(result, 'addExpectedFailure', None)
                    if addExpectedFailure is not None:
                        addExpectedFailure(self, e.exc_info)#調用addExpectedFailure
                    else:
                        warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
                                      RuntimeWarning)
                        result.addSuccess(self)#調用addSuccess
                except _UnexpectedSuccess:
                    addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None)
                    if addUnexpectedSuccess is not None:
                        addUnexpectedSuccess(self)
                    else:
                        warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failures",
                                      RuntimeWarning)
                        result.addFailure(self, sys.exc_info())
                except SkipTest as e:
                    self._addSkip(result, str(e))
                except:
                    result.addError(self, sys.exc_info())
                else:
                    success = True

                try:
                    self.tearDown()
                except KeyboardInterrupt:
                    raise
                except:
                    result.addError(self, sys.exc_info())
                    success = False

            cleanUpSuccess = self.doCleanups()
            success = success and cleanUpSuccess
            if success:
                result.addSuccess(self)
        finally:
            result.stopTest(self)
            if orig_result is None:
                stopTestRun = getattr(result, 'stopTestRun', None)
                if stopTestRun is not None:
                    stopTestRun()

 經過註釋部分咱們能夠看出,每次執行用例時,都會把執行結果保存到TestResult中。咱們再看看TextTestRunner這個類,在開始就使用了類TextTestResult,而這個類也是繼承TestResult,然後在執行的過程當中最終把TextTestResult實例對象傳遞給TestCase的run方法。因此我上文說了,不過你是用什麼方式執行unnitest,到最後都是TestCase的run方法與TestResult的遊戲。而咱們的HTMLTestRunner模塊也是在繼承在TestResult類的基礎上的。

說完了TestCase咱們來看看第三個問題吧,也是比較有實際意義的話題,開始我是這樣跳過某些test的,代碼是這樣的:

 

#coding=utf-8
import unittest
a=[False]
class Mydemo(unittest.TestCase):
    def test1(self):
        try:
            print "i am test1"
            #test 1 do some thing
        except Exception,e:
            a[0] = True
            raise e
    @unittest.skipIf(a[0],"test1 fail skip test2")
    def test2(self):
        try:
            print "i am test2"
            raise  AssertionError("error")
            # test2 do some thing
        except Exception,e:
            a[0] = True
            raise e
    @unittest.skipIf(a[0], "test1 fail skip test2")
    def test3(self):
        try:
            print "i am test3"
            # test2 do some thing
        except Exception, e:
            a[0] =True
            raise e
if __name__ == '__main__':
    unittest.main()

 

 想法很簡單:就是利用一個全局的數組,若是某個test執行出錯我就更改這個數組元素,到下一個case執行的時候就會判斷是否要跳過。上面由於test2出錯了,本來咱們想跳過test3,可是很遺憾並無跳過test3!結果以下:

C:\Python27\python.exe D:/Moudle/module_1/test4.py
i am test1
.F.
i am test2
======================================================================
i am test3
FAIL: test2 (__main__.Mydemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/Moudle/module_1/test4.py", line 20, in test2
    raise e
AssertionError: error

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)

緣由很簡單:python在建立Mydemo這個類的時候,因爲實例方法都使用了裝飾器unittest.skipIf,因此每一個方法都向unittest.skipIf這個裝飾傳遞傳遞參數a[0],可是這個a[0]是沒用執行過任何case以前的a[0],也就是咱們剛開始定義的a[0]=Flase,因此不可能跳過的。退一萬步講,即便這樣可行,也太不美觀了吧。咱們想的是當執行當前的test時能判斷前面是否有出錯的case,有的話就跳過了。可行嗎?我以爲可行。主要就是用到我上面提到的TestCase中的_resultForDoCleanups的變量,這個其實就是TestResult一個引用。那麼咱們能夠這樣寫:

#coding=utf-8
import unittest
class Mydemo(unittest.TestCase):
    def test1(self):
        print "excute test1"
    def test2(self):
        if self._resultForDoCleanups.failures or self._resultForDoCleanups.errors:
            raise unittest.SkipTest("{} do not excute because {} is failed".format(self._testMethodName,self._resultForDoCleanups.failures[0][0]._testMethodName))
        print "excute test2"
        raise AssertionError("test2 fail")

    def test3(self):
        if self._resultForDoCleanups.failures or self._resultForDoCleanups.errors:
            raise unittest.SkipTest("{} do not excute because {} is failed".format(self._testMethodName,self._resultForDoCleanups.failures[0][0]._testMethodName))
        print "excute test3"
if __name__ == '__main__':
    unittest.main()

 運行結果以下:

.Fs
======================================================================
FAIL: test2 (__main__.Mydemo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/Moudle/module_1/test4.py", line 10, in test2
    raise AssertionError("test2 fail")
AssertionError: test2 fail

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, skipped=1)
excute test1
excute test2

 能夠了,咱們看出,test2失敗了,test3跳過;固然test2若是正確,test3會執行。目的是達到了,但是每一個case都這樣寫不太好,咱們想到了裝飾器(不會自行百度),在C:\Python27\Lib\unittest\case.py中新增以下代碼:

def Myskip(func):
    def RebackTest(self):
        if self._resultForDoCleanups.failures or self._resultForDoCleanups.errors:
            raise unittest.SkipTest("{} do not excute because {} is failed".format(func.__name__,self._resultForDoCleanups.failures[0][0]._testMethodName))
        func(self)
    return  RebackTest

 而後C:\Python27\Lib\unittest\__init__.py中新增:

__all__ = ['TestResult', 'TestCase', 'TestSuite',
           'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main',
           'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless',
           'expectedFailure', 'TextTestResult', 'installHandler',
           'registerResult', 'removeResult', 'removeHandler','Myskip']
......
from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf,Myskip,
                   skipUnless, expectedFailure)

 最終咱們這樣寫:

#coding=utf-8
import unittest
class Mydemo(unittest.TestCase):
    def test1(self):
        print "excute test1"
    @unittest.Myskip
    def test2(self):
        print "excute test2"
        raise AssertionError("test2 fail")
    @unittest.Myskip
    def test3(self):
        print "excute test3"
if __name__ == '__main__':
    unittest.main()

 好了,看上去還不錯....關於其餘的unnitest相關知識,不想再扯了,最後拓展HTMLTestRunner報告,這多是你們關心的!寫這個HTMLTestRunner的大神是在很久以前的了,基本能知足你們需求。可是,目前對於web自動化,我以爲至少要新增2個東西。第一個新增skip列:由於我可能會skip某些case;第二新增截圖列,若是有錯誤我可能要截圖。
打了這麼久字不想再多說了....我給出所有代碼,而後代碼中我改變的地方我給出標記並加註釋吧,完整代碼以下:(可能有點長,可是要有點耐心)

#coding=utf-8
"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.

The simplest way to use this is to invoke its main method. E.g.

    import unittest
    import HTMLTestRunner

    ... define your tests ...

    if __name__ == '__main__':
        HTMLTestRunner.main()


For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.

    # output to a file
    fp = file('my_report.html', 'wb')
    runner = HTMLTestRunner.HTMLTestRunner(
                stream=fp,
                title='My unit test',
                description='This demonstrates the report output by HTMLTestRunner.'
                )

    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'

    # run the test
    runner.run(my_test_suite)


------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""

# URL: http://tungwaiyip.info/software/HTMLTestRunner.html

__author__ = "Wai Yip Tung"
__version__ = "0.8.2"


"""
Change History

Version 0.8.2
* Show output inline instead of popup window (Viorel Lupu).

Version in 0.8.1
* Validated XHTML (Wolfgang Borgert).
* Added description of test classes and test cases.

Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.

Version in 0.7.1
* Back port to Python 2.3 (Frank Horowitz).
* Fix missing scroll bars in detail log (Podi).
"""

# TODO: color stderr
# TODO: simplify javascript using ,ore than 1 class in the class attribute?
#coding=utf-8
import datetime
import io
import sys
reload(sys)
sys.setdefaultencoding('utf8')
import time
import unittest
import re
from xml.sax import saxutils


# ------------------------------------------------------------------------
# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# output, use the redirectors for the cached stream.
#
# e.g.
#   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
#   >>>

class OutputRedirector(object):
    """ Wrapper to redirect stdout or stderr """
    def __init__(self, fp):
        self.fp = fp

    def write(self, s):
        self.fp.write(s)

    def writelines(self, lines):
        self.fp.writelines(lines)

    def flush(self):
        self.fp.flush()

stdout_redirector = OutputRedirector(sys.stdout)
stderr_redirector = OutputRedirector(sys.stderr)



# ----------------------------------------------------------------------
# Template

class Template_mixin(object):
    """
    Define a HTML template for report customerization and generation.

    Overall structure of an HTML report

    HTML
    +------------------------+
    |<html>                  |
    |  <head>                |
    |                        |
    |   STYLESHEET           |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </head>               |
    |                        |
    |  <body>                |
    |                        |
    |   HEADING              |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   REPORT               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |   ENDING               |
    |   +----------------+   |
    |   |                |   |
    |   +----------------+   |
    |                        |
    |  </body>               |
    |</html>                 |
    +------------------------+
    """

    STATUS = {
    0: 'pass',
    1: 'fail',
    2: 'error',
    3:'skip'
    }

    DEFAULT_TITLE = 'Unit Test Report'
    DEFAULT_DESCRIPTION = ''

    # ------------------------------------------------------------------------
    # HTML Template

    HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>%(title)s</title>
    <meta name="generator" content="%(generator)s"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    %(stylesheet)s
</head>
<body>
<script language="javascript" type="text/javascript"><!--
output_list = Array();

/* level - 0:Summary; 1:Failed; 2:All */
function showCase(level) {
    trs = document.getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
        tr = trs[i];
        id = tr.id;
        if (id.substr(0,2) == 'ft') {
            if (level < 1) {
                tr.className = 'hiddenRow';
            }
            else {
                tr.className = '';
            }
        }
        if (id.substr(0,2) == 'pt') {
            if (level > 1) {
                tr.className = '';
            }
            else {
                tr.className = 'hiddenRow';
            }
}
if (id.substr(0,2) == 'st') {
if (level > 1) {
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
} } } function showClassDetail(cid, count) { var id_list = Array(count); var toHide = 1; for (var i = 0; i < count; i++) { tid0 = 't' + cid.substr(1) + '.' + (i+1); tid = 'f' + tid0; tr = document.getElementById(tid); if (!tr) { tid = 'p' + tid0; tr = document.getElementById(tid); } if (!tr) { tid = 's' + tid0; tr = document.getElementById(tid); } id_list[i] = tid; if (tr.className) { toHide = 0; } } for (var i = 0; i < count; i++) { tid = id_list[i]; if (toHide) { document.getElementById('div_'+tid).style.display = 'none' document.getElementById(tid).className = 'hiddenRow'; } else { document.getElementById(tid).className = ''; } } } function showTestDetail(div_id){ var details_div = document.getElementById(div_id) var displayState = details_div.style.display // alert(displayState) if (displayState != 'block' ) { displayState = 'block' details_div.style.display = 'block' } else { details_div.style.display = 'none' } } function html_escape(s) { s = s.replace(/&/g,'&'); s = s.replace(/</g,'<'); s = s.replace(/>/g,'>'); return s; } /* obsoleted by detail in <div> function showOutput(id, name) { var w = window.open("", //url name, "resizable,scrollbars,status,width=800,height=450"); d = w.document; d.write("<pre>"); d.write(html_escape(output_list[id])); d.write("\n"); d.write("<a href='javascript:window.close()'>close</a>\n"); d.write("</pre>\n"); d.close(); } */ --></script> %(heading)s %(report)s %(ending)s </body> </html> """ # variables: (title, generator, stylesheet, heading, report, ending) # ------------------------------------------------------------------------ # Stylesheet # # alternatively use a <link> for external style sheet, e.g. # <link rel="stylesheet" href="$url" type="text/css"> STYLESHEET_TMPL = """ <style type="text/css" media="screen"> body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; } table { font-size: 100%; } pre { word-wrap:break-word;word-break:break-all;overflow:auto;} /* -- heading ---------------------------------------------------------------------- */ h1 { font-size: 16pt; color: gray; } .heading { margin-top: 0ex; margin-bottom: 1ex; } .heading .attribute { margin-top: 1ex; margin-bottom: 0; } .heading .description { margin-top: 4ex; margin-bottom: 6ex; } /* -- css div popup ------------------------------------------------------------------------ */ a.popup_link { } a.popup_link:hover { color: red; } .popup_window { display: none; position: relative; left: 0px; top: 0px; /*border: solid #627173 1px; */ padding: 10px; background-color: 00; font-family: "Lucida Console", "Courier New", Courier, monospace; text-align: left; font-size: 8pt; width: 600px; } } /* -- report ------------------------------------------------------------------------ */ #show_detail_line { margin-top: 3ex; margin-bottom: 1ex; } #result_table { width: 80%; border-collapse: collapse; border: 1px solid #777; } #header_row { font-weight: bold; color: white; background-color: #777; } #result_table td { border: 1px solid #777; padding: 2px; } #total_row { font-weight: bold; } .passClass { background-color: #6c6; } .failClass { background-color: #c60; } .errorClass { background-color: #c00; } .passCase { color: #6c6; } .failCase { color: #c60; font-weight: bold; } .errorCase { color: #c00; font-weight: bold; } .hiddenRow { display: none; } .testcase { margin-left: 2em; } /* -- ending ---------------------------------------------------------------------- */ #ending { } </style> """ # ------------------------------------------------------------------------ # Heading # HEADING_TMPL = """<div class='heading'> <h1>%(title)s</h1> %(parameters)s <p class='description'>%(description)s</p> </div> """ # variables: (title, parameters, description) HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p> """ # variables: (name, value) # ------------------------------------------------------------------------ # Report # REPORT_TMPL = """ <p id='show_detail_line'>Show <a href='javascript:showCase(0)'>Summary</a> <a href='javascript:showCase(1)'>Failed</a> <a href='javascript:showCase(2)'>All</a> </p> <table id='result_table'> <colgroup> <col align='left' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> <col align='right' /> </colgroup> <tr id='header_row'> <td>Test Group/Test case</td> <td>Count</td> <td>Pass</td> <td>Fail</td> <td>Error</td> <td>Skip</td> <td>View</td> <td>Screenshot</td> </tr> %(test_list)s <tr id='total_row'> <td>Total</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td>%(skip)s</td> <td> </td> <td> </td> </tr> </table> """ # variables: (test_list, count, Pass, fail, error) REPORT_CLASS_TMPL = r""" <tr class='%(style)s'> <td>%(desc)s</td> <td>%(count)s</td> <td>%(Pass)s</td> <td>%(fail)s</td> <td>%(error)s</td> <td>%(skip)s</td> <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td> <td> </td> </tr> """ # variables: (style, desc, count, Pass, fail,skip, error, cid) REPORT_TEST_WITH_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='6' align='center'> <!--css div popup start--> <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" > %(status)s</a> <div id='div_%(tid)s' class="popup_window" > <div style='text-align: right; color:red;cursor:pointer'> <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " > [x]</a> </div> <pre> %(script)s </pre> </div> <!--css div popup end--> </td> <td align='center'> <a %(hidde)s href="%(image)s">picture_shot</a> </td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_NO_OUTPUT_TMPL = r""" <tr id='%(tid)s' class='%(Class)s'> <td class='%(style)s'><div class='testcase'>%(desc)s</div></td> <td colspan='6' align='center'>%(status)s</td> <td align='center'> <a %(hidde)s href="%(image)s">picture_shot</a> </td> </tr> """ # variables: (tid, Class, style, desc, status) REPORT_TEST_OUTPUT_TMPL = r""" %(id)s: %(output)s """ # variables: (id, output) # ------------------------------------------------------------------------ # ENDING # ENDING_TMPL = """<div id='ending'> </div>""" # -------------------- The end of the Template class ------------------- TestResult = unittest.TestResult class _TestResult(TestResult): # note: _TestResult is a pure representation of results. # It lacks the output and reporting ability compares to unittest._TextTestResult. def __init__(self, verbosity=1): TestResult.__init__(self) self.stdout0 = None self.stderr0 = None self.success_count = 0 self.skipped_count=0#add skipped_count self.failure_count = 0 self.error_count = 0 self.verbosity = verbosity # result is a list of result in 4 tuple # ( # result code (0: success; 1: fail; 2: error), # TestCase object, # Test output (byte string), # stack trace, # ) self.result = [] def startTest(self, test): TestResult.startTest(self, test) # just one buffer for both stdout and stderr self.outputBuffer = io.BytesIO() stdout_redirector.fp = self.outputBuffer stderr_redirector.fp = self.outputBuffer self.stdout0 = sys.stdout self.stderr0 = sys.stderr sys.stdout = stdout_redirector sys.stderr = stderr_redirector def complete_output(self): """ Disconnect output redirection and return buffer. Safe to call multiple times. """ if self.stdout0: sys.stdout = self.stdout0 sys.stderr = self.stderr0 self.stdout0 = None self.stderr0 = None return self.outputBuffer.getvalue() def stopTest(self, test): # Usually one of addSuccess, addError or addFailure would have been called. # But there are some path in unittest that would bypass this. # We must disconnect stdout in stopTest(), which is guaranteed to be called. self.complete_output() def addSuccess(self, test): self.success_count += 1 TestResult.addSuccess(self, test) output = self.complete_output() self.result.append((0, test, output, '')) if self.verbosity > 1: sys.stderr.write('ok ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('.') def addSkip(self, test, reason): self.skipped_count+= 1 TestResult.addSkip(self, test,reason) output = self.complete_output() self.result.append((3, test,'',reason)) if self.verbosity > 1: sys.stderr.write('skip ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('s') def addError(self, test, err): self.error_count += 1 TestResult.addError(self, test, err) _, _exc_str = self.errors[-1] output = self.complete_output() self.result.append((2, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('E ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('E') def addFailure(self, test, err): self.failure_count += 1 TestResult.addFailure(self, test, err) _, _exc_str = self.failures[-1] output = self.complete_output() self.result.append((1, test, output, _exc_str)) if self.verbosity > 1: sys.stderr.write('F ') sys.stderr.write(str(test)) sys.stderr.write('\n') else: sys.stderr.write('F') class HTMLTestRunner(Template_mixin): """ """ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None,name=None): self.stream = stream self.verbosity = verbosity if title is None: self.title = self.DEFAULT_TITLE else: self.title = title if name is None: self.name ='' else: self.name = name if description is None: self.description = self.DEFAULT_DESCRIPTION else: self.description = description self.startTime = datetime.datetime.now() def run(self, test): "Run the given test case or test suite." result = _TestResult(self.verbosity) test(result) self.stopTime = datetime.datetime.now() self.generateReport(test, result) # print (sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)) return result def sortResult(self, result_list): # unittest does not seems to run in any particular order. # Here at least we want to group them together by class. rmap = {} classes = [] for n,t,o,e in result_list: cls = t.__class__ if not cls in rmap: rmap[cls] = [] classes.append(cls) rmap[cls].append((n,t,o,e)) r = [(cls, rmap[cls]) for cls in classes] return r def getReportAttributes(self, result): """ Return report attributes as a list of (name, value). Override this to add custom attributes. """ startTime = str(self.startTime)[:19] duration = str(self.stopTime - self.startTime) status = [] if result.success_count: status.append('Pass %s' % result.success_count) if result.failure_count: status.append('Failure %s' % result.failure_count) if result.skipped_count: status.append('Skip %s' % result.skipped_count) if result.error_count: status.append('Error %s' % result.error_count ) if status: status = ' '.join(status) else: status = 'none' return [ ('Start Time', startTime), ('Duration', duration), ('Status', status), ] def generateReport(self, test, result): report_attrs = self.getReportAttributes(result)#報告的頭部 generator = 'HTMLTestRunner %s' % __version__ stylesheet = self._generate_stylesheet()#拿到css文件 heading = self._generate_heading(report_attrs) report = self._generate_report(result) ending = self._generate_ending() output = self.HTML_TMPL % dict( title = saxutils.escape(self.title), generator = generator, stylesheet = stylesheet, heading = heading, report = report, ending = ending, ) self.stream.write(output.encode('utf8')) def _generate_stylesheet(self): return self.STYLESHEET_TMPL def _generate_heading(self, report_attrs): a_lines = [] for name, value in report_attrs: line = self.HEADING_ATTRIBUTE_TMPL % dict( name = saxutils.escape(name), value = saxutils.escape(value), ) a_lines.append(line) heading = self.HEADING_TMPL % dict( title = saxutils.escape(self.title), parameters = ''.join(a_lines), description = saxutils.escape(self.description), ) return heading #根據result收集報告 def _generate_report(self, result): rows = [] sortedResult = self.sortResult(result.result) i = 0 for cid, (cls, cls_results) in enumerate(sortedResult): # subtotal for a class np = nf =ns=ne = 0#np表明pass個數,nf表明fail,ns表明skip,ne,表明error for n,t,o,e in cls_results: if n == 0: np += 1 elif n == 1: nf += 1 elif n==3:ns+=1 else: ne += 1 # format class description # if cls.__module__ == "__main__": # name = cls.__name__ # else: # name = "%s.%s" % (cls.__module__, cls.__name__) name = cls.__name__ try: core_name=self.name[i] except Exception,e: core_name ='' # doc = (cls.__doc__)+core_name and (cls.__doc__+core_name).split("\n")[0] or "" doc = (cls.__doc__) and cls.__doc__ .split("\n")[0] or "" desc = doc and '%s: %s' % (name, doc) or name i=i+1
#生成每一個TestCase類的彙總數據,對於報告中的 row = self.REPORT_CLASS_TMPL % dict( style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass', desc = desc, count = np+nf+ne+ns, Pass = np, fail = nf, error = ne, skip=ns, cid = 'c%s' % (cid+1), ) rows.append(row) #生成每一個TestCase類中全部方法的測試結果 for tid, (n,t,o,e) in enumerate(cls_results): self._generate_report_test(rows, cid, tid, n, t, o, e) report = self.REPORT_TMPL % dict( test_list = ''.join(rows), count = str(result.success_count+result.failure_count+result.error_count+result.skipped_count), Pass = str(result.success_count), fail = str(result.failure_count), error = str(result.error_count), skip=str(result.skipped_count) ) return report def _generate_report_test(self, rows, cid, tid, n, t, o, e): # e.g. 'pt1.1', 'ft1.1', etc has_output = bool(o or e) tid = (n == 0 and 'p' or n==3 and 's' or 'f') + 't%s.%s' % (cid+1,tid+1) name = t.id().split('.')[-1] doc = t.shortDescription() or "" desc = doc and ('%s: %s' % (name, doc)) or name tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL uo1="" # o and e should be byte string because they are collected from stdout and stderr? if isinstance(o,str): uo = str(o) else: uo = e if isinstance(e,str): # TODO: some problem with 'string_escape': it escape \n and mess up formating # ue = unicode(e.encode('string_escape')) ue = e else: ue = o script = self.REPORT_TEST_OUTPUT_TMPL % dict( id = tid, output = saxutils.escape(str(uo) + str(ue)) ) if "shot_picture_name" in str(saxutils.escape(str(ue))): hidde_status='' pattern = re.compile(r'AssertionError:.*?shot_picture_name=(.*)',re.S) shot_name =re.search(pattern,str(saxutils.escape(str(e)))) try: image_url="http://192.168.99.105/contractreport/screenshot/"+time.strftime("%Y-%m-%d", time.localtime(time.time()))+"/"+shot_name.group(1)+".png" except Exception,e: image_url = "http://192.168.99.105/contractreport/screenshot/" + time.strftime("%Y-%m-%d",time.localtime(time.time())) else: hidde_status = '''hidden="hidden"''' image_url='' row = tmpl % dict( tid = tid, Class = (n == 0 and 'hiddenRow' or 'none'), style=n == 2 and 'errorCase' or (n == 1 and 'failCase') or (n == 3 and 'skipCase' or 'none'), desc = desc, script = script, hidde=hidde_status, image=image_url, status = self.STATUS[n], ) rows.append(row) if not has_output: return def _generate_ending(self): return self.ENDING_TMPL ############################################################################## # Facilities for running tests from the command line ############################################################################## # Note: Reuse unittest.TestProgram to launch test. In the future we may # build our own launcher to support more specific command line # parameters like test title, CSS, etc. # class TestProgram(unittest.TestProgram): # """ # A variation of the unittest.TestProgram. Please refer to the base # class for command line parameters. # """ # def runTests(self): # # Pick HTMLTestRunner as the default test runner. # # base class's testRunner parameter is not useful because it means # # we have to instantiate HTMLTestRunner before we know self.verbosity. # if self.testRunner is None: # self.testRunner = HTMLTestRunner(verbosity=self.verbosity) # unittest.TestProgram.runTests(self) # # main = TestProgram ############################################################################## # Executing this module from the command line ############################################################################## if __name__ == "__main__": main(module=None)

 把上面代碼複製覆蓋原來的HTMLTestRunner就好,截圖那塊我是把錯誤的圖像放在apache服務器的某個路徑下的,若是有錯誤就顯示圖片超連接,沒有就隱藏這超連接。

關於上面的改動其實很簡單,熟悉必定的前端語言(html.javascript)便可。HTMLTestRunner原理就是咱們上文提到的利用_TestResult繼承unnitest中的TestResult類,並重寫了addSuccessaddSkip,addError等方法,把測試結果放在一個self.result裏面,最後遍歷這個result利用前端的一些知識生成一個html報告。這邊貼圖貼一下生成的樣式吧,執行testcase的代碼:

#coding=utf-8
import unittest
import HTMLTestRunner
import sys,os
class Mydemo(unittest.TestCase):
    def test1(self):
        print "excute test1"
    @unittest.Myskip
    def test2(self):
        print "excute test2"
        raise AssertionError("test2 fail")
    @unittest.Myskip
    def test3(self):
        print "excute test3"
    @unittest.Myskip
    def test4(self):
        print "excute test4"
if __name__ == '__main__':
    module_name=os.path.basename(sys.argv[0]).split(".")[0]
    module=__import__(module_name)
    fp=file("./new.html","wb")
    runner=HTMLTestRunner.HTMLTestRunner(fp)
    all_suite=unittest.defaultTestLoader.loadTestsFromModule(module)
    runner.run(all_suite)

最後生成的報告以下:說了這麼多隻是但願你們能對unnitest有更多的瞭解,固然若是你已經懂的更多或者認爲我某些地方說錯了,請一笑而過....

相關文章
相關標籤/搜索