【Python自动化测试】如何才能让用例自动运行完之后,生成一张直观可看易懂的测试报告呢?

小编使用的是unittest的一个扩展HTMLTestRunner

  • 环境准备

使用之前,我们需要下载HTMLTestRunner.py文件

点击HTMLTestRunner后进入的是一个写满代码的网页,小编推荐操作:右键 --> 另存为,文件名称千万不要改

python3使用上述HTMLTestRunner可能会报错,可以替换如下文件

  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. # URL: http://tungwaiyip.info/software/HTMLTestRunner.html

  51. __author__ = "Wai Yip Tung"

  52. __version__ = "0.8.2"

  53. """

  54. Change History

  55. Version 0.8.2

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

  57. Version in 0.8.1

  58. * Validated XHTML (Wolfgang Borgert).

  59. * Added description of test classes and test cases.

  60. Version in 0.8.0

  61. * Define Template_mixin class for customization.

  62. * Workaround a IE 6 bug that it does not treat <script> block as CDATA.

  63. Version in 0.7.1

  64. * Back port to Python 2.3 (Frank Horowitz).

  65. * Fix missing scroll bars in detail log (Podi).

  66. """

  67. # TODO: color stderr

  68. # TODO: simplify javascript using ,ore than 1 class in the class attribute?

  69. import datetime

  70. import io

  71. import sys

  72. import time

  73. import unittest

  74. from xml.sax import saxutils

  75. # ------------------------------------------------------------------------

  76. # The redirectors below are used to capture output during testing. Output

  77. # sent to sys.stdout and sys.stderr are automatically captured. However

  78. # in some cases sys.stdout is already cached before HTMLTestRunner is

  79. # invoked (e.g. calling logging.basicConfig). In order to capture those

  80. # output, use the redirectors for the cached stream.

  81. #

  82. # e.g.

  83. # >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)

  84. # >>>

  85. class OutputRedirector(object):

  86. """ Wrapper to redirect stdout or stderr """

  87. def __init__(self, fp):

  88. self.fp = fp

  89. def write(self, s):

  90. self.fp.write(s)

  91. def writelines(self, lines):

  92. self.fp.writelines(lines)

  93. def flush(self):

  94. self.fp.flush()

  95. stdout_redirector = OutputRedirector(sys.stdout)

  96. stderr_redirector = OutputRedirector(sys.stderr)

  97. # ----------------------------------------------------------------------

  98. # Template

  99. class Template_mixin(object):

  100. """

  101. Define a HTML template for report customerization and generation.

  102. Overall structure of an HTML report

  103. HTML

  104. +------------------------+

  105. |<html> |

  106. | <head> |

  107. | |

  108. | STYLESHEET |

  109. | +----------------+ |

  110. | | | |

  111. | +----------------+ |

  112. | |

  113. | </head> |

  114. | |

  115. | <body> |

  116. | |

  117. | HEADING |

  118. | +----------------+ |

  119. | | | |

  120. | +----------------+ |

  121. | |

  122. | REPORT |

  123. | +----------------+ |

  124. | | | |

  125. | +----------------+ |

  126. | |

  127. | ENDING |

  128. | +----------------+ |

  129. | | | |

  130. | +----------------+ |

  131. | |

  132. | </body> |

  133. |</html> |

  134. +------------------------+

  135. """

  136. STATUS = {

  137. 0: 'pass',

  138. 1: 'fail',

  139. 2: 'error',

  140. }

  141. DEFAULT_TITLE = 'Unit Test Report'

  142. DEFAULT_DESCRIPTION = ''

  143. # ------------------------------------------------------------------------

  144. # HTML Template

  145. HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>

  146. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

  147. <html xmlns="http://www.w3.org/1999/xhtml">

  148. <head>

  149. <title>%(title)s</title>

  150. <meta name="generator" content="%(generator)s"/>

  151. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

  152. %(stylesheet)s

  153. </head>

  154. <body>

  155. <script language="javascript" type="text/javascript"><!--

  156. output_list = Array();

  157. /* level - 0:Summary; 1:Failed; 2:All */

  158. function showCase(level) {

  159. trs = document.getElementsByTagName("tr");

  160. for (var i = 0; i < trs.length; i++) {

  161. tr = trs[i];

  162. id = tr.id;

  163. if (id.substr(0,2) == 'ft') {

  164. if (level < 1) {

  165. tr.className = 'hiddenRow';

  166. }

  167. else {

  168. tr.className = '';

  169. }

  170. }

  171. if (id.substr(0,2) == 'pt') {

  172. if (level > 1) {

  173. tr.className = '';

  174. }

  175. else {

  176. tr.className = 'hiddenRow';

  177. }

  178. }

  179. }

  180. }

  181. function showClassDetail(cid, count) {

  182. var id_list = Array(count);

  183. var toHide = 1;

  184. for (var i = 0; i < count; i++) {

  185. tid0 = 't' + cid.substr(1) + '.' + (i+1);

  186. tid = 'f' + tid0;

  187. tr = document.getElementById(tid);

  188. if (!tr) {

  189. tid = 'p' + tid0;

  190. tr = document.getElementById(tid);

  191. }

  192. id_list[i] = tid;

  193. if (tr.className) {

  194. toHide = 0;

  195. }

  196. }

  197. for (var i = 0; i < count; i++) {

  198. tid = id_list[i];

  199. if (toHide) {

  200. document.getElementById('div_'+tid).style.display = 'none'

  201. document.getElementById(tid).className = 'hiddenRow';

  202. }

  203. else {

  204. document.getElementById(tid).className = '';

  205. }

  206. }

  207. }

  208. function showTestDetail(div_id){

  209. var details_div = document.getElementById(div_id)

  210. var displayState = details_div.style.display

  211. // alert(displayState)

  212. if (displayState != 'block' ) {

  213. displayState = 'block'

  214. details_div.style.display = 'block'

  215. }

  216. else {

  217. details_div.style.display = 'none'

  218. }

  219. }

  220. function html_escape(s) {

  221. s = s.replace(/&/g,'&amp;');

  222. s = s.replace(/</g,'&lt;');

  223. s = s.replace(/>/g,'&gt;');

  224. return s;

  225. }

  226. /* obsoleted by detail in <div>

  227. function showOutput(id, name) {

  228. var w = window.open("", //url

  229. name,

  230. "resizable,scrollbars,status,width=800,height=450");

  231. d = w.document;

  232. d.write("<pre>");

  233. d.write(html_escape(output_list[id]));

  234. d.write("\n");

  235. d.write("<a href='javascript:window.close()'>close</a>\n");

  236. d.write("</pre>\n");

  237. d.close();

  238. }

  239. */

  240. --></script>

  241. %(heading)s

  242. %(report)s

  243. %(ending)s

  244. </body>

  245. </html>

  246. """

  247. # variables: (title, generator, stylesheet, heading, report, ending)

  248. # ------------------------------------------------------------------------

  249. # Stylesheet

  250. #

  251. # alternatively use a <link> for external style sheet, e.g.

  252. # <link rel="stylesheet" href="$url" type="text/css">

  253. STYLESHEET_TMPL = """

  254. <style type="text/css" media="screen">

  255. body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }

  256. table { font-size: 100%; }

  257. pre { }

  258. /* -- heading ---------------------------------------------------------------------- */

  259. h1 {

  260. font-size: 16pt;

  261. color: gray;

  262. }

  263. .heading {

  264. margin-top: 0ex;

  265. margin-bottom: 1ex;

  266. }

  267. .heading .attribute {

  268. margin-top: 1ex;

  269. margin-bottom: 0;

  270. }

  271. .heading .description {

  272. margin-top: 4ex;

  273. margin-bottom: 6ex;

  274. }

  275. /* -- css div popup ------------------------------------------------------------------------ */

  276. a.popup_link {

  277. }

  278. a.popup_link:hover {

  279. color: red;

  280. }

  281. .popup_window {

  282. display: none;

  283. position: relative;

  284. left: 0px;

  285. top: 0px;

  286. /*border: solid #627173 1px; */

  287. padding: 10px;

  288. background-color: #E6E6D6;

  289. font-family: "Lucida Console", "Courier New", Courier, monospace;

  290. text-align: left;

  291. font-size: 8pt;

  292. width: 500px;

  293. }

  294. }

  295. /* -- report ------------------------------------------------------------------------ */

  296. #show_detail_line {

  297. margin-top: 3ex;

  298. margin-bottom: 1ex;

  299. }

  300. #result_table {

  301. width: 80%;

  302. border-collapse: collapse;

  303. border: 1px solid #777;

  304. }

  305. #header_row {

  306. font-weight: bold;

  307. color: white;

  308. background-color: #777;

  309. }

  310. #result_table td {

  311. border: 1px solid #777;

  312. padding: 2px;

  313. }

  314. #total_row { font-weight: bold; }

  315. .passClass { background-color: #6c6; }

  316. .failClass { background-color: #c60; }

  317. .errorClass { background-color: #c00; }

  318. .passCase { color: #6c6; }

  319. .failCase { color: #c60; font-weight: bold; }

  320. .errorCase { color: #c00; font-weight: bold; }

  321. .hiddenRow { display: none; }

  322. .testcase { margin-left: 2em; }

  323. /* -- ending ---------------------------------------------------------------------- */

  324. #ending {

  325. }

  326. </style>

  327. """

  328. # ------------------------------------------------------------------------

  329. # Heading

  330. #

  331. HEADING_TMPL = """<div class='heading'>

  332. <h1>%(title)s</h1>

  333. %(parameters)s

  334. <p class='description'>%(description)s</p>

  335. </div>

  336. """ # variables: (title, parameters, description)

  337. HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>

  338. """ # variables: (name, value)

  339. # ------------------------------------------------------------------------

  340. # Report

  341. #

  342. REPORT_TMPL = """

  343. <p id='show_detail_line'>Show

  344. <a href='javascript:showCase(0)'>Summary</a>

  345. <a href='javascript:showCase(1)'>Failed</a>

  346. <a href='javascript:showCase(2)'>All</a>

  347. </p>

  348. <table id='result_table'>

  349. <colgroup>

  350. <col align='left' />

  351. <col align='right' />

  352. <col align='right' />

  353. <col align='right' />

  354. <col align='right' />

  355. <col align='right' />

  356. </colgroup>

  357. <tr id='header_row'>

  358. <td>Test Group/Test case</td>

  359. <td>Count</td>

  360. <td>Pass</td>

  361. <td>Fail</td>

  362. <td>Error</td>

  363. <td>View</td>

  364. </tr>

  365. %(test_list)s

  366. <tr id='total_row'>

  367. <td>Total</td>

  368. <td>%(count)s</td>

  369. <td>%(Pass)s</td>

  370. <td>%(fail)s</td>

  371. <td>%(error)s</td>

  372. <td>&nbsp;</td>

  373. </tr>

  374. </table>

  375. """ # variables: (test_list, count, Pass, fail, error)

  376. REPORT_CLASS_TMPL = r"""

  377. <tr class='%(style)s'>

  378. <td>%(desc)s</td>

  379. <td>%(count)s</td>

  380. <td>%(Pass)s</td>

  381. <td>%(fail)s</td>

  382. <td>%(error)s</td>

  383. <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>

  384. </tr>

  385. """ # variables: (style, desc, count, Pass, fail, error, cid)

  386. REPORT_TEST_WITH_OUTPUT_TMPL = r"""

  387. <tr id='%(tid)s' class='%(Class)s'>

  388. <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>

  389. <td colspan='5' align='center'>

  390. <!--css div popup start-->

  391. <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >

  392. %(status)s</a>

  393. <div id='div_%(tid)s' class="popup_window">

  394. <div style='text-align: right; color:red;cursor:pointer'>

  395. <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >

  396. [x]</a>

  397. </div>

  398. <pre>

  399. %(script)s

  400. </pre>

  401. </div>

  402. <!--css div popup end-->

  403. </td>

  404. </tr>

  405. """ # variables: (tid, Class, style, desc, status)

  406. REPORT_TEST_NO_OUTPUT_TMPL = r"""

  407. <tr id='%(tid)s' class='%(Class)s'>

  408. <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>

  409. <td colspan='5' align='center'>%(status)s</td>

  410. </tr>

  411. """ # variables: (tid, Class, style, desc, status)

  412. REPORT_TEST_OUTPUT_TMPL = r"""

  413. %(id)s: %(output)s

  414. """ # variables: (id, output)

  415. # ------------------------------------------------------------------------

  416. # ENDING

  417. #

  418. ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""

  419. # -------------------- The end of the Template class -------------------

  420. TestResult = unittest.TestResult

  421. class _TestResult(TestResult):

  422. # note: _TestResult is a pure representation of results.

  423. # It lacks the output and reporting ability compares to unittest._TextTestResult.

  424. def __init__(self, verbosity=1):

  425. TestResult.__init__(self)

  426. self.stdout0 = None

  427. self.stderr0 = None

  428. self.success_count = 0

  429. self.failure_count = 0

  430. self.error_count = 0

  431. self.verbosity = verbosity

  432. # result is a list of result in 4 tuple

  433. # (

  434. # result code (0: success; 1: fail; 2: error),

  435. # TestCase object,

  436. # Test output (byte string),

  437. # stack trace,

  438. # )

  439. self.result = []

  440. def startTest(self, test):

  441. TestResult.startTest(self, test)

  442. # just one buffer for both stdout and stderr

  443. self.outputBuffer = io.StringIO()

  444. stdout_redirector.fp = self.outputBuffer

  445. stderr_redirector.fp = self.outputBuffer

  446. self.stdout0 = sys.stdout

  447. self.stderr0 = sys.stderr

  448. sys.stdout = stdout_redirector

  449. sys.stderr = stderr_redirector

  450. def complete_output(self):

  451. """

  452. Disconnect output redirection and return buffer.

  453. Safe to call multiple times.

  454. """

  455. if self.stdout0:

  456. sys.stdout = self.stdout0

  457. sys.stderr = self.stderr0

  458. self.stdout0 = None

  459. self.stderr0 = None

  460. return self.outputBuffer.getvalue()

  461. def stopTest(self, test):

  462. # Usually one of addSuccess, addError or addFailure would have been called.

  463. # But there are some path in unittest that would bypass this.

  464. # We must disconnect stdout in stopTest(), which is guaranteed to be called.

  465. self.complete_output()

  466. def addSuccess(self, test):

  467. self.success_count += 1

  468. TestResult.addSuccess(self, test)

  469. output = self.complete_output()

  470. self.result.append((0, test, output, ''))

  471. if self.verbosity > 1:

  472. sys.stderr.write('ok ')

  473. sys.stderr.write(str(test))

  474. sys.stderr.write('\n')

  475. else:

  476. sys.stderr.write('.')

  477. def addError(self, test, err):

  478. self.error_count += 1

  479. TestResult.addError(self, test, err)

  480. _, _exc_str = self.errors[-1]

  481. output = self.complete_output()

  482. self.result.append((2, test, output, _exc_str))

  483. if self.verbosity > 1:

  484. sys.stderr.write('E ')

  485. sys.stderr.write(str(test))

  486. sys.stderr.write('\n')

  487. else:

  488. sys.stderr.write('E')

  489. def addFailure(self, test, err):

  490. self.failure_count += 1

  491. TestResult.addFailure(self, test, err)

  492. _, _exc_str = self.failures[-1]

  493. output = self.complete_output()

  494. self.result.append((1, test, output, _exc_str))

  495. if self.verbosity > 1:

  496. sys.stderr.write('F ')

  497. sys.stderr.write(str(test))

  498. sys.stderr.write('\n')

  499. else:

  500. sys.stderr.write('F')

  501. class HTMLTestRunner(Template_mixin):

  502. """

  503. """

  504. def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):

  505. self.stream = stream

  506. self.verbosity = verbosity

  507. if title is None:

  508. self.title = self.DEFAULT_TITLE

  509. else:

  510. self.title = title

  511. if description is None:

  512. self.description = self.DEFAULT_DESCRIPTION

  513. else:

  514. self.description = description

  515. self.startTime = datetime.datetime.now()

  516. def run(self, test):

  517. "Run the given test case or test suite."

  518. result = _TestResult(self.verbosity)

  519. test(result)

  520. self.stopTime = datetime.datetime.now()

  521. self.generateReport(test, result)

  522. # print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)

  523. print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))

  524. return result

  525. def sortResult(self, result_list):

  526. # unittest does not seems to run in any particular order.

  527. # Here at least we want to group them together by class.

  528. rmap = {}

  529. classes = []

  530. for n,t,o,e in result_list:

  531. cls = t.__class__

  532. if not cls in rmap:

  533. rmap[cls] = []

  534. classes.append(cls)

  535. rmap[cls].append((n,t,o,e))

  536. r = [(cls, rmap[cls]) for cls in classes]

  537. return r

  538. def getReportAttributes(self, result):

  539. """

  540. Return report attributes as a list of (name, value).

  541. Override this to add custom attributes.

  542. """

  543. startTime = str(self.startTime)[:19]

  544. duration = str(self.stopTime - self.startTime)

  545. status = []

  546. if result.success_count: status.append('Pass %s' % result.success_count)

  547. if result.failure_count: status.append('Failure %s' % result.failure_count)

  548. if result.error_count: status.append('Error %s' % result.error_count )

  549. if status:

  550. status = ' '.join(status)

  551. else:

  552. status = 'none'

  553. return [

  554. ('Start Time', startTime),

  555. ('Duration', duration),

  556. ('Status', status),

  557. ]

  558. def generateReport(self, test, result):

  559. report_attrs = self.getReportAttributes(result)

  560. generator = 'HTMLTestRunner %s' % __version__

  561. stylesheet = self._generate_stylesheet()

  562. heading = self._generate_heading(report_attrs)

  563. report = self._generate_report(result)

  564. ending = self._generate_ending()

  565. output = self.HTML_TMPL % dict(

  566. title = saxutils.escape(self.title),

  567. generator = generator,

  568. stylesheet = stylesheet,

  569. heading = heading,

  570. report = report,

  571. ending = ending,

  572. )

  573. self.stream.write(output.encode('utf8'))

  574. def _generate_stylesheet(self):

  575. return self.STYLESHEET_TMPL

  576. def _generate_heading(self, report_attrs):

  577. a_lines = []

  578. for name, value in report_attrs:

  579. line = self.HEADING_ATTRIBUTE_TMPL % dict(

  580. name = saxutils.escape(name),

  581. value = saxutils.escape(value),

  582. )

  583. a_lines.append(line)

  584. heading = self.HEADING_TMPL % dict(

  585. title = saxutils.escape(self.title),

  586. parameters = ''.join(a_lines),

  587. description = saxutils.escape(self.description),

  588. )

  589. return heading

  590. def _generate_report(self, result):

  591. rows = []

  592. sortedResult = self.sortResult(result.result)

  593. for cid, (cls, cls_results) in enumerate(sortedResult):

  594. # subtotal for a class

  595. np = nf = ne = 0

  596. for n,t,o,e in cls_results:

  597. if n == 0: np += 1

  598. elif n == 1: nf += 1

  599. else: ne += 1

  600. # format class description

  601. if cls.__module__ == "__main__":

  602. name = cls.__name__

  603. else:

  604. name = "%s.%s" % (cls.__module__, cls.__name__)

  605. doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""

  606. desc = doc and '%s: %s' % (name, doc) or name

  607. row = self.REPORT_CLASS_TMPL % dict(

  608. style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',

  609. desc = desc,

  610. count = np+nf+ne,

  611. Pass = np,

  612. fail = nf,

  613. error = ne,

  614. cid = 'c%s' % (cid+1),

  615. )

  616. rows.append(row)

  617. for tid, (n,t,o,e) in enumerate(cls_results):

  618. self._generate_report_test(rows, cid, tid, n, t, o, e)

  619. report = self.REPORT_TMPL % dict(

  620. test_list = ''.join(rows),

  621. count = str(result.success_count+result.failure_count+result.error_count),

  622. Pass = str(result.success_count),

  623. fail = str(result.failure_count),

  624. error = str(result.error_count),

  625. )

  626. return report

  627. def _generate_report_test(self, rows, cid, tid, n, t, o, e):

  628. # e.g. 'pt1.1', 'ft1.1', etc

  629. has_output = bool(o or e)

  630. tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1,tid+1)

  631. name = t.id().split('.')[-1]

  632. doc = t.shortDescription() or ""

  633. desc = doc and ('%s: %s' % (name, doc)) or name

  634. tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL

  635. # o and e should be byte string because they are collected from stdout and stderr?

  636. if isinstance(o,str):

  637. # TODO: some problem with 'string_escape': it escape \n and mess up formating

  638. # uo = unicode(o.encode('string_escape'))

  639. # uo = o.decode('latin-1')

  640. uo = e

  641. else:

  642. uo = o

  643. if isinstance(e,str):

  644. # TODO: some problem with 'string_escape': it escape \n and mess up formating

  645. # ue = unicode(e.encode('string_escape'))

  646. # ue = e.decode('latin-1')

  647. ue = e

  648. else:

  649. ue = e

  650. script = self.REPORT_TEST_OUTPUT_TMPL % dict(

  651. id = tid,

  652. output = saxutils.escape(str(uo)+ue),

  653. )

  654. row = tmpl % dict(

  655. tid = tid,

  656. Class = (n == 0 and 'hiddenRow' or 'none'),

  657. style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),

  658. desc = desc,

  659. script = script,

  660. status = self.STATUS[n],

  661. )

  662. rows.append(row)

  663. if not has_output:

  664. return

  665. def _generate_ending(self):

  666. return self.ENDING_TMPL

  667. ##############################################################################

  668. # Facilities for running tests from the command line

  669. ##############################################################################

  670. # Note: Reuse unittest.TestProgram to launch test. In the future we may

  671. # build our own launcher to support more specific command line

  672. # parameters like test title, CSS, etc.

  673. class TestProgram(unittest.TestProgram):

  674. """

  675. A variation of the unittest.TestProgram. Please refer to the base

  676. class for command line parameters.

  677. """

  678. def runTests(self):

  679. # Pick HTMLTestRunner as the default test runner.

  680. # base class's testRunner parameter is not useful because it means

  681. # we have to instantiate HTMLTestRunner before we know self.verbosity.

  682. if self.testRunner is None:

  683. self.testRunner = HTMLTestRunner(verbosity=self.verbosity)

  684. unittest.TestProgram.runTests(self)

  685. main = TestProgram

  686. ##############################################################################

  687. # Executing this module from the command line

  688. ##############################################################################

  689. if __name__ == "__main__":

  690. main(module=None)

  • 使用

