python之UnittTest模块

一. UnitTest单元测试框架

1.1 unittest概述

unittest原名为PyUnit,是由java的JUnit衍生而来。单元测试是对程序中最小的可测试模块(函数)来进行测试;对于单元测试,需要设置预先条件,对比预期结果和实际结果。

unittest有四个重要的面向对象概念:

1)test fixture:这个概念主要处理测试环境的搭建和清理。很多时候我们在进行测试的时候需要搭建合适的环境,例如创建目录、创建数据库等,而在测试完毕后这些环境又不再需要了。test fixture可以帮我们很好的处理这些事情。

2)test case: 既然要进行测试,测试用例当然是最重要的,每一项测试内容都是一个test case。测试用例是指一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。单元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。

3)test suite:我们当然不希望只能一项项的进行测试,最好是将要测试的项目放在一起。test suite相当于test case的集合,当然test suite也能嵌套在test suite中。

4)test runner:顾名思义,这个概念负责执行测试并控制结果输出。

官方文档网址:https://doc.codingdict.com/python_352/library/unittest.html

1.2 unittest的属性和子类

<1>unittest的属性如下:

['BaseTestSuite', 'FunctionTestCase', 'SkipTest', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'TestSuite', 'TextTestResult', 'TextTestRunner', '_TextTestResult','__all__', '__builtins__',
'__doc__', '__file__', '__name__', '__package__', '__path__', '__unittest','case', 'defaultTestLoader', 'expectedFailure', 'findTestCases', 'getTestCaseNames', 'installHandler', 'loader', 'main',
'makeSuite', 'registerResult', 'removeHandler', 'removeResult', 'result', 'runner', 'signals', 'skip', 'skipIf', 'skipUnless', 'suite', 'util']

unittest类常用属性和方法说明:
1)unittest.TestCase:  TestCase类,所有测试用例类继承的基本类。用法:class BaiduTest(unittest.TestCase):
2)unittest.main():  使用它可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行他们。执行方法的默认顺序是:根据ASCII码的顺序加载测试用例,数字与字母的顺序为: 0-9,A-Z,a-z。所以以A开头的测试用例方法会优先执行,以a开头会后执行。
3)unittest.TestSuite():  unittest框架的TestSuite()类是用来创建测试套件的。
4)unittest.TextTestRunner():  unittest框架的TextTestRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件。
5)unittest.defaultTestLoader():  defaultTestLoader()类,通过该类下面的discover()方法可自动根据测试目录start_dir匹配查找测试用例文件(test*.py),并将查找到的测试用例组装到测试套件,因此可以直接通过run()方法执行discover。用法如下:
discover=unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py')
6)unittest.skip():  装饰器,当运行用例时,有些用例可能不想执行,可用装饰器暂时屏蔽该条测试用例。一种常见的用法:想调试某一个测试用例,而先屏蔽其他用例时就可以用装饰器屏蔽。
@unittest.skip(reason): skip(reason)装饰器:  无条件跳过装饰的测试,并说明跳过测试的原因。

@unittest.skipIf(reason): skipIf(condition,reason)装饰器:  条件为真时,跳过装饰的测试,并说明跳过测试的原因。

@unittest.skipUnless(reason): skipUnless(condition,reason)装饰器:条件为假时,跳过装饰的测试,并说明跳过测试的原因。

@unittest.expectedFailure(): expectedFailure()测试标记为失败。

<2>unittest的TestCase类的属性如下:

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__','__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_addSkip', '_baseAssertEqual', '_classSetupFailed', '_deprecate', '_diffThreshold', '_formatMessage',
'_getAssertEqualityFunc','_truncateMessage', 'addCleanup', 'addTypeEqualityFunc', 'assertAlmostEqual', 'assertAlmostEquals', 'assertDictContainsSubset', 'assertDictEqual', 'assertEqual',
'assertEquals', 'assertFalse', 'assertGreater','assertGreaterEqual', 'assertIn', 'assertIs', 'assertIsInstance', 'assertIsNone', 'assertIsNot', 'assertIsNotNone', 'assertItemsEqual', 'assertLess',
'assertLessEqual', 'assertListEqual', 'assertMultiLineEqual','assertNotAlmostEqual', 'assertNotAlmostEquals', 'assertNotEqual', 'assertNotEquals', 'assertNotIn', 'assertNotIsInstance',
'assertNotRegexpMatches', 'assertRaises', 'assertRaisesRegexp', 'assertRegexpMatches','assertSequenceEqual', 'assertSetEqual', 'assertTrue', 'assertTupleEqual', 'assert_', 'countTestCases', 'debug',
'defaultTestResult', 'doCleanups', 'fail', 'failIf', 'failIfAlmostEqual', 'failIfEqual','failUnless', 'failUnlessAlmostEqual', 'failUnlessEqual', 'failUnlessRaises', 'failureException', 'id','longMessage',
'maxDiff', 'run','setUp','setUpClass', 'shortDescription', 'skipTest', 'tearDown','tearDownClass']

TestCase类常用属性和方法说明:

1)setUp()  方法用于测试用例执行前的初始化工作。如测试用例中需要访问数据库,可以在setUp中建立数据库连接并进行初始化。如测试用例需要登录web,可以先实例化浏览器。在所有的测试方法调用之前调用(自动调用),用来测试fixture,除了AssertionError或SkipTest之外,该方法抛出的异常都视为error,而不是测试不通过。

2)tearDown()  方法用于测试用例执行之后的善后工作。如关闭数据库连接。关闭浏览器。tearDown() 清理函数,在所有的测试方法调用之后调用(自动调用),无参数,无返回值。测试方法抛出异常,该方法也正常调用,该方法抛出的异常都视为error,而不是测试不通过。只有setUp()调用成功,该方法才会被调用。没有默认的实现。通过setup 和 tearDown组装一个module成为一个固定的测试装置。注意:如果setup运行抛出错误,测试用例代码不会执行。但是,如果setup执行成功,不管测试用例是否执行成功都会执行teardown。

3)setUpClass()  类初始化方法,在单个类中的所有测试方法调用之前调用,setUpClass作为唯一的参数被调用时,必须使用classmethod()作为装饰器。

