UI自动化测试之UnitTest测试框架全栈

  从软件架构的⻆度来说,测试最重要的步骤是在软件开发的时候介入比较好,所以在早期测试的介入,从软件经济学的⻆度上来说,发现的问题解决成本低,投入的资源比较少。因此,对一个测试的系统,开始最佳的测试就是源代码级别的测试,也就是单元测试阶段,这个过程也被成为白盒测试。

  单元测试是最基本也是最底层的测试类型,单元测试应用于最基本的软件代码,如类,函数。方法等单元测试通过可执行的断言检查被测单元的输出是否满足预期结果。测试金字塔的理论上来说,越往下的测试投入资源越高,得到的回报率越大⻅测试金字塔模型:

 

 

 

  抛开软件架构的层面,在自动化测试的体系中,单元测试框架以及单元测试的知识体系是必须要掌握的技能之一, 元测试的知识体系是自动化测试工程师以及测试开发工程师的知识体系之一,而且是必须具备的知识之一。

   Python语言中应用最广泛的单元测试框架是unittestpytest,unittest属于标准库,只要安装了Python解释器后就 可以直接导入使用,而pytest是第三方的库,需要单独的安装。单元测试框架的知识体系就围绕unittestpytest来 开始

一、白盒测试原理

复制代码

什么是白盒测试:
  白盒测试又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。
  白盒测试是一种测试用例设计方法,盒子指的是被测试的软件,白盒指的是盒子是可视的,即清楚盒子内部的东西以及里面是如何运作的。
  "白盒"法全面了解程序内部逻辑结构、对所有逻辑路径进行测试。

在软件架构的层面来说,测试最核心的步骤就是在软件开发过程中。 就软件本身而言,软件的行为或者功能是软件细节实现的产物,这些最终是交付给用户的东⻄。
所以在早期执行测试的系统有可能是一个可测试和健壮的系统,它会带来为用户提供的功能往往是让人满意的结果。 因此开始执行测试的最佳方法是来自源代码,也就是软件编写的地方以及开发人员。 由于源代码是对开发人员是可⻅的,这样的一个测试过程我们可以称为白盒测试。

复制代码

二、自动化测试用例编写

         不管基于什么的测试框架,自动化测试用例的编写都需要遵守如下的规则,具体总结如下:

 

 

 

三、UnitTest测试框架 

1、UnitTest组件

unittest是属于Python语言的单元测试框架,它的核心组件具体可以总结为如下:

  测试用例:就是测试类里面编写的测试方法 测试固件:初始化和清理,使用到的方法分别是setUp()和tearDown()
  测试套件:就是测试用例的集合,在一个测试套件可以有很多的测试用例,它的英文单词是TestSuite
  测试执行:TestRunner,执行测试套件或者测试用例
  测试报告:TestReport,就是执行所有测试用例后的结果信息

  测试固件:
    1、setUp() and tearDown():在一个测试类里面,有多少个测试用例,它就会被执行多少次
    2、setUpClass() and tearDownClass():是类方,不管测试类里面有多少测试用例,它只会执行一次

 

测试类继承unittest模块中的TestCase类后,依据继承的这个类来设置一个新的测试用例类和测试方法,具体代码如下:

import  unittest


class ApiTest(unittest.TestCase):
    def test1(self):
        pass

1.1、测试固件

测试固件表示一个测试用例或者多个测试以及清理工作所需要的设置或者准备,具体代码如下:

import  unittest


class ApiTest(unittest.TestCase):
    def setUp(self):
        pass
    def tearDown(self):
        pass

1.2、测试套件

测试套件顾名思义就是相关测试用例的集合。在unittest中主要通过TestSuite类提供对测试套件的支持,具体代码如下:

复制代码
import  unittest


class ApiTest(unittest.TestCase):
    def test_1(self):
        pass

    def test_2(self):
        pass

if __name__ == '__main__':
    suite=unittest.TestSuite()
    suite.addTest('test_1')
    unittest.TextTestRunner(verbosity=2).run(suite)
复制代码

1.3、测试运行

管理和运行测试用例的对象。

1.4、测试断言

对所测试的对象依据返回的实际结果与期望结果进行断言校验

1.5 、测试结果