接下来,小编将以登录网易邮箱为例,生成一份最基础的测试报告。小伙伴们copy走代码验证时一定记得修改账号密码哦。

目录结构如下:

 将HTMLTestRunner.py文件存放到package包下,将test_login.py存放到testcase目录下,用于编写测试用例,建立testreport包,用于存放测试报告,在email目录下建立run_test.py,用于执行测试

HTMLTestRunner的使用方法在代码注释中介绍和解释

【test_login.py】

 
  1. from selenium import webdriver

  2. import unittest,time

  3. class Login(unittest.TestCase):

  4. def setUp(self):

  5. #打开百度,搜索“163网易邮箱”,登录

  6. driver=webdriver.Firefox()

  7. driver.implicitly_wait(5)

  8. self.driver=driver

  9. driver.get("https://www.baidu.com/")

  10. driver.find_element_by_id("kw").send_keys("163邮箱登录")

  11. driver.find_element_by_id("su").click()

  12. name = driver.find_element_by_id("op_email3_username")

  13. password = driver.find_element_by_class_name("op_email3_password")

  14. login = driver.find_element_by_css_selector(".c-btn")

  15. #如下操作可以使setUp中的变量被其他模块调用

  16. self.name=name

  17. self.password=password

  18. self.login=login

  19. def tearDown(self):

  20. self.driver.quit()

  21. def switch_window(self):

  22. #切换窗口

  23. for handle in self.driver.window_handles:

  24. self.driver.switch_to.window(handle)

  25. #j增加等待时间,可以提高测试用例执行的健壮性

  26. time.sleep(2)

  27. time.sleep(3)

  28. #成功登录

  29. def test_right_login(self):

  30. #账号密码自行填写

  31. self.name.send_keys("xxx")

  32. self.password.send_keys("xxx")

  33. self.login.click()

  34. self.switch_window()

  35. #t通过新窗口的title验证用例是否通过

  36. self.assertEqual(self.driver.title,"网易邮箱6.0版","登录失败")

  37. #密码为空登录

  38. def test_null_psw_login(self):

  39. self.name.send_keys("xxx")

  40. time.sleep(3)

  41. self.login.click()

  42. self.switch_window()

  43. # t通过新窗口的title验证用例是否通过

  44. self.assertEqual(self.driver.title,"网易帐号中心 > 用户验证","未跳转至用户验证界面")