4)tearDownClass()  类清理方法,在单个类中的所有测试方法调用之后调用,tearDownClass作为唯一的参数被类调用,必须使用classmethod()作为装饰器。

复制代码
import unittest 
class Test(unittest.TestCase): 
    @classmethod 
    def setUpClass(cls): #这里的cls是当前类的对象 
        cls._connection = createExpensiveConnectionObject()         
    @classmethod 
    def tearDownClass(cls): 
        cls._connection.destroy()     
View Code 
复制代码

注意: 用setUp与setUpClass、teardown 和tearDownClass的区别

setup():每个测试case运行前运行
teardown():每个测试case运行完后执行
setUpClass():必须使用@classmethod 装饰器,有case运行前只运行一次
tearDownClass():必须使用@classmethod装饰器,所有case运行完后只运行一次

5)run(result=None)运行测试,将测试结果传递到TestResult对象中,如果结果被省略或者没有,则创建一个临时结果对象(通过调用defaultTestResult()方法)并使用。

6)skipTest(reason):在测试方法或setUp调用该方法可跳过当前测试

7)debug():以不采集测试结果方式运行测试

8)shortDescription():返回一行描述的测试结果信息

9)assert*()  一些断言方法:在执行测试用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果和预期结果是否相等决定的。

TestCase类提供的常用断言方法总结如下:

复制代码
Methed 
#基本断言                                                              Checks that                                                            New in python
1.assertEqual(first, second, msg=None)                                 a == b             
2.assertNotEqual(first, second, msg=None)                              a != b
3.assertTrue(expr, msg=None)                                           bool(expr) is True
4.assertFalse(expr, msg=None)                                          bool(expr) is False
5.assertIsNot(a,b, msg=None)                                          a is not b                                                              3.1
6.assertIs(a,b, msg=None)                                             a is b                                                                  3.1
7.assertIsNone(x, msg=None)                                           x is None                                                               3.1
8.assertIsNotNone(x, msg=None)                                        x is Not None                                                           3.1
9.assertIn(a,b, msg=None)                                             a in b                                                                  3.1
10.assertNotIn(a,b, msg=None)                                          a not in b                                                              3.1
11.assertIsInstance(a,b, msg=None)                                     isinstance(a,b)                                                         3.2
12.assertNotIsInstance(a,b, msg=None)                                  not isinstance(a,b)                                                     3.2
13.assertRaises(exception, callable, *args, **kwds)                     fun(*args, **kwds) raises exc
14.assertRaisesRegex(exception, regex, callable, *args, **kwds)          fun(*args, **kwds) raises exc and the message matches regex r                        3.1
15.assertWarns(warn, fun, *args, **kwds)                                 fun(*args, **kwds) raises warn                                                        3.2
16.assertWarnsRegex(warn, r, fun, *args, **kwds)                         fun(*args, **kwds) raises warn and the message matches regex r                        3.2
17.assertLogs(logger, level)                                             一个上下文管理器,用于测试至少一条消息是否记录在logger或其中一个子项上,至少包含给定的级别。                    3.4

#比较断言
1.assertAlmostEqual(a, b, places=7, msg=None, delta=None)               计算差值a-b后四舍五入到给定的小数点后places指定的十进制位(默认值7),再与零比较。相当于round(a-b, 7) == 0
#在上述函数中,如果delta指定了值,则a和b之间的差值必须≤delta

2.assertNotAlmostEqual(a, b, places=7, msg=None, delta=None)                round(a-b, 7) != 0
#在上述函数中,如果delta指定了值,则a和b之间的差值必须 >delta

3.assertGreater(a, b, msg=None)                                             a > b                                                                                           3.1
4.assertGreaterEqual(first, second, msg=None)                    a >= b                                                                                          3.1
5.assertLess(a, b, msg=None)                                                a < b                                                                                           3.1
6.assertLessEqual(a, b, msg=None)                                           a <= b                                                                                          3.1

7.assertRegex(text, regex, msg=None)                                        r.search(text)                                                                                  3.2                      
8.assertNotRegex(text, regex, msg=None                                      not r.search(text)                                                                              3.2
#测试regex搜索是否与文本匹配(或不匹配)。如果失败,错误消息将包括模式和文本(或模式和文本的意外匹配的部分)。regex可以是正则表达式对象或包含适用于re.search()的正则表达式的字符串。
9.assertCountEqual(first, second, msg=None)                                 测试序列1包含序列2相同的元素,不需要考虑元素的顺序。
#注意当序列1、2元素不同时,将生成列出序列之间差异的错误消息。该方法不会忽略重复元素,他比较的是每个元素的出现次数是否相等,类似于assertEqual(Counter(list(first)), Counter(list(second)))

10.assertMultiLineEqual(a, b, msg=None)                                     多行字符串 a = b       
11.assertSequenceEqual(first, second, msg=None, seq_type=None)              测试两个序列是否相等。如果提供seq_type,则第一和第二必须是seq_type的实例,否则将引发失败。如果序列不同,则构造示出两者之间的差异的错误消息。
12.assertListEqual(first, second, msg=None)        
13.assertTupleEqual(first, second, msg=None)
#测试两个列表或元组是否相等。如果没有,则构造一个错误消息,仅显示两者之间的差异。如果任一参数的类型错误,也会引发错误。在将列表或元组用assertEqual()进行比较时,默认使用这些方法。
14.assertSetEqual(a, b, msg=None)                                           测试两集合相等。如果没有,则构造一个错误消息,列出集合之间的差异。
15.assertDictEqual(first, second, msg=None)                                 测试两个字典是否相等。如果没有,则构造一个错误消息,显示字典中的差异。
TestCase类提供的断言
复制代码
所有的断言方法(除了assertRaises(), assertRaisesRegexp())接受一个msg参数,如果指定的话,它被用作失败的错误消息。
<3>unittest的TestSuite类的属性如下:(组织用例时需要用到)

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__','__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_addClassOrModuleLevelException',
'_get_previous_module', '_handleClassSetUp', '_handleModuleFixture', '_handleModuleTearDown','_tearDownPreviousClass', '_tests', 'addTest', 'addTests', 'countTestCases',
'debug', 'run']

