你的第一個接口測試:Python 接口測試

前言:

    首先咱們先明確一個概念,什麼叫接口、什麼叫接口測試javascript

接口的全稱叫【Application Programming Interface 又叫API】,是提供應用程序與開發人員基於某軟件或硬件得以訪問一組例程的能力,而又無需訪問源碼,或理解內部工做機制的細節。css

接口測試是測試系統組件間接口的一種測試。接口測試主要用於檢測外部系統與系統之間以及內部各個子系統之間的交互點。測試的重點是要檢查數據的交換,傳遞和控制管理過程,以及系統間的相互邏輯依賴關係等。html

(本章不會講POSTMan、Jmeter等工具,直接入直路,工具畢竟是有依賴性)java

一. 前期準備

    一、準備工具:

本文版權歸做者和博客園共有,原創做者:http://www.cnblogs.com/BenLam,未經做者贊成必須在文章頁面給出原文鏈接。python

        一、Python3.7 + notepad (Windows自帶的文本文檔工具)|(Linux配合vi編輯也能夠)git

        二、Request庫:下載地址 或經過Pip命令安裝lib庫:github

pip install requests

  三、urllib、Unittestjson

  四、HTMLTestRunner獲取方法【打開下面摺疊代碼,另存爲HTMLTestRunner_Py3.Py文件】
api

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @file       : HTMLTestRunner_Py3.x.py
# @Date    : 2018-06-02 14:14:46
# @Author  : Destroyers (https://github.com/lguobin)
# @Link    : https://github.com/lguobin
# @Version : $1.0$

"""
A TestRunner for use with the Python unit testing framework. It
generates a HTML report to show the result at a glance.
 
The simplest way to use this is to invoke its main method. E.g.
 
    import unittest
    import HTMLTestRunner
 
    ... define your tests ...
 
    if __name__ == '__main__':
        HTMLTestRunner.main()
 
 
For more customization options, instantiates a HTMLTestRunner object.
HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
 
    # output to a file
    fp = file('my_report.html', 'wb')
    runner = HTMLTestRunner.HTMLTestRunner(
                stream=fp,
                title='My unit test',
                description='This demonstrates the report output by HTMLTestRunner.'
                )
 
    # Use an external stylesheet.
    # See the Template_mixin class for more customizable options
    runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
 
    # run the test
    runner.run(my_test_suite)
 
 
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
 
* Redistributions of source code must retain the above copyright notice,
  this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
  used to endorse or promote products derived from this software without
  specific prior written permission.
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
 
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
 
__author__ = "Wai Yip Tung"
__version__ = "0.8.2.1" # lguobin change Python 3.x
 
 
"""
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 = e
        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 Code

 

    二、示例接口說明:

        演示接口:豆瓣電影搜索服務器

        文檔說明:https://developers.douban.com/wiki/?title=movie_v2#search

        api請求地址:https://api.douban.com/v2/movie/search?

        一、按片名搜索:api請求地址 + q=鋼鐵俠

        二、按類型搜索:api請求地址 + tag=喜劇

        說明:在實際工做中接口說明開發會提供交到咱們手上的,不用擔憂。

    三、接口踩點:

        一、拿到 api 先彆着急測試,經過手工點點可否正常使用

        二、編寫對應的測試用例,例如上邊測試按電影的片名和類型搜索:(演示)

testdemo1: https://api.douban.com/v2/movie/search?q=鋼鐵俠
testdemo2: https://api.douban.com/v2/movie/search?q=喜劇
testdemo3: https://api.douban.com/v2/movie/search?q=恐龍恐怖片
testdemo4: https://api.douban.com/v2/movie/search?q=

二. 接口調用(我會經過使用兩個Python庫來實現與接口通信)

    一、urllib發起請求:

本文版權歸做者和博客園共有,原創做者:http://www.cnblogs.com/BenLam,未經做者贊成必須在文章頁面給出原文鏈接。

