Title

13、Unittest单元测试框架使用教程

Unittest单元测试框架使用教程

unittest单元测试框架简介:

  是由python提供的单元测试框架,可以帮助我们完成接口自动化和UI自动化测试,主要使用来组织和执行测试用例,它可以将测试用例组织到一起执行,如果某条测试用例执行失败,其他的测试用例能够继续执行,unittest框架还提供了丰富的断言方法,用来判断测试是否通过,最终生成测试结果报告

Unittest框架的四个核心概念:

  TestCase:用户自定义的测试用例基类,调用run()方法时,会一次调用setUp()方法、执行用例方法、tearDown()方法

  TestSuite:测试用例集合,可以通过addTest()方法手动添加TestCase,还能够通过TestLoader自动添加TestCase,TestLoader自动添加时没有顺序

  TestRunner:运行测试用例的驱动类,可以执行TestCase,也可以执行TestSuite,执行后TestCase和TestSuite会自动管理TestResult

  TestFixture:进行测试活动的准备工作,比如创建临时数据库、文件、目录等,其中setUP()方法和setDown()方法是最常用的

Unittest测试流程

  1. 写好一个TestCase:定义一个class类,继承unittest.TestCase,就是一条测试用例,类中定义以test开头的方法,每个test开头的方法在load到suit中时都会生成一条测试用例
  2. 由TestLoader加载TestCase到TestSuite中
  3. TestRunner运行TestSuite,运行结果保存在TextTestResult中

 

TestCase使用:

# test_001.py
import unittest

def add(x, y):
  return x + y

class Test_Case_01(unitest.TestCase):      # 新建测试类必须继承unittest.TestCase

  # 测试方法名称必须以 test 开头
  def test_add_01(self):  
    result = add(2, 4)
    print(f'两数和为:{result}')

  def test_add_02(self):
    result = add(5, 7)
    print(f'两数之和为:{result}')


if __name__ == '__main__':
  # 执行测试用例
  unittest.main()

 

TestSuite使用:

  • 实例化:suite = unittest.TestSuite()
  • 添加用例:suite.addTest(ClassName(“MethodName”)),ClassName:类名,MethodName:方法名)
  • 添加扩展:suite.addTest(unittest.makeSuite(ClassName)),搜索指定ClassName内 test 开头的方法,并添加到测试套件中
  • TestSuite需要配合TestRunner才能被执行
# 案例

import unittest
from test_001 import Test_Case_01

# 实例化测试套件对象
suite = unittest.TestSuite()
# 单独添加测试用例
suite.addTest(Test_Case_01("test_add_01"))
suite.addTest(Test_Case_01("test_add_02"))

# 批量添加用例, addTest()添加的次数根据Test_Case_01中的测试用例个数决定,这里是添加2次
suite.addTest(unittest.makeSuite(Test_Case_01))

# 执行
runner = unittest.TextTestRunner()
runner.run(suite)

 

TextTestRunner使用

  • 实例化:runner = unittest.TextTestRunner()
  • 执行:runner.run(suite) ,suite为测试套件的名称,代码示例参考TestSuit使用代码即可
runner = unittest.TextTestRunner()
runner.run(suite)

 

 TestLoader的使用

  • 用来加载TestCase到TestSuite中,即加载满足条件的测试用例,并把测试用例封装成测试套件。

  • 使用unittest.TestLoader(),通过该类下面的discover()方法自动搜索指定目录下指定开头的 .py 文件,并将查到的测试用例组装到测试套件。
# 示例
import os
import unittest
# 获取根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))   # 获取文件的根目录

# 测试用例地址
test_case_path = os.path.join(BASE_DIR, "Scripts")

suit = unittest.TestSuite()
loader = unittest.TestLoader()  # 专门用来查找用例的实例
# test_case_path:测试用例的所在路径。pattern:搜索以test开头的py文件,默认为‘test*.py’
suit.addTests(loader.discover(test_case_path, pattern="test*.py"))

runner = unittest.TextTestRunner()
runner.run(suit)

注意: 也可以使用unittest.defaultTestLoader 代替 unittest.TestLoader()

discover = unittest.defaultTestLoader.discover(test_case_path, pattern="t*.py")
suit.addTests(discover)

 