TestSuite类常用属性和方法总结:

1)addTest(test):  该方法是将测试用例test添加到测试套件中。

示例:将test_baidu模块下的BaiduTest类下的test_baidu测试用例添加到测试套件。
suite = unittest.TestSuite()
suite.addTest(test_baidu.BaiduTest('test_baidu'))

2) addTests(tests): 该方法用于将多个测试用例添加到测试套件中。

3)run(result): 运行与此套件关联的测试,将结果收集到作为result传递的测试结果对象中。注意,不同于TestCase.run(),TestSuite.run()需要传入结果对象。

4)countTestCases(): 返回此测试对象表示的测试数,包括所有单个测试和子套件。

TestSuite类使用综合示例:

复制代码
import unittest
from testCase.mathfunctest import *

#构建测试类
#coding = 'utf-8'
class Count(object):
    def add(self,x,y):
        return x+y

    def sub(self,x,y):
        return x-y

    def multi(self,a, b):
        return a * b

    def divide(self,a, b):
        return a / b

#定义测试类,父类为unittest.TestCase。
class TestCount(unittest.TestCase):
    #定义setUp()方法用于测试用例执行前的初始化工作
    @classmethod
    def setUpClass(cls):
        print('setUp')
        cls.obj = Count()
    #定义多个测试用例,以“test_”开头命名的方法
    def test_add(self):
        self.assertEqual(15, self.obj.add(10, 5))

    def test_sub(self):
        self.assertEqual(5, self.obj.sub(10, 5))

    def test_multi(self):
        self.assertEqual(50, self.obj.multi(10, 5))

    def test_divide(self):
        self.assertEqual(2, self.obj.divide(10, 5))
        self.assertEqual(2.5,self.obj.divide(5, 2))
    # 定义tearDown()方法用于测试用例执行之后的善后工作。
    @classmethod
    def tearDownClass(cls):
        print('tearDown')
        cls.obj = None

#构建测试集示例1:
def get_suite1():
    #使用addTest
    testcase1 = TestCount('test_add')
    suite = unittest.TestSuite()
    suite.addTest(testcase1)
    return suite


def get_suite2():
    #使用addTests
    case_list = ['test_add','test_sub','test_multi','test_divide']
    demos = map(TestCount,case_list)
    suite = unittest.TestSuite()
    suite.addTests(demos)
    return suite

if __name__ == '__main__':
    s = get_suite2()
    print(s)
#<unittest.suite.TestSuite tests=[<__main__.TestCount testMethod=test_add>, <__main__.TestCount testMethod=test_sub>,
# <__main__.TestCount testMethod=test_multi>, <__main__.TestCount testMethod=test_divide>]>
    print(s.countTestCases())#计算测试用例数
    #实例化一个result对象
    result = unittest.TestResult()
    test_result = s.run(result)
    print(test_result)#<unittest.result.TestResult run=4 errors=0 failures=0>
TestSuite基本用法示例
复制代码

<4>unittest的TestLoader类的属性如下

['errors', '_loading_packages', '__module__', '__doc__', 'testMethodPrefix', 'sortTestMethodsUsing', 'suiteClass', '_top_level_dir', '__init__', 'loadTestsFromTestCase',
'loadTestsFromModule', 'loadTestsFromName','loadTestsFromNames', 'getTestCaseNames', 'discover', '_get_directory_containing_module', '_get_name_from_path',
'_get_module_from_name', '_match_path', '_find_tests', '_find_test_path', '__dict__', '__weakref__','__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__',
'__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__','__init_subclass__', '__format__',
'__sizeof__', '__dir__', '__class__']

TestLoader类常用属性、方法说明:

1)loadTestsFromTestCase(testCaseClass):返回一组包含在TestCase 派生类 TestCaseClass中的所有测试用例构成的测试套件。

2)loadTestsFromModule(module, pattern=None):返回一组包含在给定模块中的所有测试用例构成的TestSuite。此方法在模块中搜索从TestCase派生的类,并为类定义的每个测试方法创建类的实例。

3)loadTestsFromName(name, module=None):从字符串说明符中返回一组测试用例,说明符name是可以解析为模块,测试用例类,测试用例类中的测试方法

4)loadTestsFromNames(names, module=None):类似于loadTestsFromName(),但是接受一系列名称而不是单个名称。返回值是一个测试套件。

5)getTestCaseNames(testCaseClass)返回在testCaseClass中找到的方法名称的排序序列,返回形式是列表;testCaseClass应该是TestCase的子类。

6)discover(start_dir, pattern='test*.py', top_level_dir=None)通过从指定的起始目录递归到子目录中找到所有测试模块,并返回包含它们的TestSuite对象。仅加载与模式匹配的测试文件。

7)属性1:testMethodPrefix 返回字符串,返回将被解释为测试方法的方法名称的前缀。默认值为'test'。这会影响getTestCaseNames()和所有loadTestsFrom*()方法。

8)属性2:sortTestMethodsUsing用于在getTestCaseNames()和所有loadTestsFrom*()方法中对方法名称进行排序时用于比较方法名称的函数。

9)属性3:suiteClass从测试列表构造测试套件的可调用对象

TestLoader类使用方法的综合示例:

步骤1:先在mathfunc.py中准备待测类

复制代码
#构建待测试类
#coding = 'utf-8'
class Count(object):
    def add(self,x,y):
        return x+y

    def sub(self,x,y):
        return x-y

    def multi(self,a, b):
        return a * b

    def divide(self,a, b):
        return a / b
待测代码mathfunc.py
复制代码

步骤2:在test_mathfunc.py中定义测试类,测试类中添加测试方法

复制代码
import unittest
from testCase.mathfunc import *


