深入解读Python的unittest并拓展HTMLTestRunner

深入解读Python的unittest并拓展HTMLTestRunner

原文地址:https://www.cnblogs.com/hhudaqiang/p/6596043.html

unnitest是Python的一个重要的单元测试框架,对于用Python进行开发的同事们可能不需要对他有过深入的了解会用就行,但是,对于自动化测试人员我觉得是要熟知unnitest的执行原理以及相关模块的作用。我这边提几个简单的需求如下:

1.如何利用unnitest执行流程测试而非单元测试。比如我们可能利用selenium+unnitest来跑一段流程,比如test1里面我们实现登陆,test2在test1成功登陆的基础上,实现一个查询的测试,test3我们查询一些数据后,页面选择性提交数据。这个你总不能在test1里面登陆完然后关闭浏览器;在test2里面再登陆再执行查询后关闭浏览器;test3再登陆执行提交数据。你或者可以这样想把这几个步骤写在一个test里面。但是,如果流程过长怎么办?自己难道感觉不到剪不断理还乱的纠结吗....

2.如何控制unnitest的执行顺序。unnitest里面tests数组里面存放的TestCase默认是以首字母排序的,这对于test1,test2....test9这样的执行顺序是没用问题的,但是对于多个test比如test1,test2.........test18,这样unnitest可不是按照这个顺序,我说了是按照首字母排序来的,他会这样执行test1,test10,....test18,test2,..........test9,当然我说的这些对于单元测试是影响不大的(除非各个test之间有数据依赖关系,后面提到),对于流程可能是颠覆性的。

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的原因,后面逐行解释如果拓展它。

.......还有很多

补充一下,为什么往流程上扯呢,因为公司的模块太多,而且复杂,单元测试机会不太会用,只要保证各个业务的主流程没问题即可,但是不影响我们解析unnitest。

我们的想法很多,但是如何来实现呢?那就让我们来深入探讨下python的unnitest吧!

关于unnitest看似复杂我给出来就是unnitest=TestCase+TestResult,只要熟知这2个模块,你就能"为所欲为"!!可能有人说不对不是有什么TestSuite吗还有TextTestRunner等等吗,不错确实我们平时用到的大多是这些模块,但是,到其实最终执行的是TestCase中的run方法,并把结果给TestResult(或它的子类)。我们先来看一个简单的unnitest例子,并以此来拓展!例子如下:

复制代码

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)

复制代码

运行结果如下:

1

2

3

4

5

6

7

8

...

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。我们看个例子:

1

2

3

4

5

6

7

8

9

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(实例对象属性),能共享我们就必须使用类属性,比如下个例子:

1

2

3

4

5

6

7

8

9

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()

 运行结果如下:

1

2

3

4

5

6

7

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()方法,方法如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

def run(self, result, debug=False):

        topLevel = False

        if getattr(result, '_testRunEntered'Falseis 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'Falseor

                    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就是:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#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<br>        browser.close()<br>    <br>if __name__ == '__main__':

    unittest.main()

 

 上面就会在所有的case执行之前启动firefox,因为每个test中拿到的都是Mydemo类中同一个webdriver对象,所以能保证操作的都是同一个浏览器句柄。关于这个setUpClass如果想要动态的改变某个值一定要使用python的可变的对象比如list,dict等...这些其实都是一些python类的一些知识,算我啰嗦吧我还是想举个例子,嫌烦的同学,绕过这一部分吧。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#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()

 运行结果:

1

2

3

4

5

6

7

8

9

10

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没有被改变,这其实也很好理解。如果我们想要改变怎么办,看看下面的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#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()

 运行结果:

1

2

3

4

5

6

7

8

9

10

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方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

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(selfself._testMethodName)

        if (getattr(self.__class__, "__unittest_skip__"Falseor

            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的,代码是这样的:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

#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一个引用。那么我们可以这样写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#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()

 运行结果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

.Fs

======================================================================

FAIL: test2 (__main__.Mydemo)

----------------------------------------------------------------------

Traceback (most recent call last):

  File "D:/Moudle/module_1/test4.py", line 10in 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中新增如下代码:

1

2

3

4

5

6

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中新增:

1

2

3

4

5

6

7

8

__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)

 最终我们这样写:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#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;第二新增截图列,如果有错误我可能要截图。
打了这么久字不想再多说了....我给出全部代码,然后代码中我改变的地方我给出标记并加注释吧,完整代码如下:(可能有点长,但是要有点耐心)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

#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))

        = [(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)

        = 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 == 0: np += 1

                elif == 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")[0or ""

            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=== 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类,并重写了addSuccess,addSkip,addError等方法,把测试结果放在一个self.result里面,最后遍历这个result利用前端的一些知识生成一个html报告。这边贴图贴一下生成的样式吧,执行testcase的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

#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有更多的了解,当然如果你已经懂的更多或者认为我某些地方说错了,请一笑而过....

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值