TestSuite和TestLoader的区别

  • 共同点:都是测试套件
  • 不同点:实现方式不同
    • TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中的某个测试方法)
    • TestLoader搜索指定目录下指定开头的py文件,并添加测试类中的所有测试方法,不能指定添加某个测试方法

断言

  • 比较预期结果和实际结果是否一致,从而判断测试用例是否执行通过
  • 断言的方法在unittest.testCase类中已经定义好了,我们的自定义测试类继承了unittest.TestCase类,所以可以直接使用
  • 断言方法如下
  • assertEqual(a, b, msg='测试失败时打印的信息')       # a == b    断言a和b是否相等,相等则测试用例通过
    
    assertNotEqual(a, b, msg='测试失败时打印的信息') # a != b 断言a和b是否相等,不相等则测试用例通过  
    
    assertTrue(x, msg='测试失败时打印的信息') # x is True 断言x是否True,是True则测试用例通过  
    
    assertFalse(x, msg='测试失败时打印的信息') # x is False 断言x是否False,是False则测试用例通过  
    
    assertIn(a, b, msg='测试失败时打印的信息') # a in b 断言a是否在b中,在b中则测试用例通过  
    
    assertNotIn(a, b, msg='测试失败时打印的信息') # a not in b 断言a是否在b中,不在b中则测试用例通过  
    
    assertIsNone(x,msg='测试失败时打印的信息') # x is None 断言x是否None,是None则测试用例通过  
    
    assertIsNotNone(x, msg='测试失败时打印的信息') # x not is None 断言x是否None,不是None则测试用例通过  
    
    assertIs(a, b, msg='测试失败时打印的信息') # a is b 断言a是否是b,是则测试用例通过  
    
    assertNotIs(a, b, msg='测试失败时打印的信息') # a not is b 断言a是否是b,不是则测试用例通过  
    
    assertIsInstance(a, b, msg='测试失败时打印的信息') # 断言a是是b的一个实例,是则测试用例通过  
    
    assertNotIsInstance(a, b, msg='测试失败时打印的信息') # 断言a是是b的一个实例,不是则测试用例通过
  • 代码示例:比较字符串是否相同
import unittest

from time import sleep
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains


class XiCheng(unittest.TestCase):
    def setUp(self):
        # 获取谷歌浏览器驱动
        self.driver = webdriver.Chrome()

        # 打开携程登录页面
        self.driver.get(
            'https://passport.ctrip.com/user/login?BackUrl=https%3A%2F%2Fwww.ctrip.com%2F#ctm_ref=c_ph_login_buttom')

        # 浏览器最大化
        self.driver.maximize_window()

        # 显示等待
        self.driver.implicitly_wait(10)

    def tearDown(self):
        sleep(5)
        self.driver.quit()

    def test_xiecheng_login(self):
        # 定位用户名输入框,并输入用户名
        self.driver.find_element_by_id('nloginname').send_keys('17696295304')
        sleep(1)

        # 输入密码
        self.driver.find_element_by_xpath("//*[@id='npwd']").send_keys('hyf19940513')
        sleep(1)

        # 勾选协议
        xieyi = self.driver.find_element_by_xpath('//*[@id="checkboxAgreementInput"]')
        self.driver.execute_script("arguments[0].click();", xieyi)

        # 点击登录按钮
        self.driver.find_element_by_id('nsubmit').click()

        sleep(10)

        # 获取页面上显示的用户名
        username = self.driver.find_element_by_xpath('//*[@id="hp_nfes_accountbar"]/li[1]/div/button/div/span').text

        # 断言:比较页面读取到的用户名和真实的用户名是否一致
        self.assertEqual(username,'东街刘华强', msg = '用户名不正确,页面显示用户名为'+username)

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

 