测试结果类管理着测试结果的输出,测试结果呈现给最终的用户来反馈本次测试执行的结果信息。

2、unittest测试固件详解

在unittest中测试固件依据方法可以分为两种执行方式,一种是测试固件只执行一次,另外一种是测试固件每次都
执行,下面依据具体的案例来讲解二者。

2.1、测试固件每次均执行

复制代码
import  unittest
from selenium import  webdriver

class ApiTest(unittest.TestCase):
    def setUp(self):
        self.driver=webdriver.Chrome()
        self.driver.maximize_window()
        self.driver.get('http://www.baidu.com')
        self.driver.implicitly_wait(30)
        
    def tearDown(self):
        self.driver.quit()
        
def test_baidu_title(self): 
    self.assertEqual(self.driver.title,'百度一下,你就知道')
    
def test_baidu_url(self):
    self.assertEqual(self.driver.current_url,'https://www.baidu.com/')
    
if __name__ == '__main__':
    unittest.main(verbosity=2)
复制代码

2.2、测试固件只执行一次

使用的是类方法,这样测试固件只会执行一次的。

复制代码
import  unittest
from selenium import  webdriver

class ApiTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.driver=webdriver.Chrome()
        cls.driver.maximize_window()
        cls.driver.get('http://www.baidu.com')
        cls.driver.implicitly_wait(30)
        
    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()
        
    def test_baidu_title(self):
        self.assertEqual(self.driver.title,'百度一下,你就知道')
        
    def test_baidu_url(self):
        self.assertEqual(self.driver.current_url,'https://www.baidu.com/')
        
if __name__ == '__main__':
    unittest.main(verbosity=2)
复制代码

2.3 、测试用例执行顺序详解

        在unittest中,测试点的执行顺序是依据ascill码来执行的,也就是说根据ASCII码的顺序加载,数字与字母的顺序为:0-9,A-Z,a-z,所以以A开头的测试用例方法会优先执行,以a开头会后执行。也就是根据数字的大小从小到 大执行的,切记数字的大小值的是不包含test,值的是test后面的测试点的数字大小,⻅案例代码:

复制代码
import  unittest

import  unittest
class Api(unittest.TestCase):
    def test_1(self):
        pass
    
def test_2(self):
    pass

if __name__ == '__main__':
    unittest.main(verbosity=2)
复制代码

执行的顺序为:test_1,下来时test_2 当然测试点不会单纯是数字的,也有字符串的,在python中,字符串与数字的转换为:

  chr(): 数字转为字母
  ord(): 字母转为数字

测试点的名称是字符串的执行顺序:

复制代码
import  unittest

class Api(unittest.TestCase):
   def test_abc(self):
      pass
   
def test_acb(self):
   pass

if __name__ == '__main__':
   unittest.main(verbosity=2)
复制代码

备注:执行的顺序为:test_abc,下来是test_acb

再看字符串与数字的比较:

复制代码
import  unittest

class Api(unittest.TestCase):
   def test_api_0a0(self):
      pass
   
def test_api_00a(self):
   pass

if __name__ == '__main__':
   unittest.main(verbosity=2)
复制代码

2.3 、编写测试用例注意事项

1、在一个测试类里面,每一个测试方法都是以test开头的,test不能是中间或者尾部,必须是开头,建议test_
2、每一个测试用例方法都应该有注释信息,这样在测试报告就会显示具体的测试点的检查点
3、在自动化测试中,每个测试用例都必须得有断言,无断言的自动化测试用例是无效的
4、最好一个测试用例方法对应一个业务测试点,不要多个业务检查点写一个测试用例
5、如果涉及到业务逻辑的处理,最好把业务逻辑的处理方法放在断言前面,这样做的目的是不要因为业务逻辑 执行错误导致断言也是失败
6、测试用例名称最好规范,有约束 
7、是否先写自动化测试的测试代码,再使用自动化测试方式写?
    个人觉得没必要,毕竟能够做自动化测试的人都具备了功能测试的基本水平,所以没必要把一个业务的检查点写多次,浪费时间和人力成本。

见案例代码:

def test_baidu_title(self): 
    '''验证:验证百度首⻚的title是否正确''' 
    self.assertEqual(self.driver.title,'百度一下,你就知道')