#定义测试类,父类为unittest.TestCase。
class TestCount(unittest.TestCase):
    #定义setUp()方法用于测试用例执行前的初始化工作
    @classmethod
    def setUpClass(cls):
        print('setUp')
        cls.obj = Count()
    #定义多个测试用例,以“test_”开头命名的方法
    def test_add(self):
        self.assertEqual(15, self.obj.add(10, 5))

    def test_sub(self):
        self.assertEqual(5, self.obj.sub(10, 5))

    def test_multi(self):
        self.assertEqual(50, self.obj.multi(10, 5))

    def test_divide(self):
        self.assertEqual(2, self.obj.divide(10, 5))
        self.assertEqual(2.5,self.obj.divide(5, 2))
    # 定义tearDown()方法用于测试用例执行之后的善后工作。
    @classmethod
    def tearDownClass(cls):
        print('tearDown')
        cls.obj = None
test_mathfunc.py
复制代码

步骤3:为了演示testLoader跨模块加载测试用例,在test_string.py中定义其他测试类,并添加测试方法

复制代码
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)
test_string.py
复制代码

步骤4:在main.py中展示test_Loader构建测试用例套件的方法及

复制代码
#coding = 'utf-8'
import unittest
import os
from test_math_func import *

#构建测试集示例1:
def get_suite1():
    #使用TestSuite()类方法addTest单个加载测试用例
    testcase1 = TestCount('test_add')
    suite = unittest.TestSuite()
    suite.addTest(testcase1)
    return suite


def get_suite2():
    #使用TestSuite()类方法addTests同时加载多个测试用例
    case_list = ['test_add','test_sub','test_multi','test_divide']
    demos = map(TestCount,case_list)
    suite = unittest.TestSuite()
    suite.addTests(demos)
    return suite

def get_suite3():
    #TestLoder类的基本使用详解

    #法1.从testCase派生的测试类下加载该类下的所有测试用例,使用方法loadTestsFromTestCase(testCaseClass)
    test_loader = unittest.TestLoader()
    suite = test_loader.loadTestsFromTestCase(TestCount)
    #print(suite)#<unittest.suite.TestSuite tests=[<__main__.TestCount testMethod=test_add>, <__main__.TestCount testMethod=test_divide>, <__main__.TestCount testMethod=test_multi>, <__main__.TestCount testMethod=test_sub>]>

    #法2.加载给定模块中的所有测试用例,使用方法loadTestsFromModule(Module)
    #先导入待加载测试用例的模块
    import test_math_func
    suite1 = test_loader.loadTestsFromModule(test_math_func)

    #法3.从字符串说明符中返回一组测试用例,使用方法loadTestsFromName(name, module=None)。说明符name是可以解析为模块,测试用例类,测试用例类中的测试方法
    suite2 = test_loader.loadTestsFromName(name='test_math_func')#name解析为一个模块
    #print(suite2)##<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_math_func.TestCount testMethod=test_add>, <test_math_func.TestCount testMethod=test_divide>, <test_math_func.TestCount testMethod=test_multi>, <test_math_func.TestCount testMethod=test_sub>]>]>
    suite3 = test_loader.loadTestsFromName(name='test_math_func.TestCount')#name解析为test_math_func模块下的测试用例类TestCount
    suite4 = test_loader.loadTestsFromName(name='test_math_func.TestCount.test_add')
    #print(suite4)#<unittest.suite.TestSuite tests=[<test_math_func.TestCount testMethod=test_add>]>

    #法4.从多个字符串说明图中返回一组测试用例,使用方法loadTestsFromNames(names, module=None)
    names = ('test_math_func','test_string')#多个模块名构成的元组
    suite5 = test_loader.loadTestsFromNames(names=names)
    #print(suite5)#<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_math_func.TestCount testMethod=test_add>, <test_math_func.TestCount testMethod=test_divide>, <test_math_func.TestCount testMethod=test_multi>, <test_math_func.TestCount testMethod=test_sub>]>]>, <unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_string.TestStringMethods testMethod=test_isupper>, <test_string.TestStringMethods testMethod=test_split>, <test_string.TestStringMethods testMethod=test_upper>]>]>]>

    #法5.getTestCaseNames(testCaseClass)返回在testCaseClass中找到的方法名称的排序序列,返回形式是列表;testCaseClass应该是TestCase的子类。
    #print(test_loader.getTestCaseNames(test_math_func.TestCount))#['test_add', 'test_divide', 'test_multi', 'test_sub']

    #法6.discover(start_dir, pattern='test*.py', top_level_dir=None)通过从指定的起始目录递归到子目录中找到所有测试模块,并返回包含它们的TestSuite对象。仅加载与模式匹配的测试文件。

    start_dir = os.path.dirname(os.path.realpath(__file__))
    suite6 = test_loader.discover(start_dir=start_dir,pattern='test_*.py')
    #print(suite6)

    #TestLoder类的属性
    #TestLoader的以下属性可以通过实例化后赋值来配置
    #属性1:testMethodPrefix 返回字符串,返回将被解释为测试方法的方法名称的前缀。默认值为'test'。这会影响getTestCaseNames()和所有loadTestsFrom*()方法。
    print(test_loader.testMethodPrefix)#test

    #属性2:sortTestMethodsUsing用于在getTestCaseNames()和所有loadTestsFrom*()方法中对方法名称进行排序时用于比较方法名称的函数。
    print(test_loader.sortTestMethodsUsing)#<function three_way_cmp at 0x0000022FEC5D6598>

    #属性3:suiteClass从测试列表构造测试套件的可调用对象

    print(test_loader.suiteClass)#<class 'unittest.suite.TestSuite'>

    return suite6


if __name__ == '__main__':
    s = get_suite3()
    result = unittest.TestResult()
    result = s.run(result)
main.py
复制代码

<5>unittest的TextTestRunner的属性如下

['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '_makeResult', 'buffer', 'descriptions', 'failfast', 'resultclass', 'run', 'stream', 'verbosity']

常用方法说明:
run(): run()方法是运行测试套件的测试用例,入参为suite测试套件。
runner = unittest.TextTestRunner()
runner.run(suite)

二. UnitTest的基本使用

2.1 UnitTest的基本使用方法

1)import unittest

2)定义一个继承自unittest.TestCase的测试用例类

3)定义setUp和tearDown,在每个测试用例前后做一些辅助工作。

4)定义测试用例,名字以test开头