import urllib.request
params = "TomCruise" # 湯姆·克魯斯的電影
url = "https://api.douban.com/v2/movie/search?"
html = urllib.request.urlopen(url + params) # 經過urllib發起請求
html = html.read().decode("utf-8")
print(html)

    二、Requests發起請求:

import requests
params = "TomCruise" # 湯姆·克魯斯的電影
url = "https://api.douban.com/v2/movie/search?"
req = requests.get(url) # 經過requests 發起請求
print(req)

打印結果:

{
    "count": 20,
    "start": 0,
    "total": 40,
    "subjects": [{
        "rating": {
            "max": 10,
            "average": 7.7,
            "stars": "40",
            "min": 0
        },
        "genres": ["劇情", "懸疑", "驚悚"],
        "title": "大開眼戒",
        "casts": [{
            "alt": "https:\/\/movie.douban.com\/celebrity\/1054435\/",
            "avatars": {
                "small": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p567.jpg",
                "large": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p567.jpg",
                "medium": "https://img1.doubanio.com\/view\/celebrity\/s_ratio_celebrity\/public\/p567.jpg"
            },
            "name": "湯姆·克魯斯",
            "id": "1054435"  
            .......... 省略不少東西 ...............
    "title": "搜索 \"TomCruise\" 的結果"
}
json內容太長了,濃縮了重點

這樣就完成了咱們第一次請求api內容。

三. 結果校驗

    一、Python自身特性斷言:

本文版權歸做者和博客園共有,原創做者:http://www.cnblogs.com/BenLam,未經做者贊成必須在文章頁面給出原文鏈接。

經過Requests請求,用Python IF 判斷 HTTP 狀態碼(只有驗證碼是200就經過)

import requests
actor_name = "q=TomCruise" # 湯姆·克魯斯的電影
url = "https://api.douban.com/v2/movie/search?"
req = requests.get(url + actor_name) # 經過requests 發起請求
if req.status_code == 200:
    # req.status_code 就是HTTP的狀態碼
    print("測試經過,請求已成功,請求所但願的響應頭或數據體將隨此響應返回")
elif req.status_code == 301:
    # 有些重定向的301 能夠用 elif 再判斷
    print("重定向過,被請求的資源已永久移動到新位置,而且未來任何對此資源的引用都應該使用本響應返回的若干個 URI 之一")
elif req.status_code == 500:
    print("服務器遇到了一個不曾預料的情況,致使了它沒法完成對請求的處理。")
elif req.status_code == 404:
    print("請求失敗,請求所但願獲得的資源未被在服務器上發現。")
else:
    print("請求出現意外,未找到合適的 HTTP Status_code!")

# 好處:經過Python自身的特性來判斷返回的狀態碼是否正確
# 壞處:判斷過於簡單,沒法精準檢查接口

 用Python IF 判斷增強版

import requests,json
actor_name = "q=TomCruise" # 演員名
url = "https://api.douban.com/v2/movie/search?"
try:
    req = requests.get(url + actor_name) # 經過requests 發起請求
    req = json.dumps(req.json(), ensure_ascii=False, indent=4)
except requests.RequestException as e:
    raise
name = "湯姆·克魯斯"
if name in req: # 判斷返回的數據是否包括咱們搜索的條件,這裏咱們搜索是「湯姆克魯斯」
    print("經過測試")
else:
    print("測試不經過")

    2.二、Unittest斷言:

 使用unittest進行判斷測試結果是否包含咱們想要的信息

import requests,json
import unittest

class demo(unittest.TestCase):
    def setUp(self):
        # 初始化信息
        self.url = "https://api.douban.com/v2/movie/search?"
        self.actor_name = "q=TomCruise"
        self.style_tag = "tag=愛情"
        self.name = "湯姆·克魯斯"
        self.action = "動做"

    def test_actor(self):
        # 執行測試
        req = requests.get(self.url + self.actor_name) # 經過requests 發起請求
        req = json.dumps(req.json(), ensure_ascii=False, indent=4)
        self.assertIn(self.name, req, msg=None) # 檢查有沒有 "湯姆·克魯斯"這個演員

    def test_style_tag(self):
        # 執行測試
        req = requests.get(self.url + self.actor_name) # 經過requests 發起請求
        req = json.dumps(req.json(), ensure_ascii=False, indent=4)
        self.assertIn(self.action, req, msg=None) # 檢查有沒有 "動做片"

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

這是經過測試的運行結果:

..
----------------------------------------------------------------------
Ran 2 tests in 1.610s

OK
[Run in 1.9s]

若是測試結果不經過顯示F (以下):

import unittest

class demo(unittest.TestCase):
    def test_Error(self):
        a = "1"
        b = "2"
        self.assertEqual(a,b) # 檢查 a 和 b 是否相同

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

# ==============================
#       測試結果
# ==============================
F
======================================================================
FAIL: test_Error (__main__.demo)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\1\demo.py", line 45, in test_Error
    self.assertEqual(a,b) # 檢查 a 和 b 是否相同
AssertionError: '1' != '2'
- 1
+ 2

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
[Run in 0.3s with exit code 1]

四. 報告生成

本文版權歸做者和博客園共有,原創做者:http://www.cnblogs.com/BenLam,未經做者贊成必須在文章頁面給出原文鏈接。

經過HTMLTestRunner快速生成:

import unittest
import HTMLTestRunner
import time
class Test(unittest.TestCase):
    def test_case1(self):
        # 填寫你的測試用例

if __name__ == '__main__':
    # 獲取當前時間
    now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
    # html存放文件路徑
    Report_path = (Report_path, "Report_"+now+".html")
    # 打開一個文件,將result寫入此file中
    file = open(Report_path, "wb")
    runner = HTMLTestRunner.HTMLTestRunner(stream=file, title=u'接口測試報告:', description=u'用例執行狀況:')
    # 這裏填寫你的測試用例名
    runner.run(all_case())
    file.close()

五. 郵件推送測試報告

本文版權歸做者和博客園共有,原創做者:http://www.cnblogs.com/BenLam,未經做者贊成必須在文章頁面給出原文鏈接。

經過郵件發送接口測試結果

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @file       : 郵件.py
# @Date    : 2018-06-02 14:14:46
# @Author  : Destroyers (https://github.com/lguobin)
# @Link    : https://github.com/lguobin
# @Version : $1.0$

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
 
class Myemail:
    def __init__(self,mail_list,mail_title):
        self.mail_list = mail_list
        self.mail_title = mail_title
        self.mail_host = "smtp.163.com"
        self.mail_user = "你本身郵箱@163.com"
        self.mail_pass = "你的密碼"
        self.mail_postfix = "163.com"

    def SendMail(self):
        info = self.mail_user + "<" + self.mail_user + "@" + self.mail_postfix + ">"
        msg = MIMEMultipart()
        msg['Subject'] = '接口測試報告'
        msg['From'] = info
        msg['To'] = ";".join(self.mail_list)

        with open("/temp/Report.html", 'r') as f:
            Report_content = f.read().encode("utf-8")
        puretext = MIMEText(Report_content, 'html', 'utf-8')
        msg.attach(puretext)
    try:
        s = smtplib.SMTP() #建立郵件服務器對象
        s.connect(self.mail_host) #鏈接到指定的smtp服務器。參數分別表示smpt主機和端口
        s.login(self.mail_user, self.mail_pass) #登陸到你郵箱
        s.sendmail(me, self.mail_list, msg.as_string()) #發送內容
        s.close()
        return True
    except Exception, e:
        print str(e)
        return False

if __name__ == '__main__':
    # 郵件登陸
    mailto_list = ["你領導的郵件@163,com","aaaa@163.com"]
    mail_title = '接口測試報告:'
    test = Myemail(mailto_list, mail_title)
    print(test.SendMail())

 

本章節 End————下章再會

相關文章
相關標籤/搜索