下面具体演示第五点的情况,⻅测试的源码:

复制代码
import requests
import  unittest
import  os


class ApiTest(unittest.TestCase):
   def writeBookID(self,bookID):
      with open(os.path.join(os.path.dirname(__file__),'bookID'),'w') as f:
         f.write(str(bookID))

   @property
   def readBookID(self):
      with open(os.path.join(os.path.dirname(__file__), 'bookID'), 'r') as f:
         return f.read()

   def creeteBook(self):
      dict1={'author':'无涯','name':'Python自动化测试实战','done':True}
      r=requests.post(url='http://localhost:5000/v1/api/books',json=dict1)
      self.writeBookID(r.json()[0]['datas']['id'])
      return r

   def test_api_book(self):
      '''API测试:查询书籍'''
      self.creeteBook()
      r=requests.get(url='http://localhost:5000/v1/api/book/{0}'.format(self.readBookID))
      self.assertEqual(r.json()['datas'][0]['name'],'Python自动化测试实战')
      self.assertEqual(r.json()['datas'][0]['id'],int(self.readBookID))

   def delBook(self):
      r=requests.delete(url='http://localhost:5000/v1/api/book/{0}'.format(self.readBookID))

if __name__ == '__main__':
   unittest.main(verbosity=2)
复制代码

3.测试套件详解

          UnitTest的测试框架中提供了很丰富的测试套件,测试套件可以把它理解为测试用例的集合,或者可以说理解为一个容器,在这个容器里面可以存放很多的测试用例。下面详细的说明下各个不同测试套件的应用

3.1、按测试类执行

          按测试类执行,可以理解为在测试套件中,我们按测试类的方式来进行执行,也就不需要在乎一个测试类里面有多少测试用例,我们是以测试类为单位来进行执行,测试类里面有多少的测试用例,我们都会进行执行,下面详细的演示具体案例代码如下:

复制代码
import  unittest
from selenium import  webdriver
from test.init import Init

class Baidu(Init):

    def test_baidu_shouye_title(self):
        '''验证百度首页的title信息'''
        assert self.driver.title=='百度一下,你就知道'

    def test_baidu_shouye_url(self):
        '''验证百度的首页URL地址'''
        assert self.driver.current_url=='https://www.baidu.com/'

if __name__ == '__main__':
    '''按测试类执行'''
    suite=unittest.TestLoader().loadTestsFromTestCase(Baidu)
    unittest.TextTestRunner(verbosity=2).run(suite)

#执行如上的代码后,就会按类为单位来进行执行。


复制代码

3.2、按测试模块执行

            思维:按测试模块来执行就是以模块为单位来进行执行,并且在一个模块里面可以编写很多的类,

下面通过详细的代码演示具体代码如下:

复制代码
import  unittest
from selenium import  webdriver
from test.init import Init

class Baidu(Init):
   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'

   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'


class BaiDuSo(Init):
   def test_baidu_so_value(self):
      '''百度搜索关键字的验证'''
      so=self.driver.find_element_by_id('kw')
      so.send_keys('Selenium4')
      assert so.get_attribute('value')=='Selenium4'

if __name__ == '__main__':
   '''按测试类执行'''
   suite=unittest.TestLoader().loadTestsFromModule('test_module.py')
   unittest.TextTestRunner(verbosity=2).run(suite)
复制代码

3.3、按具体的测试用例来执行

             如果是仅仅执行某一个测试用例,执行的方式一种是鼠标放到具体的测试用例,然后右键执行就可以了,

另外一种方式是我们可以把需要执行的测试用例添加到测试套件中,然后来单独的进行执行,这种方式其实我个人是不建议的,通过具体的代码来演示具体如下:

复制代码
import  unittest
from selenium import  webdriver
from test.init import Init

class Baidu(Init):

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'

   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'

if __name__ == '__main__':
   '''按测试类执行'''
   apiSuite=unittest.makeSuite(Baidu,'test_baidu_shouye_title')
   suite=unittest.TestSuite(apiSuite)
   unittest.TextTestRunner(verbosity=2).run(suite)
复制代码