5)一个测试用例应该只测试一个方面,测试目的和测试内容应很明确。主要是调用assertEqual、assertRaises等断言方法判断程序执行结果和预期值是否相符。
6)调用unittest.main()启动测试
7)如果测试未通过,会输出相应的错误提示。如果测试全部通过则不显示任何东西,这时可以添加-v参数显示详细信息

 示例:

第一步:先在mathfunc.py中准备待测类:

复制代码
#构建测试类
class Count(object):
    def add(self,x,y):
        return x+y

    def sub(self,x,y):
        return x-y

    def multi(self,a, b):
        return a * b

    def divide(self,a, b):
        return a / b
mathfunc.py
复制代码

第二步:在test_mathfunc.py中定义测试类,测试类中添加测试方法

复制代码
import unittest
from mathfunc import *

#定义测试类,父类为unittest.TestCase。
class TestCount(unittest.TestCase):
    #定义setUp()方法用于测试用例执行前的初始化工作
    def setUp(self):
        print('setUp')
        self.obj = Count()
    #定义多个测试用例,以“test_”开头命名的方法
    def test_add(self):
        self.assertEqual(15, self.obj.add(10, 5))

    def test_sub(self):
        self.assertEqual(5, self.obj.sub(10, 5))

    def test_multi(self):
        self.assertEqual(50, self.obj.multi(10, 5))

    def test_divide(self):
        self.assertEqual(2, self.obj.divide(10, 5))
        self.assertEqual(2.5,self.obj.divide(5, 2))
    # 定义tearDown()方法用于测试用例执行之后的善后工作。
    def tearDown(self):
        print('tearDown')
        self.obj = None
test_mathfunc.py
复制代码

第三步:在test_suite.py中创建测试用例集,并运行测试用例集

创建测试用例集方法总结:

法1:逐个实例化测试用例,并通过addTest方法逐个添加测试用例到测试用例集

复制代码
import unittest
from test_mathfunc import *
#(法1)逐个实例化测试用例,并通过addTest方法逐个添加测试用例到测试用例集
#定义测试用例管理函数
#构建测试集示例1:
def get_suite():
    #实例化测试用例
    demo_countadd = TestCount('test_add')
    demo_countsub = TestCount('test_sub')
    demo_countmulti = TestCount('test_multi')
    demo_countdivide = TestCount('test_divide')

    #实例化测试用例集
    suite = unittest.TestSuite()
    #通过addTest方法将测试用例加载到测试用例集
    suite.addTest(demo_countadd)
    suite.addTest(demo_countsub)
    suite.addTest(demo_countmulti)
    suite.addTest(demo_countdivide)
    print(suite)
    return suite
if __name__ == '__main__':
    s =get_suite()
    #统计测试用例条数
    s.countTestCases()
    #实例化TextTestRunner类
    runner = unittest.TextTestRunner()
    #使用run()方法运行测试套件(即运行测试套件中的所有用例)
    runner.run(s)

#运行结果:
#<unittest.suite.TestSuite tests=[<test_mathfunc.TestCount testMethod=test_add>, <test_mathfunc.TestCount testMethod=test_sub>, <test_mathfunc.TestCount testMethod=test_multi>, <test_mathfunc.TestCount testMethod=test_divide>]>
#----------------------------------------------------------------------
#Ran 4 tests in 0.000s

#OK
test_suite.py
复制代码

法2:通过map方法一次实例化多个测试用例,并通过addTests方法同时加载多条用例到测试用例集

复制代码
import unittest
from test_mathfunc import *
#(法2)通过map方法一次实例化多个测试用例,并通过addTests方法同时加载多条用例到测试用例集
#构建测试集示例2:
def get_suite():
    case_list = ['test_add', 'test_sub','test_multi','test_divide']
    #批量实例化测试用例
    demos = map(TestCount, case_list)
    suite = unittest.TestSuite()
    #通过addTests方法批量加载测试用例
    suite.addTests(demos)
    print(suite)
    return suite
if __name__ == '__main__':
    s =get_suite()
    s.countTestCases()
    runner = unittest.TextTestRunner()
    runner.run(s)
    
#运行结果:
#<unittest.suite.TestSuite tests=[<test_mathfunc.TestCount testMethod=test_add>, <test_mathfunc.TestCount testMethod=test_sub>, <test_mathfunc.TestCount testMethod=test_multi>, <test_mathfunc.TestCount testMethod=test_divide>]>
#----------------------------------------------------------------------
#Ran 4 tests in 0.000s
#OK
test_suite.py
复制代码

法3:继承父类unittest.TestSuite,通过__init__方法直接批量添加测试用例

复制代码
import unittest
from test_mathfunc import *

#(法3)继承父类unittest.TestSuite,通过__init__方法直接批量添加测试用例
#构建测试集示例3:
class CountTestSuite(unittest.TestSuite):
    def __init__(self):
        case_list = ['test_add','test_sub','test_multi','test_divide']
        unittest.TestSuite.__init__(self,map(TestCount,case_list))

if __name__ == '__main__':
    s  = CountTestSuite()
    print(s)
    s.countTestCases()
    runner = unittest.TextTestRunner()
    runner.run(s)


#运行结果
#<__main__.CountTestSuite tests=[<test_mathfunc.TestCount testMethod=test_add>, <test_mathfunc.TestCount testMethod=test_sub>, <test_mathfunc.TestCount testMethod=test_multi>, <test_mathfunc.TestCount testMethod=test_divide>]>
#----------------------------------------------------------------------
#Ran 4 tests in 0.000s

#OK
test_suite.py
复制代码

 法4:使用unittest.makeSuite()方法自动构建测试用例集,这种方法对于编写测试用例的要求:

(a)测试方法都以规定的命名开头   

(b)使用makeSuite直接生成测试集 

(c)unittest.makeSuite(testcaseClass,prefix='test')    说明:unittest.makeSuite()第一个参数是测试类,第二个参数定义加载的测试用例方法的开头字符

复制代码
import unittest
from test_mathfunc import *
#(法4)使用unittest.makeSuite()方法自动构建测试用例集,这种方法对于编写测试用例的要求:
#构建测试集示例4:
def get_suite():
  suite = unittest.makeSuite(TestCount,prefix='test')
  return suite