【run_test.py】

 
  1. #导入HTMLTestRunner的包

  2. from package import HTMLTestRunner

  3. #导入test_login的包,执行测试用例时需使用

  4. from testcase.test_login import *

  5. #定义要执行的测试用例的路径

  6. test_dir = './testcase'

  7. #定义要执行的测试用例的路径和名称格式

  8. #test_*.py的意思是,./testcase路径下文件名称格式为test_*.py的文件,*为任意匹配,路径下有多少的test_*.py格式的文件,就依次执行几个

  9. discover = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')

  10. #定义测试报告的名称和存储位置

  11. filename = './testreport/loginReport.html'

  12. #开始执行

  13. if __name__ == '__main__':

  14. suit=unittest.TestSuite()

  15. suit.addTest(Login("test_right_login"))

  16. suit.addTest(Login("test_null_psw_login"))

  17. #以wb(可写的二进制文件)形式,打开文件,若文件不存在,则先执行创建,再执行打开

  18. fp = open(filename, 'wb')

  19. #调用HTMLTestRunner生成报告

  20. runner = HTMLTestRunner.HTMLTestRunner(

  21. # 指定测试报告的文件

  22. stream=fp,

  23. # 测试报告的标题

  24. title=u"登录网易邮箱测试报告",

  25. # 测试报告的副标题

  26. description=u'用例执行情况(win7 64位)'

  27. )

  28. #执行用例

  29. runner.run(discover)

  • 报告展示

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

  视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/356097.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode.209. 长度最小的子数组