3.4、自定义测试套件

           针对测试套件的方式是很多的,我们可以把加载所有测试用例的方式单独分离出来,这样我们只需要关注更多的测试用例的执行,

下面具体演示下测试套件的分离部分,具体代码如下:

复制代码
import  unittest
from selenium import  webdriver
from test.init import Init


class Baidu(Init):

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'

   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'

   def suite(self):
      '''自定义测试套件'''
      return unittest.TestLoader().loadTestsFromModule('test_customer.py')

if __name__ == '__main__':
   '''自定义测试套件'''
   unittest.TextTestRunner(verbosity=2).run(Baidu.suite())
复制代码

3.5、分离测试套件

          在一个完整的自动化测试用例中,比如在UI的自动化测试用例中,我们的测试用例是按照业务模块来进行划分的,那么以为着我们需要编写很多的模块,但是就存在重复的代码,比如我们针对百度产品的测试,不管是测试什么模块,测试固件这部分的代码每个测试模块都是一样的,这样就导致很多的重复的代码,重复必然就带来测试效率的低下的问题,举一个很简单的问题,比如需要修改测试的地址,就需要修改很多的测试模块,但如果把测试套件分离出来,就只需要修改一个地方就可以了,这样我们的测试效率就提升了一点,毕竟效率的提升是需要做很多的,不可能一点就进行大幅度的提升的。

分离测试套件的思想其实很简单的,就是使用了继承的思想来解决这个问题,我们把测试固件分离到init.py里面,代码具体如下:

复制代码
import unittest
from selenium import  webdriver


class Init(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.implicitly_wait(30)
      self.driver.get('http://www.baidu.com')

   def tearDown(self) -> None:
      self.driver.quit()
复制代码

这样其他测试模块就只需要引入这个模块中的Init类就可以了,然后再继承这个类,具体的代码如下:

复制代码
import  unittest
from selenium import  webdriver
from test.init import Init

class Baidu(Init):

   def test_baidu_shouye_title(self):
      '''验证百度首页的title信息'''
      assert self.driver.title=='百度一下,你就知道'

   def test_baidu_shouye_url(self):
      '''验证百度的首页URL地址'''
      assert self.driver.current_url=='https://www.baidu.com/'

if __name__ == '__main__':
   '''按测试类执行'''
   suite=unittest.TestLoader().loadTestsFromTestCase(Baidu)
   unittest.TextTestRunner(verbosity=2).run(suite)
复制代码

3.6、unittest之参数化

            参数化:当一个测试场景,它的测试步骤相同,测试数据不一样的时候,那么就可以使用参数化的思想来解决

    在unittest的测试框架中,可以结合ddt的模块来达到参数化的应用,当然关于ddt库的应用在数据驱动方面有很详细的解释,这里就直接说另外的一个第三方的库parameterized,安装的命令为:

pip3 install parameterized

安装成功后,这里就以一个两个数相加的案例来演示它的应用,实现的源码如下:

复制代码
'''
本质思想:把测试的数据看成列表当中的一个元素,
那么针对列表进行循环的时候,把每个元素进行赋值
'''

def add(a,b):
   return a+b

import unittest
from parameterized import  parameterized,param

class AddTest(unittest.TestCase):
   @parameterized.expand([
      param(1,1,2),
      param('a','b','ab')
   ])
   def test_add_function(self,x,y,result):
      print(result)
      assert add(a=x,b=y)==result

if __name__ == '__main__':
   unittest.main(verbosity=2)
结果:
2
ab

verbosity参数设置,默认为1,可以设置为0和2。

  • 0 (静默模式): 你只能获得总的测试用例数和总的结果。
  • 1 (默认模式): 非常类似静默模式 只是在每个成功的用例前面有个“.” 每个失败的用例前面有个 “E”
  • 2 (详细模式):测试结果会显示每个测试用例的所有相关的信息 并且 你在命令行里加入不同的参数可以起到一样的效果
复制代码

    在UI自动化测试中,parameterized特别有用,如针对一个登录案例的测试,针对登录就会有很多的测试案例的,主要是用户名和密码的input表单的验证以及错误信息的验证,下面就结合具体的案例来看在UI自动化测 试中的应用具体源码为:

复制代码
'''
本质思想:
把测试的数据 看成列表中的一个元素,在列表进行循环的时候,把每个元素进行赋值
''' def add(a,b): return a+b import unittest from parameterized import parameterized,param from selenium import webdriver import time as t class AddTest(unittest.TestCase): def setUp(self) -> None: self.driver=webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(30) self.driver.get('https://mail.sina.com.cn/#') def tearDown(self) -> None: self.driver.quit() @parameterized.expand([ param('','','请输入邮箱名'), param('srtSA','saert','您输入的邮箱名格式不正确'), param('aserSDAsd@sina.com','asdfrty','登录名或密码错误') ]) def test_sina_email(self,username,password,result): t.sleep(2) self.driver.find_element_by_id('freename').send_keys(username) t.sleep(2) self.driver.find_element_by_id('freepassword').send_keys(password) t.sleep(2) self.driver.find_element_by_link_text('登录').click() t.sleep(3) div=self.driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]') assert div.text==result if __name__ == '__main__': unittest.main(verbosity=2)
复制代码