if __name__ == '__main__':
    s  = get_suite()
    print(s)
    s.countTestCases()
    runner = unittest.TextTestRunner()
    runner.run(s)

#运行结果:
#<unittest.suite.TestSuite tests=[<test_mathfunc.TestCount testMethod=test_add>, <test_mathfunc.TestCount testMethod=test_divide>, <test_mathfunc.TestCount testMethod=test_multi>, <test_mathfunc.TestCount testMethod=test_sub>]>
#....
#----------------------------------------------------------------------
#Ran 4 tests in 0.000s

#OK
test_suite.py
复制代码

法5:使用最简单的方式:unitest.main(),这种方法自动检测测试类中所有以test开头的方法;该方法的优点:

(a)自动查找测试用例

(b)自动构建测试集

(c)自动运行测试用例

复制代码
import unittest
from test_mathfunc import *
#(法5)使用最简单的方式:unitest.main(),这种方法自动检测测试类中所有以test开头的方法;该方法的优点:
#构建测试集示例5:
if __name__ == '__main__':
    unittest.main()
#运行结果:
#----------------------------------------------------------------------
#Ran 4 tests in 0.000s

#OK
test_suite.py
复制代码

法6:使用unittest.defaultTestLoader.discover构造测试集

用法:discover = unittest.defaultTestLoader.discover(case_dir, pattern="test*.py",top_level_dir=None)

discover方法里面有三个参数:

-case_dir:这个是待执行用例的目录。

-pattern:这个是匹配脚本名称的规则,test*.py意思是匹配test开头的所有脚本。

-top_level_dir:这个是顶层目录的名称,一般默认等于None就行了

复制代码
import unittest
from test_case_directory import test_mathfunc
import os.path
import sys
#(法6) 使用unittest.defaultTestLoader.discover构造测试集(简化了先要创建测试套件然后再依次加载测试用例)
#构建测试集示例6:
sys.path.append(os.path.dirname(os.path.realpath(__file__))+ 'test_case_directory')
def get_suite():
    test_dir = os.path.dirname(os.path.realpath(__file__))
    test_dir = os.path.join(test_dir,'test_case_directory')

    suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
    print('suite:',suite)
    return suite
if __name__ == '__main__':
    s = get_suite()
    print(s.countTestCases())
    runner = unittest.TextTestRunner()
    runner.run(s)
#运行结果:
#Ran 4 tests in 0.000s
#suite: <unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<test_mathfunc.TestCount testMethod=test_add>, <test_mathfunc.TestCount testMethod=test_divide>, <test_mathfunc.TestCount testMethod=test_multi>, <test_mathfunc.TestCount testMethod=test_sub>]>]>]>

#4
#OK
test_suite.py
复制代码

2.2 跳过某个测试用例

复制代码
import unittest
from mathfunc import *

#定义测试类,父类为unittest.TestCase。
class TestCount(unittest.TestCase):
    #定义setUp()方法用于测试用例执行前的初始化工作
    def setUp(self):
        #print('setUp')
        self.obj = Count()
    #定义多个测试用例,以“test_”开头命名的方法
    def test_add(self):
        self.assertEqual(15, self.obj.add(10, 5))

    def test_sub(self):
        self.assertEqual(5, self.obj.sub(10, 5))
    #@unittest.skipIf(Count.version==1,'no test')条件为真时跳过测试用例
    #@unittest.skipUnless(Count.version==1,'no test')#条件为真时不跳过测试用例
    @unittest.expectedFailure #测试结果与预期值不相同,不计入测试失败统计
    def test_multi(self):
        self.assertEqual(50, self.obj.multi(10, 5))
    @unittest.skip('Notest')#无条件跳过该测试用例
    def test_divide(self):
        self.assertEqual(2, self.obj.divide(10, 5))
        self.assertEqual(2.5,self.obj.divide(5, 2))
    # 定义tearDown()方法用于测试用例执行之后的善后工作。

    def tearDown(self):
       # print('tearDown')
        self.obj = None
View Code
复制代码

2.3 unittest.mock模块

Mock:向测试对象提供一套和测试资源完全相同的接口和方法,不关心具体实现过程,只关心具体结果
参数 说明
name Mock对象的名字
spec Mock对象的属性
return_value Mock对象返回值
mock_calls Mock对象所有调用顺序
call_args Mock对象初始化参数
call_args_list 调用中使用参数
call_count Mock被调用次数
assert_called_with(arg) 检查函数调用参数是否正确
assert_called_once_with(arg) 同上,但是只调用一次
assert_has_calls() 期望调用方法列表
   

 

 示例:用mock模拟云端客户端接口,然后在客户端功能未实现之前,通过模拟的接口进行用例测试

复制代码
from unittest import mock
#模拟云端客户端
class CouldClient(object):
    def connect(self):
        pass
    def disconnect(self):
        pass
    def upload(self):
        pass
    def download(self):
        pass
tmock=mock.Mock(CouldClient)
tmock.connect.return_value = 200
tmock.disconnect.return_value = 404
View Code
复制代码
复制代码
import unittest
from unittest import mock
from mockcalss import CouldClient
import unittest
class TestCould(unittest.TestCase):
    def setUp(self):
        self.obj = mock.Mock(CouldClient)
    def tearDown(self):
        self.obj = None
    def test_connect(self):
        self.obj.connect.return_value = 200
        self.assertEqual(self.obj.connect(),200)
if __name__ == '__mian__':
    unittest.main()
View Code
复制代码

 2.4 将测试结果输出到文件

(1)将测试结果输出到txt文件

 将原来的test_suite.py文件做如下改动,便能将测试结果输出到txt格式的文本中

复制代码
import unittest
from unittest import mock
from test_case_directory import test_mathfunc
import os.path

def get_suite():
    test_dir = os.path.dirname(os.path.realpath(__file__))
    test_dir = os.path.join(test_dir,'test_case_directory')

    suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
    print('suite:',suite)
    return suite