HTMLTestRunner生成html测试报告

 1 """
  2 A TestRunner for use with the Python unit testing framework. It
  3 generates a HTML report to show the result at a glance.
  4 The simplest way to use this is to invoke its main method. E.g.
  5     import unittest
  6     import HTMLTestRunner
  7     ... define your tests ...
  8     if __name__ == '__main__':
  9         HTMLTestRunner.main()
 10 For more customization options, instantiates a HTMLTestRunner object.
 11 HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
 12     # output to a file
 13     fp = file('my_report.html', 'wb')
 14     runner = HTMLTestRunner.HTMLTestRunner(
 15                 stream=fp,
 16                 title='My unit test',
 17                 description='This demonstrates the report output by HTMLTestRunner.'
 18                 )
 19     # Use an external stylesheet.
 20     # See the Template_mixin class for more customizable options
 21     runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
 22     # run the test
 23     runner.run(my_test_suite)
 24 ------------------------------------------------------------------------
 25 Copyright (c) 2004-2007, Wai Yip Tung
 26 All rights reserved.
 27 Redistribution and use in source and binary forms, with or without
 28 modification, are permitted provided that the following conditions are
 29 met:
 30 * Redistributions of source code must retain the above copyright notice,
 31   this list of conditions and the following disclaimer.
 32 * Redistributions in binary form must reproduce the above copyright
 33   notice, this list of conditions and the following disclaimer in the
 34   documentation and/or other materials provided with the distribution.
 35 * Neither the name Wai Yip Tung nor the names of its contributors may be
 36   used to endorse or promote products derived from this software without
 37   specific prior written permission.
 38 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 39 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 40 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 41 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 42 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 43 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 44 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 45 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 46 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 47 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 48 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 49 """
 50  
 51 # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
 52  
 53 __author__ = "Wai Yip Tung"
 54 __version__ = "0.8.2"
 55  
 56  
 57 """
 58 Change History
 59 Version 0.8.2
 60 * Show output inline instead of popup window (Viorel Lupu).
 61 Version in 0.8.1
 62 * Validated XHTML (Wolfgang Borgert).
 63 * Added description of test classes and test cases.
 64 Version in 0.8.0
 65 * Define Template_mixin class for customization.
 66 * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
 67 Version in 0.7.1
 68 * Back port to Python 2.3 (Frank Horowitz).
 69 * Fix missing scroll bars in detail log (Podi).
 70 """
 71  
 72 # TODO: color stderr
 73 # TODO: simplify javascript using ,ore than 1 class in the class attribute?
 74  
 75 import datetime
 76 import io
 77 import sys
 78 import time
 79 import unittest
 80 from xml.sax import saxutils
 81  
 82  
 83 # ------------------------------------------------------------------------
 84 # The redirectors below are used to capture output during testing. Output
 85 # sent to sys.stdout and sys.stderr are automatically captured. However
 86 # in some cases sys.stdout is already cached before HTMLTestRunner is
 87 # invoked (e.g. calling logging.basicConfig). In order to capture those
 88 # output, use the redirectors for the cached stream.
 89 #
 90 # e.g.
 91 #   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
 92 #   >>>
 93  
 94 class OutputRedirector(object):
 95     """ Wrapper to redirect stdout or stderr """
 96     def __init__(self, fp):
 97         self.fp = fp
 98  
 99     def write(self, s):