3.7、测试报告

         测试报告的生成以及加载所有测试模块的过程中,我们在tests的模块下编写了很多的测试用例,但是实际的生产环境中总不能按测试模块来执行,我们都是加载所有的测试模块来执行并且最终生成基于HTML的测试报告,测试报告会使用到第三方的库HTMLTestRunner,下载的地址为:https://github.com/tungwaiyip/HTMLTestRunner,当然针对Python3需要修改下源码部分,把该模块需要放在 /Library/Frameworks/Python.framework/Versions/3.7/lib的目录下。

整体源码为:

复制代码
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

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

import datetime
import io
import sys
import time
import unittest
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',
    }

    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';
            }
        }
    }
}


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);
        }
        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         { }

/* -- 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: #E6E6D6;
    font-family: "Lucida Console", "Courier New", Courier, monospace;
    text-align: left;
    font-size: 8pt;
    width: 500px;
}

}
/* -- 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>View</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> </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><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
</tr>
""" # variables: (style, desc, count, Pass, fail, 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='5' 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>
</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='5' align='center'>%(status)s</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.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.StringIO()
        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 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):
        self.stream = stream
        self.verbosity = verbosity
        if title is None:
            self.title = self.DEFAULT_TITLE
        else:
            self.title = title
        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)
        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.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()
        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


    def _generate_report(self, result):
        rows = []
        sortedResult = self.sortResult(result.result)
        for cid, (cls, cls_results) in enumerate(sortedResult):
            # subtotal for a class
            np = nf = ne = 0
            for n,t,o,e in cls_results:
                if n == 0: np += 1
                elif n == 1: nf += 1
                else: ne += 1

            # format class description
            if cls.__module__ == "__main__":
                name = cls.__name__
            else:
                name = "%s.%s" % (cls.__module__, cls.__name__)
            doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
            desc = doc and '%s: %s' % (name, doc) or name

            row = self.REPORT_CLASS_TMPL % dict(
                style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                desc = desc,
                count = np+nf+ne,
                Pass = np,
                fail = nf,
                error = ne,
                cid = 'c%s' % (cid+1),
            )
            rows.append(row)

            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),
            Pass = str(result.success_count),
            fail = str(result.failure_count),
            error = str(result.error_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 '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

        # o and e should be byte string because they are collected from stdout and stderr?
        if isinstance(o,str):
            # TODO: some problem with 'string_escape': it escape \n and mess up formating
            # uo = unicode(o.encode('string_escape'))
            # uo = o.decode('latin-1')
            uo = o
        else:
            uo = o
        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.decode('latin-1')
            ue = e
        else:
            ue = e

        script = self.REPORT_TEST_OUTPUT_TMPL % dict(
            id = tid,
            output = saxutils.escape(str(uo)+ue),
        )

        row = tmpl % dict(
            tid = tid,
            Class = (n == 0 and 'hiddenRow' or 'none'),
            style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
            desc = desc,
            script = script,
            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源码
复制代码

3.7.1、加载所有的测试模块

          下面就编写具体的函数来加载所有的测试模块,路径处理部分使用os的模块来进行处理,针对路径处理不能使用硬编码,使用硬编码只会带来维护的成本性,而且也涉及到不同的操作系统针对路径是有不同的,比如MAC和Linux下是没有C盘的,但是Windows操作系统是有的,这部分需要特别的注意下,下面的函数主要体现的是加载所有测试模块的代码,具体如下:

复制代码
import  os
import  HTMLTestRunner
import  unittest


def base_dir():
   return os.path.join(os.path.dirname(__file__),'test')


def getSuite():
   tests=unittest.defaultTestLoader.discover(
      start_dir=base_dir(),
      pattern='test_*.py',
      top_level_dir=None
   )
   return tests
复制代码

调用getSuite的方法,调用的代码为:

复制代码
import  os
import  HTMLTestRunner
import  unittest


def base_dir():
   return os.path.join(os.path.dirname(__file__),'test')


def getSuite():
   tests=unittest.defaultTestLoader.discover(
      start_dir=base_dir(),
      pattern='test_*.py',
      top_level_dir=None
   )
   return tests



if __name__ == '__main__':
   for item in getSuite():
      print(item)

见如下输出的结果信息:

<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_assert.Baidu testMethod=test_baidu_shouye_so>, <test_assert.Baidu testMethod=test_baidu_shouye_title>, <test_assert.Baidu testMethod=test_baidu_shouye_url>]>, <unittest.suite.TestSuite tests=[]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_baidu_shouYe.Baidu testMethod=test_baidu_shouye_title>, <test_baidu_shouYe.Baidu testMethod=test_baidu_shouye_url>]>, <unittest.suite.TestSuite tests=[]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_customer.Baidu testMethod=test_baidu_shouye_title>, <test_customer.Baidu testMethod=test_baidu_shouye_url>]>, <unittest.suite.TestSuite tests=[]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_make_suite.Baidu testMethod=test_baidu_shouye_title>, <test_make_suite.Baidu testMethod=test_baidu_shouye_url>]>, <unittest.suite.TestSuite tests=[]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_module.BaiDuSo testMethod=test_baidu_so_value>]>, <unittest.suite.TestSuite tests=[<test_module.Baidu testMethod=test_baidu_shouye_title>, <test_module.Baidu testMethod=test_baidu_shouye_url>]>, <unittest.suite.TestSuite tests=[]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_params.AddTest testMethod=test_add_function_0>, <test_params.AddTest testMethod=test_add_function_1_a>]>]>
<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_sina.AddTest testMethod=test_sina_email_0_>, <test_sina.AddTest testMethod=test_sina_email_1_srtSA>, <test_sina.AddTest testMethod=test_sina_email_2_aserSDAsd_sina_com>]>]>

Process finished with exit code 0
复制代码

从如上的输出结果来看,已经到模块级别了,下面出一个需求,总共有多少测试用例,请统计出来!其实可以针对模块再次进行循环到类级别,然后到测试用例的级别,然后把所有的测试用例加到一个列表里面,获取列表的长度就是测试用例的总数,下面是到类级别的代码和输出结果:

复制代码
if __name__ == '__main__':
   for item in getSuite():
      for i in item:
         print(i)

结果:


<unittest.suite.TestSuite tests=[<test_assert.Baidu testMethod=test_baidu_shouye_so>, <test_assert.Baidu testMethod=test_baidu_shouye_title>, <test_assert.Baidu testMethod=test_baidu_shouye_url>]>
<unittest.suite.TestSuite tests=[]>
<unittest.suite.TestSuite tests=[<test_baidu_shouYe.Baidu testMethod=test_baidu_shouye_title>, <test_baidu_shouYe.Baidu testMethod=test_baidu_shouye_url>]>
<unittest.suite.TestSuite tests=[]>
<unittest.suite.TestSuite tests=[<test_customer.Baidu testMethod=test_baidu_shouye_title>, <test_customer.Baidu testMethod=test_baidu_shouye_url>]>
<unittest.suite.TestSuite tests=[]>
<unittest.suite.TestSuite tests=[<test_make_suite.Baidu testMethod=test_baidu_shouye_title>, <test_make_suite.Baidu testMethod=test_baidu_shouye_url>]>
<unittest.suite.TestSuite tests=[]>
<unittest.suite.TestSuite tests=[<test_module.BaiDuSo testMethod=test_baidu_so_value>]>
<unittest.suite.TestSuite tests=[<test_module.Baidu testMethod=test_baidu_shouye_title>, <test_module.Baidu testMethod=test_baidu_shouye_url>]>
<unittest.suite.TestSuite tests=[]>
<unittest.suite.TestSuite tests=[<test_params.AddTest testMethod=test_add_function_0>, <test_params.AddTest testMethod=test_add_function_1_a>]>
<unittest.suite.TestSuite tests=[<test_sina.AddTest testMethod=test_sina_email_0_>, <test_sina.AddTest testMethod=test_sina_email_1_srtSA>, <test_sina.AddTest testMethod=test_sina_email_2_aserSDAsd_sina_com>]>

Process finished with exit code 0
复制代码

再循环一层,到具体的测试用例级别,代码为:

复制代码
import  os
import  HTMLTestRunner
import  unittest


def base_dir():
    return os.path.join(os.path.dirname(__file__),'test')


def getSuite():
    tests=unittest.defaultTestLoader.discover(
        start_dir=base_dir(),
        pattern='test_*.py',
        top_level_dir=None
    )
    return tests



if __name__ == '__main__':
    for item in getSuite():
        for i in item:
            for j in i:
                print(j)

输出结果信息为具体的测试用例,具体如下:

test_baidu_shouye_so (test_assert.Baidu)
test_baidu_shouye_title (test_assert.Baidu)
test_baidu_shouye_url (test_assert.Baidu)
test_baidu_shouye_title (test_baidu_shouYe.Baidu)
test_baidu_shouye_url (test_baidu_shouYe.Baidu)
test_baidu_shouye_title (test_customer.Baidu)
test_baidu_shouye_url (test_customer.Baidu)
test_baidu_shouye_title (test_make_suite.Baidu)
test_baidu_shouye_url (test_make_suite.Baidu)
test_baidu_so_value (test_module.BaiDuSo)
test_baidu_shouye_title (test_module.Baidu)
test_baidu_shouye_url (test_module.Baidu)
test_add_function_0 (test_params.AddTest)
test_add_function_1_a (test_params.AddTest)
test_sina_email_0_ (test_sina.AddTest)
test_sina_email_1_srtSA (test_sina.AddTest)
test_sina_email_2_aserSDAsd_sina_com (test_sina.AddTest)
复制代码

定义一个列表,然后获取测试用例的总数,具体代码如下:

复制代码
import  os
import  HTMLTestRunner
import  unittest


def base_dir():
   return os.path.join(os.path.dirname(__file__),'test')


def getSuite():
   tests=unittest.defaultTestLoader.discover(
      start_dir=base_dir(),
      pattern='test_*.py',
      top_level_dir=None
   )
   return tests



if __name__ == '__main__':
   testCases=list()
   for item in getSuite():
      for i in item:
         for j in i:
            testCases.append(j)
   print('测试用例的总数为:{0}'.format(len(testCases)))
复制代码

3.7.2、生成HTML测试报告

         下面具体展示测试报告的生成,把测试报告存储到report的文件夹里面,思考到每次生成的测试报名名称一致,可以以当前时间作为区分。

整体实现的代码如下:

复制代码
import  os
import  HTMLTestRunner
import  unittest
import time


def base_dir():
    return os.path.join(os.path.dirname(__file__),'test')


def getSuite():
    tests=unittest.defaultTestLoader.discover(
        start_dir=base_dir(),
        pattern='test_*.py',
        top_level_dir=None
    )
    return tests



def getNowTime():
    return time.strftime('%y-%m-%d %h_%m_%s',time.localtime(time.time()))

def run():
    filename=os.path.join(os.path.dirname(__file__),'report',getNowTime()+'report.html')
    fp=open(filename,'wb')
    runner=HTMLTestRunner.HTMLTestRunner(
        stream=fp,
        title='',
        description=''
    )
    runner.run(getSuite())

if __name__ == '__main__':
    run()
复制代码

执行后,输出结果为:

.................<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> 
Time Elapsed: 0:01:13.527186

在report的文件夹下,生成的测试报告打开后显示如下:

 

 

 

posted @   LaraCroft  阅读(495)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示