if __name__ == '__main__':
    s = get_suite()
    print(s.countTestCases())
    with open('unittestTestReport.txt','a')as f:
        #注意:verbosity参数可以控制输出的错误报告的详细程度,只有3个取值
        #0 (quiet): 只显示执行的用例的总数和全局的执行结果。
        #1 (default): 默认值,显示执行的用例的总数和全局的执行结果,并对每个用例的执行结果(成功T或失败F)有个标注。
        #2 (verbose): 显示执行的用例的总数和全局的执行结果,并输出每个用例的详细的执行结果。
        runner = unittest.TextTestRunner(stream=f,verbosity=2)
        runner.run(s)
test_suite.py
复制代码

执行此文件,可以看到,在同目录下生成了如下所示的UnittestTextReport.txt,所有的执行报告均输出到了此文件中,这下我们便有了txt格式的测试报告了

复制代码
test_add (test_mathfunc.TestCount) ... ok
test_divide (test_mathfunc.TestCount) ... ok
test_multi (test_mathfunc.TestCount) ... ok
test_sub (test_mathfunc.TestCount) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
unittestTextReport.txt
复制代码

(2)借助HTMLTestRunner生成漂亮的HTML报告

txt格式的文本执行报告过于简陋,这里我们学习一下借助HTMLTestRunner生成HTML报告。首先需要下载HTMLTestRunner.py,并放到当前目录下,或者python目录下的Lib中,就可以导入运行了。

 将原来的test_suite.py文件做如下改动,便能将测试结果输出到HTML格式的文本中

复制代码
#_*_ coding=utf-8 _*_
import unittest
from unittest import mock
from test_case_directory import test_mathfunc
import HTMLTestRunner
import os.path

def get_suite():
    test_dir = os.path.dirname(os.path.realpath(__file__))
    test_dir = os.path.join(test_dir,'test_case_directory')

    suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
    print('suite:',suite)
    return suite
if __name__ == '__main__':
    s = get_suite()
    print(s.countTestCases())
    with open('HTMLReport.html','wb')as f:
        #注意:verbosity参数可以控制输出的错误报告的详细程度,只有3个取值
        #0 (quiet): 只显示执行的用例的总数和全局的执行结果。
        #1 (default): 默认值,显示执行的用例的总数和全局的执行结果,并对每个用例的执行结果(成功T或失败F)有个标注。
        #2 (verbose): 显示执行的用例的总数和全局的执行结果,并输出每个用例的详细的执行结果。
        runner = HTMLTestRunner.HTMLTestRunner(stream=f,title='MathFunc Test Report',description='generated by HTMLTestRunner.',verbosity=2)
        runner.run(s)
test_suite.py
复制代码

输出 的测试报告如下图:

 (3)增加测试报告的可读性

虽然在我们在测试用例开发时为每个用例添加了注释,但测试报告一般是给非测试人员阅读的,如果能在报告中为每一个测试用例添加说明,那么将会使报告更加易于阅读,

打开我们的测试用例文件,为每一个测试用例(方法)下面添加注释,如下:

复制代码
import unittest
from conut import *
#定义测试类,父类为unittest.TestCase。
class TestCount(unittest.TestCase):
    #定义setUp()方法用于测试用例执行前的初始化工作
    """测试类:Conut"""
    def setUp(self):
        #print('setUp')
        self.obj = Count()
    #定义多个测试用例,以“test_”开头命名的方法
    def test_add(self):
        """测试加法"""
        self.assertEqual(10, self.obj.add(10, 5))

    def test_sub(self):
        """测试减法"""
        self.assertEqual(5, self.obj.sub(10, 5))

    def test_multi(self):
        """测试乘法"""
        self.assertEqual(50, self.obj.multi(10, 5))

    def test_divide(self):
        """测试除法"""
        self.assertEqual(2, self.obj.divide(10, 5))
        self.assertEqual(2.5,self.obj.divide(5, 2))
    # 定义tearDown()方法用于测试用例执行之后的善后工作。
    def tearDown(self):
        #print('tearDown')
        self.obj = None
View Code
复制代码

现在生成的测试报告中将会有注释信息,如下:

 

 

三 . 将测试结果通过邮件发送

使用python3的email模块和smtplib模块可以实现发送邮件的动能。email模块用来生成email,smtplib模块用来发送邮件,接下来看如何在生成测试报告之后,将报告放在邮件附件中并发送给项目组的人

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。

python的smtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装。

Python创建 SMTP 对象语法如下:


import smtplib

smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )

参数说明:

  • host: SMTP 服务器主机。 你可以指定主机的ip地址或者域名如: runoob.com,这个是可选参数。
  • port: 如果你提供了 host 参数, 你需要指定 SMTP 服务使用的端口号,一般情况下 SMTP 端口号为25。
  • local_hostname: 如果 SMTP 在你的本机上,你只需要指定服务器地址为 localhost 即可。
Python SMTP 对象使用 sendmail 方法发送邮件,语法如下:
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])

参数说明:

  • from_addr: 邮件发送者地址。
  • to_addrs: 字符串列表,邮件发送地址。
  • msg: 发送消息

这里要注意一下第三个参数,msg 是字符串,表示邮件。我们知道邮件一般由标题,发信人,收件人,邮件内容,附件等构成,发送邮件的时候,要注意 msg 的格式。这个格式就是 smtp 协议中定义的格式。


以下执行实例需要你本机已安装了支持 SMTP 的服务,如:sendmail。

以下是一个使用 Python 发送邮件简单的实例:

复制代码
#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import smtplib
from email.mime.text import MIMEText
from email.header import Header
 