100         self.fp.write(bytes(s,'UTF-8'))
101  
102     def writelines(self, lines):
103         self.fp.writelines(lines)
104  
105     def flush(self):
106         self.fp.flush()
107  
108 stdout_redirector = OutputRedirector(sys.stdout)
109 stderr_redirector = OutputRedirector(sys.stderr)
110  
111  
112  
113 # ----------------------------------------------------------------------
114 # Template
115  
116 class Template_mixin(object):
117     """
118     Define a HTML template for report customerization and generation.
119     Overall structure of an HTML report
120     HTML
121     +------------------------+
122     |<html>                  |
123     |  <head>                |
124     |                        |
125     |   STYLESHEET           |
126     |   +----------------+   |
127     |   |                |   |
128     |   +----------------+   |
129     |                        |
130     |  </head>               |
131     |                        |
132     |  <body>                |
133     |                        |
134     |   HEADING              |
135     |   +----------------+   |
136     |   |                |   |
137     |   +----------------+   |
138     |                        |
139     |   REPORT               |
140     |   +----------------+   |
141     |   |                |   |
142     |   +----------------+   |
143     |                        |
144     |   ENDING               |
145     |   +----------------+   |
146     |   |                |   |
147     |   +----------------+   |
148     |                        |
149     |  </body>               |
150     |</html>                 |
151     +------------------------+
152     """
153  
154     STATUS = {
155     0: 'pass',
156     1: 'fail',
157     2: 'error',
158     }
159  
160     DEFAULT_TITLE = 'Unit Test Report'
161     DEFAULT_DESCRIPTION = ''
162  
163     # ------------------------------------------------------------------------
164     # HTML Template
165  
166     HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
167 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
168 <html xmlns="http://www.w3.org/1999/xhtml">
169 <head>
170     <title>%(title)s</title>
171     <meta name="generator" content="%(generator)s"/>
172     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
173     %(stylesheet)s
174 </head>
175 <body>
176 <script language="javascript" type="text/javascript"><!--
177 output_list = Array();
178 /* level - 0:Summary; 1:Failed; 2:All */
179 function showCase(level) {
180     trs = document.getElementsByTagName("tr");
181     for (var i = 0; i < trs.length; i++) {
182         tr = trs[i];
183         id = tr.id;
184         if (id.substr(0,2) == 'ft') {
185             if (level < 1) {
186                 tr.className = 'hiddenRow';
187             }
188             else {
189                 tr.className = '';
190             }
191         }
192         if (id.substr(0,2) == 'pt') {
193             if (level > 1) {
194                 tr.className = '';
195             }
196             else {
197                 tr.className = 'hiddenRow';
198             }
199         }
200     }
201 }
202 function showClassDetail(cid, count) {
203     var id_list = Array(count);
204     var toHide = 1;
205     for (var i = 0; i < count; i++) {
206         tid0 = 't' + cid.substr(1) + '.' + (i+1);
207         tid = 'f' + tid0;
208         tr = document.getElementById(tid);
209         if (!tr) {
210             tid = 'p' + tid0;
211             tr = document.getElementById(tid);
212         }
213         id_list[i] = tid;
214         if (tr.className) {
215             toHide = 0;
216         }
217     }
218     for (var i = 0; i < count; i++) {
219         tid = id_list[i];
220         if (toHide) {
221             document.getElementById('div_'+tid).style.display = 'none'
222             document.getElementById(tid).className = 'hiddenRow';
223         }
224         else {
225             document.getElementById(tid).className = '';
226         }
227     }
228 }
229 function showTestDetail(div_id){
230     var details_div = document.getElementById(div_id)
231     var displayState = details_div.style.display
232     // alert(displayState)
233     if (displayState != 'block' ) {
234         displayState = 'block'
235         details_div.style.display = 'block'
236     }
237     else {
238         details_div.style.display = 'none'
239     }
240 }
241 function html_escape(s) {
242     s = s.replace(/&/g,'&amp;');
243     s = s.replace(/</g,'&lt;');
244     s = s.replace(/>/g,'&gt;');
245     return s;
246 }
247 /* obsoleted by detail in <div>
248 function showOutput(id, name) {
249     var w = window.open("", //url
250                     name,
251                     "resizable,scrollbars,status,width=800,height=450");
252     d = w.document;
253     d.write("<pre>");
254     d.write(html_escape(output_list[id]));
255     d.write("\n");
256     d.write("<a href='javascript:window.close()'>close</a>\n");
257     d.write("</pre>\n");
258     d.close();
259 }
260 */
261 --></script>
262 %(heading)s
263 %(report)s
264 %(ending)s
265 </body>
266 </html>
267 """
268     # variables: (title, generator, stylesheet, heading, report, ending)
269  
270  
271     # ------------------------------------------------------------------------
272     # Stylesheet
273     #
274     # alternatively use a <link> for external style sheet, e.g.
275     #   <link rel="stylesheet" href="$url" type="text/css">
276  
277     STYLESHEET_TMPL = """
278 <style type="text/css" media="screen">
279 body        { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
280 table       { font-size: 100%; }
281 pre         { }
282 /* -- heading ---------------------------------------------------------------------- */
283 h1 {
284     font-size: 16pt;
285     color: gray;
286 }
287 .heading {
288     margin-top: 0ex;
289     margin-bottom: 1ex;
290 }
291 .heading .attribute {
292     margin-top: 1ex;
293     margin-bottom: 0;
294 }
295 .heading .description {
296     margin-top: 4ex;
297     margin-bottom: 6ex;
298 }
299 /* -- css div popup ------------------------------------------------------------------------ */
300 a.popup_link {
301 }
302 a.popup_link:hover {
303     color: red;
304 }
305 .popup_window {
306     display: none;
307     position: relative;
308     left: 0px;
309     top: 0px;
310     /*border: solid #627173 1px; */
311     padding: 10px;
312     background-color: #E6E6D6;
313     font-family: "Lucida Console", "Courier New", Courier, monospace;
314     text-align: left;
315     font-size: 8pt;
316     width: 500px;
317 }
318 }
319 /* -- report ------------------------------------------------------------------------ */
320 #show_detail_line {
321     margin-top: 3ex;
322     margin-bottom: 1ex;
323 }
324 #result_table {
325     width: 80%;
326     border-collapse: collapse;
327     border: 1px solid #777;
328 }
329 #header_row {
330     font-weight: bold;
331     color: white;
332     background-color: #777;
333 }
334 #result_table td {
335     border: 1px solid #777;
336     padding: 2px;
337 }
338 #total_row  { font-weight: bold; }
339 .passClass  { background-color: #6c6; }
340 .failClass  { background-color: #c60; }
341 .errorClass { background-color: #c00; }
342 .passCase   { color: #6c6; }
343 .failCase   { color: #c60; font-weight: bold; }
344 .errorCase  { color: #c00; font-weight: bold; }
345 .hiddenRow  { display: none; }
346 .testcase   { margin-left: 2em; }
347 /* -- ending ---------------------------------------------------------------------- */
348 #ending {
349 }
350 </style>
351 """
352  
353  
354  
355     # ------------------------------------------------------------------------
356     # Heading
357     #
358  
359     HEADING_TMPL = """<div class='heading'>
360 <h1>%(title)s</h1>
361 %(parameters)s
362 <p class='description'>%(description)s</p>
363 </div>
364 """ # variables: (title, parameters, description)
365  
366     HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
367 """ # variables: (name, value)
368  
369  
370  
371     # ------------------------------------------------------------------------
372     # Report
373     #
374  
375     REPORT_TMPL = """
376 <p id='show_detail_line'>Show
377 <a href='javascript:showCase(0)'>Summary</a>
378 <a href='javascript:showCase(1)'>Failed</a>
379 <a href='javascript:showCase(2)'>All</a>
380 </p>
381 <table id='result_table'>
382 <colgroup>
383 <col align='left' />
384 <col align='right' />
385 <col align='right' />
386 <col align='right' />
387 <col align='right' />
388 <col align='right' />
389 </colgroup>
390 <tr id='header_row'>
391     <td>Test Group/Test case</td>
392     <td>Count</td>
393     <td>Pass</td>
394     <td>Fail</td>
395     <td>Error</td>
396     <td>View</td>
397 </tr>
398 %(test_list)s
399 <tr id='total_row'>
400     <td>Total</td>
401     <td>%(count)s</td>
402     <td>%(Pass)s</td>
403     <td>%(fail)s</td>
404     <td>%(error)s</td>
405     <td>&nbsp;</td>
406 </tr>
407 </table>
408 """ # variables: (test_list, count, Pass, fail, error)
409  
410     REPORT_CLASS_TMPL = r"""
411 <tr class='%(style)s'>
412     <td>%(desc)s</td>
413     <td>%(count)s</td>
414     <td>%(Pass)s</td>
415     <td>%(fail)s</td>
416     <td>%(error)s</td>
417     <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
418 </tr>
419 """ # variables: (style, desc, count, Pass, fail, error, cid)
420  
421  
422     REPORT_TEST_WITH_OUTPUT_TMPL = r"""
423 <tr id='%(tid)s' class='%(Class)s'>
424     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
425     <td colspan='5' align='center'>
426     <!--css div popup start-->
427     <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
428         %(status)s</a>
429     <div id='div_%(tid)s' class="popup_window">
430         <div style='text-align: right; color:red;cursor:pointer'>
431         <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
432            [x]</a>
433         </div>
434         <pre>
435         %(script)s
436         </pre>
437     </div>
438     <!--css div popup end-->
439     </td>
440 </tr>
441 """ # variables: (tid, Class, style, desc, status)
442  
443  
444     REPORT_TEST_NO_OUTPUT_TMPL = r"""
445 <tr id='%(tid)s' class='%(Class)s'>
446     <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
447     <td colspan='5' align='center'>%(status)s</td>
448 </tr>
449 """ # variables: (tid, Class, style, desc, status)
450  
451  
452     REPORT_TEST_OUTPUT_TMPL = r"""
453 %(id)s: %(output)s
454 """ # variables: (id, output)
455  
456  
457  
458     # ------------------------------------------------------------------------
459     # ENDING
460     #
461  
462     ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
463  
464 # -------------------- The end of the Template class -------------------
465  
466  
467 TestResult = unittest.TestResult
468  
469 class _TestResult(TestResult):
470     # note: _TestResult is a pure representation of results.
471     # It lacks the output and reporting ability compares to unittest._TextTestResult.
472  
473     def __init__(self, verbosity=1):
474         TestResult.__init__(self)
475         self.stdout0 = None
476         self.stderr0 = None
477         self.success_count = 0
478         self.failure_count = 0
479         self.error_count = 0
480         self.verbosity = verbosity
481  
482         # result is a list of result in 4 tuple
483         # (
484         #   result code (0: success; 1: fail; 2: error),
485         #   TestCase object,
486         #   Test output (byte string),
487         #   stack trace,
488         # )
489         self.result = []
490  
491  
492     def startTest(self, test):
493         TestResult.startTest(self, test)
494         # just one buffer for both stdout and stderr
495         self.outputBuffer = io.BytesIO()
496         stdout_redirector.fp = self.outputBuffer
497         stderr_redirector.fp = self.outputBuffer
498         self.stdout0 = sys.stdout
499         self.stderr0 = sys.stderr
500         sys.stdout = stdout_redirector
501         sys.stderr = stderr_redirector
502  
503  
504     def complete_output(self):
505         """
506         Disconnect output redirection and return buffer.
507         Safe to call multiple times.
508         """
509         if self.stdout0:
510             sys.stdout = self.stdout0
511             sys.stderr = self.stderr0
512             self.stdout0 = None
513             self.stderr0 = None
514         return self.outputBuffer.getvalue()
515  
516  
517     def stopTest(self, test):
518         # Usually one of addSuccess, addError or addFailure would have been called.
519         # But there are some path in unittest that would bypass this.
520         # We must disconnect stdout in stopTest(), which is guaranteed to be called.
521         self.complete_output()
522  
523  
524     def addSuccess(self, test):
525         self.success_count += 1
526         TestResult.addSuccess(self, test)
527         output = self.complete_output()
528         self.result.append((0, test, output, ''))
529         if self.verbosity > 1:
530             sys.stderr.write('ok ')
531             sys.stderr.write(str(test))
532             sys.stderr.write('\n')
533         else:
534             sys.stderr.write('.')
535  
536     def addError(self, test, err):
537         self.error_count += 1
538         TestResult.addError(self, test, err)
539         _, _exc_str = self.errors[-1]
540         output = self.complete_output()
541         self.result.append((2, test, output, _exc_str))
542         if self.verbosity > 1:
543             sys.stderr.write('E  ')
544             sys.stderr.write(str(test))
545             sys.stderr.write('\n')
546         else:
547             sys.stderr.write('E')
548  
549     def addFailure(self, test, err):
550         self.failure_count += 1
551         TestResult.addFailure(self, test, err)
552         _, _exc_str = self.failures[-1]
553         output = self.complete_output()
554         self.result.append((1, test, output, _exc_str))
555         if self.verbosity > 1:
556             sys.stderr.write('F  ')
557             sys.stderr.write(str(test))
558             sys.stderr.write('\n')
559         else:
560             sys.stderr.write('F')
561  
562  
563 class HTMLTestRunner(Template_mixin):
564     """
565     """
566     def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
567         self.stream = stream
568         self.verbosity = verbosity
569         if title is None:
570             self.title = self.DEFAULT_TITLE
571         else:
572             self.title = title
573         if description is None:
574             self.description = self.DEFAULT_DESCRIPTION
575         else:
576             self.description = description
577  
578         self.startTime = datetime.datetime.now()
579  
580  
581     def run(self, test):
582         "Run the given test case or test suite."
583         result = _TestResult(self.verbosity)
584         test(result)
585         self.stopTime = datetime.datetime.now()
586         self.generateReport(test, result)
587         print('\nTime Elapsed: %s' % (self.stopTime-self.startTime),file=sys.stderr)
588         return result
589  
590  
591     def sortResult(self, result_list):
592         # unittest does not seems to run in any particular order.
593         # Here at least we want to group them together by class.
594         rmap = {}
595         classes = []
596         for n,t,o,e in result_list:
597             cls = t.__class__
598             if not cls in rmap:
599                 rmap[cls] = []
600                 classes.append(cls)
601             rmap[cls].append((n,t,o,e))
602         r = [(cls, rmap[cls]) for cls in classes]
603         return r
604  
605  
606     def getReportAttributes(self, result):
607         """
608         Return report attributes as a list of (name, value).
609         Override this to add custom attributes.
610         """
611         startTime = str(self.startTime)[:19]
612         duration = str(self.stopTime - self.startTime)
613         status = []
614         if result.success_count: status.append('Pass %s'    % result.success_count)
615         if result.failure_count: status.append('Failure %s' % result.failure_count)
616         if result.error_count:   status.append('Error %s'   % result.error_count  )
617         if status:
618             status = ' '.join(status)
619         else:
620             status = 'none'
621         return [
622             ('Start Time', startTime),
623             ('Duration', duration),
624             ('Status', status),
625         ]
626  
627  
628     def generateReport(self, test, result):
629         report_attrs = self.getReportAttributes(result)
630         generator = 'HTMLTestRunner %s' % __version__
631         stylesheet = self._generate_stylesheet()
632         heading = self._generate_heading(report_attrs)
633         report = self._generate_report(result)
634         ending = self._generate_ending()
635         output = self.HTML_TMPL % dict(
636             title = saxutils.escape(self.title),
637             generator = generator,
638             stylesheet = stylesheet,
639             heading = heading,
640             report = report,
641             ending = ending,
642         )
643         self.stream.write(output.encode('utf8'))
644  
645  
646     def _generate_stylesheet(self):
647         return self.STYLESHEET_TMPL
648  
649  
650     def _generate_heading(self, report_attrs):
651         a_lines = []
652         for name, value in report_attrs:
653             line = self.HEADING_ATTRIBUTE_TMPL % dict(
654                     name = saxutils.escape(name),
655                     value = saxutils.escape(value),
656                 )
657             a_lines.append(line)
658         heading = self.HEADING_TMPL % dict(
659             title = saxutils.escape(self.title),
660             parameters = ''.join(a_lines),
661             description = saxutils.escape(self.description),
662         )
663         return heading
664  
665  
666     def _generate_report(self, result):
667         rows = []
668         sortedResult = self.sortResult(result.result)
669         for cid, (cls, cls_results) in enumerate(sortedResult):
670             # subtotal for a class
671             np = nf = ne = 0
672             for n,t,o,e in cls_results:
673                 if n == 0: np += 1
674                 elif n == 1: nf += 1
675                 else: ne += 1
676  
677             # format class description
678             if cls.__module__ == "__main__":
679                 name = cls.__name__
680             else:
681                 name = "%s.%s" % (cls.__module__, cls.__name__)
682             doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
683             desc = doc and '%s: %s' % (name, doc) or name
684  
685             row = self.REPORT_CLASS_TMPL % dict(
686                 style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
687                 desc = desc,
688                 count = np+nf+ne,
689                 Pass = np,
690                 fail = nf,
691                 error = ne,
692                 cid = 'c%s' % (cid+1),
693             )
694             rows.append(row)
695  
696             for tid, (n,t,o,e) in enumerate(cls_results):
697                 self._generate_report_test(rows, cid, tid, n, t, o, e)
698  
699         report = self.REPORT_TMPL % dict(
700             test_list = ''.join(rows),
701             count = str(result.success_count+result.failure_count+result.error_count),
702             Pass = str(result.success_count),
703             fail = str(result.failure_count),
704             error = str(result.error_count),
705         )
706         return report
707  
708  
709     def _generate_report_test(self, rows, cid, tid, n, t, o, e):
710         # e.g. 'pt1.1', 'ft1.1', etc
711         has_output = bool(o or e)
712         tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)
713         name = t.id().split('.')[-1]
714         doc = t.shortDescription() or ""
715         desc = doc and ('%s: %s' % (name, doc)) or name
716         tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
717  
718         # o and e should be byte string because they are collected from stdout and stderr?
719         if isinstance(o,str):
720             # TODO: some problem with 'string_escape': it escape \n and mess up formating
721             # uo = unicode(o.encode('string_escape'))
722             uo = o
723         else:
724             uo = o.decode('utf-8') 
725         if isinstance(e,str):
726             # TODO: some problem with 'string_escape': it escape \n and mess up formating
727             # ue = unicode(e.encode('string_escape'))
728             ue = e
729         else:
730             ue = e.decode('utf-8')
731  
732         script = self.REPORT_TEST_OUTPUT_TMPL % dict(
733             id = tid,
734             output = saxutils.escape(uo+ue),
735         )
736  
737         row = tmpl % dict(
738             tid = tid,
739             Class = (n == 0 and 'hiddenRow' or 'none'),
740             style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
741             desc = desc,
742             script = script,
743             status = self.STATUS[n],
744         )
745         rows.append(row)
746         if not has_output:
747             return
748  
749     def _generate_ending(self):
750         return self.ENDING_TMPL
751  
752  
753 ##############################################################################
754 # Facilities for running tests from the command line
755 ##############################################################################
756  
757 # Note: Reuse unittest.TestProgram to launch test. In the future we may
758 # build our own launcher to support more specific command line
759 # parameters like test title, CSS, etc.
760 class TestProgram(unittest.TestProgram):
761     """
762     A variation of the unittest.TestProgram. Please refer to the base
763     class for command line parameters.
764     """
765     def runTests(self):
766         # Pick HTMLTestRunner as the default test runner.
767         # base class's testRunner parameter is not useful because it means
768         # we have to instantiate HTMLTestRunner before we know self.verbosity.
769         if self.testRunner is None:
770             self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
771         unittest.TestProgram.runTests(self)
772  
773 main = TestProgram
774  
775 ##############################################################################
776 # Executing this module from the command line
777 ##############################################################################
778  
779 if __name__ == "__main__":
780     main(module=None)
  • 创建一个文件,名称“HTMLTestRunner.py”,放在python/Lib目录下,将上述代码复制到文件中保存
  • 需要导包:from HTMLTestRunner import HTMLTestRunner 
  • 代码示例

测试用例代码

 

from selenium import webdriver
from time import sleep
import unittest


class SwagLabs(unittest.TestCase):
    def setUp(self) -> None:
        # 获取浏览器驱动、
        self.driver = webdriver.Chrome()
        # 打开测试网页
        self.driver.get('https://www.saucedemo.com/')
        # 浏览器最大化
        self.driver.maximize_window()
        # 隐式等待页面加载完成
        self.driver.implicitly_wait(10)

    def tearDown(self) -> None:
        # 等待5s,退出浏览器
        sleep(5)
        self.driver.quit()

    def test_login(self):
        # 用户名输入框输入“standard_user”
        self.driver.find_element_by_id('user-name').send_keys('standard_user')
        # 密码输入框输入“standard_user”
        self.driver.find_element_by_id('password').send_keys('secret_sauce')
        # 点击登陆按钮
        self.driver.find_element_by_id('login-button').click()
        # 定位页面标题,对比是否是Swag Labs来判断是否登陆成功
        text_title = self.driver.find_element_by_xpath('//*[@id="header_container"]/div[1]/div[2]/div').text
        self.assertEqual(text_title, 'Swag Labs', msg='登陆失败,未获取指定字符串Swag Labs')


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

 

执行代码

 

 1 import unittest
 2 from HTMLTestRunner import HTMLTestRunner
 3 import time
 4 from open_chrome import SwagLabs
 5 import os
 6 
 7 # 定义报告存放目录
 8 report_path = './reports/'
 9 
10 # 组装报告文件路径:/reports/2023-04-02 12:00:00.html
11 now_time_str = time.strftime('%Y-%m-%d', time.localtime()) + '.html'
12 print(now_time_str)
13 file_path = os.path.join(report_path,now_time_str)
14 print(file_path)
15 
16 # 打开报告存放文件
17 with open(file_path, mode='wb') as fl:
18     # 创建测试集合
19     suit = unittest.TestSuite()
20 
21     # 将测试用例添加到测试集合
22     suit.addTest(SwagLabs('test_login'))
23     # 将测试用例添加到测试集合方式二
24     # suit.addTest(unittest.makeSuite(SwagLabs))
25     
26     # HTMLTestRunner类启动测试运行测试集合
27     runner = HTMLTestRunner(title='测试标题', description='描述本次测试的大概内容', stream=fl)
28     runner.run(suit)

 

posted @ 2023-04-02 20:17  huayaofan  阅读(48)  评论(0)    收藏  举报