题目 题目链接 分析 本题的题意就是让我们找最短的子数组和 > target 的子数组的长度。 首先最能想到的就是暴力方法&#xff0c;外层循环以数组每一个元素都作为起点&#xff0c;内存循环累加元素&#xff0c;当大于等于 target 的时候记录当前元素个数&#xff0c;更新…

.net winform 使用NModbus4建立 modbus tcp通讯

1、使用nuget引入NModbus4。 2、编写TCP访问modbus的方法 public void StartTcpClient(string ipstr,string portstr,ushort adress, ushort readLenth) { try { IPAddress myIP IPAddress.Parse(ipstr); in…

GM/T 0018-2012 设备接口描述笔记

GM/T 0018-2012 设备接口描述笔记 文章目录 GM/T 0018-2012 设备接口描述笔记6. 设备接口描述6.1 密码设备应用接口在公钥密码基础设施应用技术体系框架中的位置6.2 设备管理类函数6.3 密钥管理类函数6.4 非对称算法运算类函数6.5 对称算法运算类函数6.6 杂凑运算类函数6.7 用户…

单调队列优化DP模型整理

135. 最大子序和&#xff08;活动 - AcWing&#xff09; 找一个长度不超过m的连续子序列&#xff0c;但是并未指定这个子序列的长度&#xff0c;所以长度就有很多种选择&#xff0c;要获取任意一段长度的序列的区间和&#xff0c;那么显然要用到前缀和。然后我们来考虑&#xf…

leetcode hot100跳跃游戏

在本题中&#xff0c;我们要模仿整个跳跃过程&#xff0c;当前位置数组元素为nums[i]&#xff0c;那我们就最大能往后跳nums[i]步&#xff0c;可以小于等于这个数。如果我们直接遍历数组&#xff0c;那么我们需要每一步都控制跳跃0——nums[i]步&#xff0c;这样不可能实现。 …

ITSS证书:点亮职业发展的明灯

&#x1f4da;ITSS是中国电子技术标准化研究院推出的权威认证&#xff0c;涵盖IT服务工程师和IT服务经理的系列培训&#xff0c;旨在培养具备专业知识和技能的IT服务人才。 &#x1f31f;证书优势&#xff1a; 1️⃣提升问题解决能力&#xff1a;通过学习ITSS方法论和实践经验&…

【网络】WireShark过滤 | WireShark实现TCP三次握手和四次挥手

目录 一、开启WireShark的大门 1.1 WireShark简介 1.2 常用的Wireshark过滤方式 二、如何抓包搜索关键字 2.1 协议过滤 2.2 IP过滤 ​编辑 2.3 过滤端口 2.4 过滤MAC地址 2.5 过滤包长度 2.6 HTTP模式过滤 三、ARP协议分析 四、WireShark之ICMP协议 五、TCP三次握…

Python笔记15-实战小游戏飞机大战(中)

文章目录 创建第一个敌机创建一群敌机创建多行敌机让敌机移动射杀敌机生成新的敌机群结束游戏有敌机到达屏幕底端游戏结束 在上一篇基础上继续 本示例源码地址 点击下载 创建第一个敌机 在屏幕上放置外星人与放置飞船类似。每个外星人的行为都由Alien 类控制&#xff0c;我们…

C++面试宝典第25题:阶乘末尾零的个数

题目 给定一个整数n,返回n!(n的阶乘)结果尾数中零的个数。 示例 1: 输入:3 输出:0 解释:3! = 6,尾数中没有零。 示例 2: 输入:5 输出:1 解释:5! = 120,尾数中有1个零。 解析 这道题主要考察应聘者对于数学问题的分析和理解能力,以及在多个解决方案中,寻求最优…

从零学习Linux操作系统 第二十部分 mariadb数据库的管理

一、对于数据库的基本介绍 1.什么是数据库 数据库就是个高级的表格软件 2.常见数据库 Mysql Oracle mongodb db2 sqlite sqlserver … 3.Mysql (SUN -----> Oracle) 4.mariadb (Mysql的一种&#xff09; 数据库中的常用名词 1.字段 &#xff1a;表格中的表头 2.表 &…

MySQL原理(一)架构组成(1)物理文件组成

目录 一、日志文件 1、错误日志 Error Log 1.1、作用&#xff1a; 1.2、开启关闭&#xff1a; 1.3、使用 2、二进制日志 Binary Log & Binary Log Index 2.1、作用&#xff1a; 2.2、开启关闭&#xff1a; 2.3、Binlog还有一些附加选项参数 &#xff08;1&#x…

嵌入式C++必知必会之基础知识-常用关键字(1)

温故而知新 Hello&#xff0c;大家好&#xff01;我是木荣。温故而知新&#xff0c;可以为师矣。作为一名攻城狮&#xff0c;扎实的基本功是解决问题及完成工作中任务的重要前提。没有良好的基本功作为铺垫&#xff0c;一味的追求知识的宽度是毫无意义&#xff0c;知其然更要知…

野火霸道V2学习笔记

STM32F103学习笔记 STM32F103学习笔记说明基础配置配置KeilMDK配置串口下载程序美化Keil界面配置VScode 理论知识STM32命名方式例子 置位与清零GPIOGPIO简介GPIO和引脚的区别引脚的分类 GPIO 框图讲解保护二极管推挽输出推挽输出的含义推挽输出的原理推挽输出的特点推挽输出的应…

力扣题目训练(3)

2024年1月27日力扣题目训练 2024年1月27日力扣题目训练290. 单词规律292. Nim 游戏303. 区域和检索 - 数组不可变91. 解码方法92. 反转链表 II41. 缺失的第一个正数 2024年1月27日力扣题目训练 2024年1月27日第三天编程训练&#xff0c;今天主要是进行一些题训练&#xff0c;包…

大数据Doris(六十一):SQL函数之Bitmap函数

文章目录 SQL函数之Bitmap函数 一、BITMAP_AND(BITMAP lhs, BITMAP rhs)

用C#实现最小二乘法(用OxyPlot绘图)

最小二乘法介绍✨ 最小二乘法&#xff08;Least Squares Method&#xff09;是一种常见的数学优化技术&#xff0c;广泛应用于数据拟合、回归分析和参数估计等领域。其目标是通过最小化残差平方和来找到一组参数&#xff0c;使得模型预测值与观测值之间的差异最小化。 最小二…

小白都会的幻兽帕鲁服务器搭建教程(详细图文)

​  简介&#xff1a;由于幻兽帕鲁游戏的火爆&#xff0c;导致其官方服务器频现游戏卡顿掉线&#xff0c;为了能够正常流畅的体验幻兽帕鲁&#xff0c;有不少人都搭建了幻兽帕鲁服务器(私服)&#xff0c;网上虽然也有很多幻兽帕鲁服务器搭建教程&#xff0c;但内容专业性有点…

qq通讯录怎么关闭?QQ好友删除了怎么恢复?

在QQ中&#xff0c;通讯录是我们管理好友和进行聊天的重要工具&#xff0c;但有时候我们可能需要一些隐私保护&#xff0c;不让一些用户通过手机通讯录添加自己。如果您正在思考qq通讯录怎么关闭以及恢复意外删除的好友&#xff0c;本文将为您详细介绍如何关闭QQ通讯录和恢复被…

Java开发秘籍:实战公众号开发指南

目录 第一章&#xff1a;公众号开发简介 1.1 什么是公众号开发 1.2 公众号开发的意义和应用场景 1.3 公众号开发的技术要求和基础知识 第二章&#xff1a;搭建公众号开发环境 2.1 配置Java开发环境 2.2 下载和安装开发工具 2.3 设置公众号开发所需的配置文件 第三章&a…

2024阿里云和腾讯云的第一战打响:搭建《幻兽帕鲁》私服游戏

为了搭建《幻兽帕鲁》游戏私服&#xff0c; 2024年阿里云 VS 腾讯云的第一场战争开始了…… 事情是这样的&#xff1a; 1月19日&#xff0c;最离谱新游 《幻兽帕鲁》突然爆火了&#xff0c;这是一款日本开发商展耗费4年开发的冒险类游戏&#xff0c;这款戏一推出就迅速俘获了…