sender = 'from@runoob.com'
receivers = ['429240967@qq.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
 
# 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码
message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8')
message['From'] = Header("菜鸟教程", 'utf-8')   # 发送者
message['To'] =  Header("测试", 'utf-8')        # 接收者
 
subject = 'Python SMTP 邮件测试'
message['Subject'] = Header(subject, 'utf-8')
 
 
try:
    smtpObj = smtplib.SMTP('localhost')
    smtpObj.sendmail(sender, receivers, message.as_string())
    print "邮件发送成功"
except smtplib.SMTPException:
    print "Error: 无法发送邮件"
View Code
复制代码

 如果我们本机没有 sendmail 访问,也可以使用其他邮件服务商的 SMTP 访问(QQ、网易、Google等)。

复制代码
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

from email.utils import formataddr
import os.path
from constant.constant import REPORT_PATH
from utils.common.log import logger

# 使用第三方邮件服务商的SMTP发送邮件
#QQ 邮箱 SMTP 服务器地址:smtp.qq.com,ssl 端口:465。
mail_host = "smtp.qq.com"  # 设置服务器
sender = "xxxxxxxx@qq.com"  # 用户名
password = "xxxxxxxxxxxx"  # QQ邮箱的SMTP授权码
receivers = ['xxxxxxxxx@qq.com','xxxxxxxxxxx@163.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
file = os.path.join(REPORT_PATH, 'ExampleReport.html')#测试报告地址

def mail():
    # 创建一个带附件的实例
    message = MIMEMultipart()
    # 括号里的对应发件人邮箱昵称、发件人邮箱账号
    message['From'] = formataddr(['发件人姓名',sender])
    logger.info('发件人邮箱:%s' % sender)
    # 括号里的对应收件人邮箱昵称、收件人邮箱账号
    #单个收件人: message['To'] = formataddr(['收件人姓名',sender])
    #多个收件人:
    message['To'] = ';'.join(receivers)
    logger.info('收件人邮箱:%s' % receivers)
    # 邮件的主题,也可以说是标题
    message['Subject'] = 'Python SMTP 邮件测试'
    # 邮件正文内容
    message.attach(MIMEText('Python 邮件发送测试...', 'plain', 'utf-8'))
    # 构造附件1
    att1 = MIMEText(open(file, 'rb').read(), 'base64', 'utf-8')
    logger.info('读取附件')
    att1["Content-Type"] = 'text/html'
    # filename是附件名,附件名称为中文时的写法
    #att1.add_header("Content-Disposition", "attachment", filename=("gbk", "", "xxx接口自动化测试报告.html"))
    # 附件名称非中文时的写法
    att1["Content-Disposition"] = 'attachment; filename="ExampleReport.html")'
    #添加附件
    message.attach(att1)
    logger.info('添加附件')
    try:
    # 发件人邮箱中的SMTP服务器,一般端口是25
        server = smtplib.SMTP_SSL(mail_host, 465)
        logger.info('连接QQ邮箱smtp服务')
    # 括号中对应的是发件人邮箱账号、邮箱密码
        server.login(sender, password)
        logger.info('连接成功')
    # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
        server.sendmail(sender, receivers, message.as_string())
    # 关闭连接
        server.quit()
        logger.info("邮件发送成功")
    except Exception:
        logger.error("邮件发送失败", exc_info=1)
View Code
复制代码

成功发送邮件后,接受邮箱的信息显示如下:

 

 将上面发送邮件的过程抽象成一个类:

复制代码
class Email:
    def __init__(self, server, sender, password, receiver, title, message=None, path=None):
        """初始化Email

        :param title: 邮件标题,必填。
        :param message: 邮件正文,非必填。
        :param path: 附件路径,可传入list(多附件)或str(单个附件),非必填。
        :param server: smtp服务器,必填。
        :param sender: 发件人,必填。
        :param password: 发件人SMTP授权码,必填。
        :param receiver: 收件人,多收件人用“;”隔开,必填。
        """
        self.title = title
        self.message = message
        self.files = path
        self.msg = MIMEMultipart('related')
        self.server = server
        self.sender = sender
        self.receiver = receiver
        self.password = password

    def _attach_file(self, att_file):
        """将单个文件添加到附件列表中"""
        att = MIMEText(open('%s' % att_file, 'rb').read(), 'plain', 'utf-8')
        att["Content-Type"] = 'application/octet-stream'
        file_name = re.split(r'[\\|/]', att_file)
        att["Content-Disposition"] = 'attachment; filename="%s"' % file_name[-1]
        self.msg.attach(att)
        logger.info('attach file {}'.format(att_file))

    def send(self):
        self.msg['Subject'] = self.title
        self.msg['From'] = self.sender
        self.msg['To'] = self.receiver

        # 邮件正文
        if self.message:
            self.msg.attach(MIMEText(self.message))

        # 添加附件,支持多个附件(传入list),或者单个附件(传入str)
        if self.files:
            if isinstance(self.files, list):
                for f in self.files:
                    self._attach_file(f)
            elif isinstance(self.files, str):
                self._attach_file(self.files)

        # 连接服务器并发送
        try:
            smtp_server = smtplib.SMTP(self.server)  # 连接sever
        except (gaierror and error) as e:
            logger.exception('发送邮件失败,无法连接到SMTP服务器,检查网络以及SMTP服务器. %s', e)
        else:
            try:
                smtp_server.login(self.sender, self.password)  # 登录
            except smtplib.SMTPAuthenticationError as e:
                logger.exception('用户名密码验证失败!%s', e)
            else:
                smtp_server.sendmail(self.sender, self.receiver.split(';'), self.msg.as_string())  # 发送邮件
            finally:
                smtp_server.quit()  # 断开连接
                logger.info('发送邮件"{0}"成功! 收件人:{1}。如果没有收到邮件,请检查垃圾箱,'
                            '同时检查收件人地址是否正确'.format(self.title, self.receiver))


if __name__ == '__main__':
    # 使用第三方邮件服务商的SMTP发送邮件
    #QQ 邮箱 SMTP 服务器地址:smtp.qq.com,ssl 端口:465。
    mail_host = "smtp.qq.com"  # 设置服务器
    sender = "xxxxxxxxx@qq.com"  # 用户名
    password = "xxxxxxxxxxxxxxxxx"  # QQ邮箱的SMTP授权码
    receivers = ['xxxxxxxxx@qq.com','xxxxxxxxxxx@163.com']  # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
    file = os.path.join(REPORT_PATH, 'ExampleReport.html')#测试报告地址
    e = Email(title='测试报告',
              message='这是今天的测试报告,请查收!',
              receiver=';'.join(receivers),
              server=mail_host,
              sender=sender,
              password=password,
              path=file
              )
    e.send()
View Code
复制代码

 

 

 



















 

 

>>>>>>>待续

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