一. UnitTest单元测试框架

1.1概述

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:顾名思义,这个概念负责执行测试并控制结果输出。

1.2 常用方法总结

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

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

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

  1. import unittest
  2. class Test(unittest.TestCase):
  3. @classmethod
  4. def setUpClass(cls): #这里的cls是当前类的对象
  5. cls._connection = createExpensiveConnectionObject()
  6. @classmethod
  7. def tearDownClass(cls):
  8. cls._connection.destroy()

注意: 用setUp与setUpClass区别

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

6)run(result=None)运行测试,并返回测试结果(返回值为对象),如果结果被省略或者没有,则创建一个临时结果对象(通过调用defaultTestResult()方法)并使用。

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

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

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

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

assertAlmostEqual(first, second[, places, ...])

适用于小数,place是应最少几位相等布尔值才为1(默认为7),如果在place位以内不同则断言失败。

assertDictContainsSubset(expected, actual[, msg])

检查实际是否超预期

assertDictEqual(d1, d2[, msg])

前后字典是否相同

assertEqual(first, second[, msg])

前后两个数不等,断言失败

assertFalse(expr[, msg])

检查表达式是否为假

assertGreater(a, b[, msg])

和self.assertTrue(a > b)用法一样,但是多了设置条件 .

assertGreaterEqual(a, b[, msg])

和self.assertTrue(a > =b)用法一样,但是多了设置条件 .

assertIn(member, container[, msg])

self.assertTrue(a in b)

assertIs(expr1, expr2[, msg])

assertTrue(a is b)

assertIsInstance(obj, cls[, msg])

Isinstance(a,b)

assertIsNone(obj[, msg])

Obj is none.

assertIsNot(expr1, expr2[, msg])

a is not b.

assertIsNotNone(obj[, msg])

Obj is not none.

assertItemsEqual(expected_seq, actual_seq[, msg])

一个无序的序列特异性的比较。

assertLess(a, b[, msg])

Just like self.assertTrue(a < b), but with a nicer default message.

assertLessEqual(a, b[, msg])

Just like self.assertTrue(a <= b), but with a nicer default message.

assertListEqual(list1, list2[, msg])

List1与list2是否相等.

assertMuitiLineEqual(first, second[, msg])

断言,2个多行字符串是相等的

assertNotAlmostEqual(first, second[, ...])

Fail if the two objects are equal as determined by their difference rounded to the given number of decimal places (default 7) and

comparing to zero, or by comparing that the betweenthe two objects is less than the given delta.

assertNotAlmostEquals(first, second[, ...])

Fail if the two objects are equal as determined by their difference rounded to the given number of decimal places (default 7) and

comparing to zero, or by comparing that the between the two objects is less than the given delta.

assertNotEqual(first, second[, msg])

Fail if the two objects are equal as determined by the ‘==’

assertNotEquals(first, second[, msg])

Fail if the two objects are equal as determined by the ‘==’

assertNotIn(member, container[, msg])

Just like self.assertTrue(a not in b), but with a nicer default message.

assertNotIsInstance(obj, cls[, msg])

Included for symmetry with assertIsInstance.

assertNOtRegexpMatches(text, unexpected_regexp)

如果文本匹配正则表达式,将失败。

assertRaises(excClass[, callableObj])

除非excclass类抛出异常失败

assertRaisesRegexp(expected_exception, ...)

认为在引发异常的情况下消息匹配一个正则表达式。

assertRegexpMatches(text, expected_regexp[, msg])

测试失败,除非文本匹配正则表达式。

assertSequenceEqual(seq1, seq2[, msg, seq_type])

有序序列的相等断言 (like lists and tuples).

assertSetEqual(set1, set2[, msg])

A set-specific equality assertion.

assertTrue(expr[, msg])

Check that the expression is true.

assertTupleEqual(tuple1, tuple2[, msg])

A tuple-specific equality assertion.

assert_(expr[, msg])

Check that the expression is true.

所有的断言方法(除了assertRaises(), assertRaisesRegexp())接受一个msg参数,如果指定的话,它被用作失败的错误消息。
<3>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(): addTest()方法是将测试用例添加到测试套件中。

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

<4>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中准备待测类:

  1. #构建测试类
  2. class Count(object):
  3. def add(self,x,y):
  4. return x+y
  5.  
  6. def sub(self,x,y):
  7. return x-y
  8.  
  9. def multi(self,a, b):
  10. return a * b
  11.  
  12. def divide(self,a, b):
  13. return a / b

mathfunc.py

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

  1. import unittest
  2. from mathfunc import *
  3.  
  4. #定义测试类,父类为unittest.TestCase。
  5. class TestCount(unittest.TestCase):
  6. #定义setUp()方法用于测试用例执行前的初始化工作
  7. def setUp(self):
  8. print('setUp')
  9. self.obj = Count()
  10. #定义多个测试用例,以“test_”开头命名的方法
  11. def test_add(self):
  12. self.assertEqual(15, self.obj.add(10, 5))
  13.  
  14. def test_sub(self):
  15. self.assertEqual(5, self.obj.sub(10, 5))
  16.  
  17. def test_multi(self):
  18. self.assertEqual(50, self.obj.multi(10, 5))
  19.  
  20. def test_divide(self):
  21. self.assertEqual(2, self.obj.divide(10, 5))
  22. self.assertEqual(2.5,self.obj.divide(5, 2))
  23. # 定义tearDown()方法用于测试用例执行之后的善后工作。
  24. def tearDown(self):
  25. print('tearDown')
  26. self.obj = None

test_mathfunc.py

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

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

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

  1. import unittest
  2. from test_mathfunc import *
  3. #(法1)逐个实例化测试用例,并通过addTest方法逐个添加测试用例到测试用例集
  4. #定义测试用例管理函数
  5. #构建测试集示例1:
  6. def get_suite():
  7. #实例化测试用例
  8. demo_countadd = TestCount('test_add')
  9. demo_countsub = TestCount('test_sub')
  10. demo_countmulti = TestCount('test_multi')
  11. demo_countdivide = TestCount('test_divide')
  12.  
  13. #实例化测试用例集
  14. suite = unittest.TestSuite()
  15. #通过addTest方法将测试用例加载到测试用例集
  16. suite.addTest(demo_countadd)
  17. suite.addTest(demo_countsub)
  18. suite.addTest(demo_countmulti)
  19. suite.addTest(demo_countdivide)
  20. print(suite)
  21. return suite
  22. if __name__ == '__main__':
  23. s =get_suite()
  24. #统计测试用例条数
  25. s.countTestCases()
  26. #实例化TextTestRunner类
  27. runner = unittest.TextTestRunner()
  28. #使用run()方法运行测试套件(即运行测试套件中的所有用例)
  29. runner.run(s)
  30.  
  31. #运行结果:
  32. #<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>]>
  33. #----------------------------------------------------------------------
  34. #Ran 4 tests in 0.000s
  35.  
  36. #OK

test_suite.py

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

  1. import unittest
  2. from test_mathfunc import *
  3. #(法2)通过map方法一次实例化多个测试用例,并通过addTests方法同时加载多条用例到测试用例集
  4. #构建测试集示例2:
  5. def get_suite():
  6. case_list = ['test_add', 'test_sub','test_multi','test_divide']
  7. #批量实例化测试用例
  8. demos = map(TestCount, case_list)
  9. suite = unittest.TestSuite()
  10. #通过addTests方法批量加载测试用例
  11. suite.addTests(demos)
  12. print(suite)
  13. return suite
  14. if __name__ == '__main__':
  15. s =get_suite()
  16. s.countTestCases()
  17. runner = unittest.TextTestRunner()
  18. runner.run(s)
  19.  
  20. #运行结果:
  21. #<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>]>
  22. #----------------------------------------------------------------------
  23. #Ran 4 tests in 0.000s
  24. #OK

test_suite.py

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

  1. import unittest
  2. from test_mathfunc import *
  3.  
  4. #(法3)继承父类unittest.TestSuite,通过__init__方法直接批量添加测试用例
  5. #构建测试集示例3:
  6. class CountTestSuite(unittest.TestSuite):
  7. def __init__(self):
  8. case_list = ['test_add','test_sub','test_multi','test_divide']
  9. unittest.TestSuite.__init__(self,map(TestCount,case_list))
  10.  
  11. if __name__ == '__main__':
  12. s = CountTestSuite()
  13. print(s)
  14. s.countTestCases()
  15. runner = unittest.TextTestRunner()
  16. runner.run(s)
  17.  
  18. #运行结果
  19. #<__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>]>
  20. #----------------------------------------------------------------------
  21. #Ran 4 tests in 0.000s
  22.  
  23. #OK

test_suite.py

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

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

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

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

  1. import unittest
  2. from test_mathfunc import *
  3. #(法4)使用unittest.makeSuite()方法自动构建测试用例集,这种方法对于编写测试用例的要求:
  4. #构建测试集示例4:
  5. def get_suite():
  6. suite = unittest.makeSuite(TestCount,prefix='test')
  7. return suite
  8.  
  9. if __name__ == '__main__':
  10. s = get_suite()
  11. print(s)
  12. s.countTestCases()
  13. runner = unittest.TextTestRunner()
  14. runner.run(s)
  15.  
  16. #运行结果:
  17. #<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>]>
  18. #....
  19. #----------------------------------------------------------------------
  20. #Ran 4 tests in 0.000s
  21.  
  22. #OK

test_suite.py

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

(a)自动查找测试用例

(b)自动构建测试集

(c)自动运行测试用例

  1. import unittest
  2. from test_mathfunc import *
  3. #(法5)使用最简单的方式:unitest.main(),这种方法自动检测测试类中所有以test开头的方法;该方法的优点:
  4. #构建测试集示例5:
  5. if __name__ == '__main__':
  6. unittest.main()
  7. #运行结果:
  8. #----------------------------------------------------------------------
  9. #Ran 4 tests in 0.000s
  10.  
  11. #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就行了。

  1. import unittest
  2. from test_case_directory import test_mathfunc
  3. import os.path
  4. import sys
  5. #(法6) 使用unittest.defaultTestLoader.discover构造测试集(简化了先要创建测试套件然后再依次加载测试用例)
  6. #构建测试集示例6:
  7. sys.path.append(os.path.dirname(os.path.realpath(__file__))+ 'test_case_directory')
  8. def get_suite():
  9. test_dir = os.path.dirname(os.path.realpath(__file__))
  10. test_dir = os.path.join(test_dir,'test_case_directory')
  11.  
  12. suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
  13. print('suite:',suite)
  14. return suite
  15. if __name__ == '__main__':
  16. s = get_suite()
  17. print(s.countTestCases())
  18. runner = unittest.TextTestRunner()
  19. runner.run(s)
  20. #运行结果:
  21. #Ran 4 tests in 0.000s
  22. #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>]>]>]>
  23.  
  24. #
  25. #OK

test_suite.py

2.2 跳过某个测试用例

  1. import unittest
  2. from mathfunc import *
  3.  
  4. #定义测试类,父类为unittest.TestCase。
  5. class TestCount(unittest.TestCase):
  6. #定义setUp()方法用于测试用例执行前的初始化工作
  7. def setUp(self):
  8. #print('setUp')
  9. self.obj = Count()
  10. #定义多个测试用例,以“test_”开头命名的方法
  11. def test_add(self):
  12. self.assertEqual(15, self.obj.add(10, 5))
  13.  
  14. def test_sub(self):
  15. self.assertEqual(5, self.obj.sub(10, 5))
  16. #@unittest.skipIf(Count.version==1,'no test')条件为真时跳过测试用例
  17. #@unittest.skipUnless(Count.version==1,'no test')#条件为真时不跳过测试用例
  18. @unittest.expectedFailure #测试结果与预期值不相同,不计入测试失败统计
  19. def test_multi(self):
  20. self.assertEqual(50, self.obj.multi(10, 5))
  21. @unittest.skip('Notest')#无条件跳过该测试用例
  22. def test_divide(self):
  23. self.assertEqual(2, self.obj.divide(10, 5))
  24. self.assertEqual(2.5,self.obj.divide(5, 2))
  25. # 定义tearDown()方法用于测试用例执行之后的善后工作。
  26.  
  27. def tearDown(self):
  28. # print('tearDown')
  29. self.obj = None

2.3 unittest.mock模块

  1. 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模拟云端客户端接口,然后在客户端功能未实现之前,通过模拟的接口进行用例测试

  1. from unittest import mock
  2. #模拟云端客户端
  3. class CouldClient(object):
  4. def connect(self):
  5. pass
  6. def disconnect(self):
  7. pass
  8. def upload(self):
  9. pass
  10. def download(self):
  11. pass
  12. tmock=mock.Mock(CouldClient)
  13. tmock.connect.return_value = 200
  14. tmock.disconnect.return_value = 404
  1. import unittest
  2. from unittest import mock
  3. from mockcalss import CouldClient
  4. import unittest
  5. class TestCould(unittest.TestCase):
  6. def setUp(self):
  7. self.obj = mock.Mock(CouldClient)
  8. def tearDown(self):
  9. self.obj = None
  10. def test_connect(self):
  11. self.obj.connect.return_value = 200
  12. self.assertEqual(self.obj.connect(),200)
  13. if __name__ == '__mian__':
  14. unittest.main()

 2.4 将测试结果输出到文件

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

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

  1. import unittest
  2. from unittest import mock
  3. from test_case_directory import test_mathfunc
  4. import os.path
  5.  
  6. def get_suite():
  7. test_dir = os.path.dirname(os.path.realpath(__file__))
  8. test_dir = os.path.join(test_dir,'test_case_directory')
  9.  
  10. suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
  11. print('suite:',suite)
  12. return suite
  13. if __name__ == '__main__':
  14. s = get_suite()
  15. print(s.countTestCases())
  16. with open('unittestTestReport.txt','a')as f:
  17. #注意:verbosity参数可以控制输出的错误报告的详细程度,只有3个取值
  18. #0 (quiet): 只显示执行的用例的总数和全局的执行结果。
  19. #1 (default): 默认值,显示执行的用例的总数和全局的执行结果,并对每个用例的执行结果(成功T或失败F)有个标注。
  20. #2 (verbose): 显示执行的用例的总数和全局的执行结果,并输出每个用例的详细的执行结果。
  21. runner = unittest.TextTestRunner(stream=f,verbosity=2)
  22. runner.run(s)

test_suite.py

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

  1. test_add (test_mathfunc.TestCount) ... ok
  2. test_divide (test_mathfunc.TestCount) ... ok
  3. test_multi (test_mathfunc.TestCount) ... ok
  4. test_sub (test_mathfunc.TestCount) ... ok
  5.  
  6. ----------------------------------------------------------------------
  7. Ran 4 tests in 0.001s
  8.  
  9. OK

unittestTextReport.txt

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

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

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

  1. #_*_ coding=utf-8 _*_
  2. import unittest
  3. from unittest import mock
  4. from test_case_directory import test_mathfunc
  5. import HTMLTestRunner
  6. import os.path
  7.  
  8. def get_suite():
  9. test_dir = os.path.dirname(os.path.realpath(__file__))
  10. test_dir = os.path.join(test_dir,'test_case_directory')
  11.  
  12. suite = unittest.defaultTestLoader.discover(test_dir, pattern='test_*.py',top_level_dir=None)
  13. print('suite:',suite)
  14. return suite
  15. if __name__ == '__main__':
  16. s = get_suite()
  17. print(s.countTestCases())
  18. with open('HTMLReport.html','wb')as f:
  19. #注意:verbosity参数可以控制输出的错误报告的详细程度,只有3个取值
  20. #0 (quiet): 只显示执行的用例的总数和全局的执行结果。
  21. #1 (default): 默认值,显示执行的用例的总数和全局的执行结果,并对每个用例的执行结果(成功T或失败F)有个标注。
  22. #2 (verbose): 显示执行的用例的总数和全局的执行结果,并输出每个用例的详细的执行结果。
  23. runner = HTMLTestRunner.HTMLTestRunner(stream=f,title='MathFunc Test Report',description='generated by HTMLTestRunner.',verbosity=2)
  24. runner.run(s)

test_suite.py

输出 的测试报告如下图:

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

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

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

  1. import unittest
  2. from conut import *
  3. #定义测试类,父类为unittest.TestCase。
  4. class TestCount(unittest.TestCase):
  5. #定义setUp()方法用于测试用例执行前的初始化工作
  6. """测试类:Conut"""
  7. def setUp(self):
  8. #print('setUp')
  9. self.obj = Count()
  10. #定义多个测试用例,以“test_”开头命名的方法
  11. def test_add(self):
  12. """测试加法"""
  13. self.assertEqual(10, self.obj.add(10, 5))
  14.  
  15. def test_sub(self):
  16. """测试减法"""
  17. self.assertEqual(5, self.obj.sub(10, 5))
  18.  
  19. def test_multi(self):
  20. """测试乘法"""
  21. self.assertEqual(50, self.obj.multi(10, 5))
  22.  
  23. def test_divide(self):
  24. """测试除法"""
  25. self.assertEqual(2, self.obj.divide(10, 5))
  26. self.assertEqual(2.5,self.obj.divide(5, 2))
  27. # 定义tearDown()方法用于测试用例执行之后的善后工作。
  28. def tearDown(self):
  29. #print('tearDown')
  30. self.obj = None

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

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

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

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

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

Python创建 SMTP 对象语法如下:

  1.  
  1. import smtplib
  2.  
  3. smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )

参数说明:

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

参数说明:

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

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

  1.  

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

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

  1. #!/usr/bin/python
  2. # -*- coding: UTF-8 -*-
  3.  
  4. import smtplib
  5. from email.mime.text import MIMEText
  6. from email.header import Header
  7.  
  8. sender = 'from@runoob.com'
  9. receivers = ['429240967@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
  10.  
  11. # 三个参数:第一个为文本内容,第二个 plain 设置文本格式,第三个 utf-8 设置编码
  12. message = MIMEText('Python 邮件发送测试...', 'plain', 'utf-8')
  13. message['From'] = Header("菜鸟教程", 'utf-8') # 发送者
  14. message['To'] = Header("测试", 'utf-8') # 接收者
  15.  
  16. subject = 'Python SMTP 邮件测试'
  17. message['Subject'] = Header(subject, 'utf-8')
  18.  
  19. try:
  20. smtpObj = smtplib.SMTP('localhost')
  21. smtpObj.sendmail(sender, receivers, message.as_string())
  22. print "邮件发送成功"
  23. except smtplib.SMTPException:
  24. print "Error: 无法发送邮件"

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

  1. import smtplib
  2. from email.mime.text import MIMEText
  3. from email.mime.multipart import MIMEMultipart
  4.  
  5. from email.utils import formataddr
  6. import os.path
  7. from constant.constant import REPORT_PATH
  8. from utils.common.log import logger
  9.  
  10. # 使用第三方邮件服务商的SMTP发送邮件
  11. #QQ 邮箱 SMTP 服务器地址:smtp.qq.com,ssl 端口:465。
  12. mail_host = "smtp.qq.com" # 设置服务器
  13. sender = "xxxxxxxx@qq.com" # 用户名
  14. password = "xxxxxxxxxxxx" # QQ邮箱的SMTP授权码
  15. receivers = ['xxxxxxxxx@qq.com','xxxxxxxxxxx@163.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
  16. file = os.path.join(REPORT_PATH, 'ExampleReport.html')#测试报告地址
  17.  
  18. def mail():
  19. # 创建一个带附件的实例
  20. message = MIMEMultipart()
  21. # 括号里的对应发件人邮箱昵称、发件人邮箱账号
  22. message['From'] = formataddr(['发件人姓名',sender])
  23. logger.info('发件人邮箱:%s' % sender)
  24. # 括号里的对应收件人邮箱昵称、收件人邮箱账号
  25. #单个收件人: message['To'] = formataddr(['收件人姓名',sender])
  26. #多个收件人:
  27. message['To'] = ';'.join(receivers)
  28. logger.info('收件人邮箱:%s' % receivers)
  29. # 邮件的主题,也可以说是标题
  30. message['Subject'] = 'Python SMTP 邮件测试'
  31. # 邮件正文内容
  32. message.attach(MIMEText('Python 邮件发送测试...', 'plain', 'utf-8'))
  33. # 构造附件1
  34. att1 = MIMEText(open(file, 'rb').read(), 'base64', 'utf-8')
  35. logger.info('读取附件')
  36. att1["Content-Type"] = 'text/html'
  37. # filename是附件名,附件名称为中文时的写法
  38. #att1.add_header("Content-Disposition", "attachment", filename=("gbk", "", "xxx接口自动化测试报告.html"))
  39. # 附件名称非中文时的写法
  40. att1["Content-Disposition"] = 'attachment; filename="ExampleReport.html")'
  41. #添加附件
  42. message.attach(att1)
  43. logger.info('添加附件')
  44. try:
  45. # 发件人邮箱中的SMTP服务器,一般端口是25
  46. server = smtplib.SMTP_SSL(mail_host, 465)
  47. logger.info('连接QQ邮箱smtp服务')
  48. # 括号中对应的是发件人邮箱账号、邮箱密码
  49. server.login(sender, password)
  50. logger.info('连接成功')
  51. # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件
  52. server.sendmail(sender, receivers, message.as_string())
  53. # 关闭连接
  54. server.quit()
  55. logger.info("邮件发送成功")
  56. except Exception:
  57. logger.error("邮件发送失败", exc_info=1)

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

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

  1. class Email:
  2. def __init__(self, server, sender, password, receiver, title, message=None, path=None):
  3. """初始化Email
  4.  
  5. :param title: 邮件标题,必填。
  6. :param message: 邮件正文,非必填。
  7. :param path: 附件路径,可传入list(多附件)或str(单个附件),非必填。
  8. :param server: smtp服务器,必填。
  9. :param sender: 发件人,必填。
  10. :param password: 发件人SMTP授权码,必填。
  11. :param receiver: 收件人,多收件人用“;”隔开,必填。
  12. """
  13. self.title = title
  14. self.message = message
  15. self.files = path
  16. self.msg = MIMEMultipart('related')
  17. self.server = server
  18. self.sender = sender
  19. self.receiver = receiver
  20. self.password = password
  21.  
  22. def _attach_file(self, att_file):
  23. """将单个文件添加到附件列表中"""
  24. att = MIMEText(open('%s' % att_file, 'rb').read(), 'plain', 'utf-8')
  25. att["Content-Type"] = 'application/octet-stream'
  26. file_name = re.split(r'[\\|/]', att_file)
  27. att["Content-Disposition"] = 'attachment; filename="%s"' % file_name[-1]
  28. self.msg.attach(att)
  29. logger.info('attach file {}'.format(att_file))
  30.  
  31. def send(self):
  32. self.msg['Subject'] = self.title
  33. self.msg['From'] = self.sender
  34. self.msg['To'] = self.receiver
  35.  
  36. # 邮件正文
  37. if self.message:
  38. self.msg.attach(MIMEText(self.message))
  39.  
  40. # 添加附件,支持多个附件(传入list),或者单个附件(传入str)
  41. if self.files:
  42. if isinstance(self.files, list):
  43. for f in self.files:
  44. self._attach_file(f)
  45. elif isinstance(self.files, str):
  46. self._attach_file(self.files)
  47.  
  48. # 连接服务器并发送
  49. try:
  50. smtp_server = smtplib.SMTP(self.server) # 连接sever
  51. except (gaierror and error) as e:
  52. logger.exception('发送邮件失败,无法连接到SMTP服务器,检查网络以及SMTP服务器. %s', e)
  53. else:
  54. try:
  55. smtp_server.login(self.sender, self.password) # 登录
  56. except smtplib.SMTPAuthenticationError as e:
  57. logger.exception('用户名密码验证失败!%s', e)
  58. else:
  59. smtp_server.sendmail(self.sender, self.receiver.split(';'), self.msg.as_string()) # 发送邮件
  60. finally:
  61. smtp_server.quit() # 断开连接
  62. logger.info('发送邮件"{0}"成功! 收件人:{1}。如果没有收到邮件,请检查垃圾箱,'
  63. '同时检查收件人地址是否正确'.format(self.title, self.receiver))
  64.  
  65. if __name__ == '__main__':
  66. # 使用第三方邮件服务商的SMTP发送邮件
  67. #QQ 邮箱 SMTP 服务器地址:smtp.qq.com,ssl 端口:465。
  68. mail_host = "smtp.qq.com" # 设置服务器
  69. sender = "xxxxxxxxx@qq.com" # 用户名
  70. password = "xxxxxxxxxxxxxxxxx" # QQ邮箱的SMTP授权码
  71. receivers = ['xxxxxxxxx@qq.com','xxxxxxxxxxx@163.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
  72. file = os.path.join(REPORT_PATH, 'ExampleReport.html')#测试报告地址
  73. e = Email(title='测试报告',
  74. message='这是今天的测试报告,请查收!',
  75. receiver=';'.join(receivers),
  76. server=mail_host,
  77. sender=sender,
  78. password=password,
  79. path=file
  80. )
  81. e.send()
  1.  

>>>>>>>待续

python之UnittTest模块的更多相关文章

  1. python之platform模块

    python之platform模块 ^_^第三个模块从天而降喽!! 函数列表 platform.system() 获取操作系统类型,windows.linux等 platform.platform() ...

  2. python之OS模块详解

    python之OS模块详解 ^_^,步入第二个模块世界----->OS 常见函数列表 os.sep:取代操作系统特定的路径分隔符 os.name:指示你正在使用的工作平台.比如对于Windows ...

  3. python之sys模块详解

    python之sys模块详解 sys模块功能多,我们这里介绍一些比较实用的功能,相信你会喜欢的,和我一起走进python的模块吧! sys模块的常见函数列表 sys.argv: 实现从程序外部向程序传 ...

  4. 学习PYTHON之路, DAY 6 - PYTHON 基础 6 (模块)

    一 安装,导入模块 安装: pip3 install 模块名称 导入: import module from module.xx.xx import xx from module.xx.xx impo ...

  5. linux下python调用c模块

    在C调用Python模块时需要初始化Python解释器,导入模块等,但Python调用C模块却比较简单,下面还是以helloWorld.c 和 main.py 做一说明:   (1)编写C代码,hel ...

  6. Python学习之模块进程函数详解

    今天在看<Beginning Linux Programming>中的进程相关部分,讲到Linux几个进程相关的系统函数: system , exec , fork ,wait . Pyt ...

  7. python基础——第三方模块

    python基础——第三方模块 在Python中,安装第三方模块,是通过包管理工具pip完成的.  如果你正在使用Mac或Linux,安装pip本身这个步骤就可以跳过了.  如果你正在使用Window ...

  8. python基础——使用模块

    python基础——使用模块 Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用. 我们以内建的sys模块为例,编写一个hello的模块: #!/usr/bin/env ...

  9. python 中time模块使用

    在开始之前,首先要说明这几点: 1.在Python中,通常有这几种方式来表示时间:1)时间戳 2)格式化的时间字符串 3)元组(struct_time)共九个元素.由于Python的time模块实现主 ...

随机推荐

  1. DJANGO之自定义模板过滤器

    我查找了DJANGO模板的过滤器,好像指定字符串包含指定关-键字符的过滤器没有呢, 没有硬着头-皮,按网上其它人的作法,写了一个,成功了...:) 参考URL: http://liuzhijun.it ...

  2. 第五节、矩阵分解之LU分解

    一.A的LU分解:A=LU 我们之前探讨过矩阵消元,当时我们通过EA=U将A消元得到了U,这一节,我们从另一个角度分析A与U的关系 假设A是非奇异矩阵且消元过程中没有行交换,我们便可以将矩阵消元的EA ...

  3. faster-rcnn代码阅读1

    毫无疑问,faster-rcnn是目标检测领域的一个里程碑式的算法.本文主要是本人阅读python版本的faster-rcnn代码的一个记录,算法的具体原理本文也会有介绍,但是为了对该算法有一个整体性 ...

  4. nyoj-20-吝啬的国度(深搜)

    吝啬的国度 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描写叙述 在一个吝啬的国度里有N个城市.这N个城市间仅仅有N-1条路把这个N个城市连接起来.如今,Tom在第S号城市 ...

  5. Narrow Art Gallery

    Time Limit: 4000ms, Special Time Limit:10000ms, Memory Limit:65536KB Total submit users: 11, Accepte ...

  6. Android后台服务拍照的解决方式

    一.背景介绍 近期在项目中遇到一个需求.实现一个后台拍照的功能. 一開始在网上寻找解决方式.也尝试了非常多种实现方式,都没有惬意的方案.只是确定了难点:即拍照要先预览,然后再调用拍照方法.问题也随之而 ...

  7. 大数据攻城狮之Linux基础------rpm软件管理

    rpm的英文名称为: Redhat package manager 常用的命令加组合: i 安装 rpm -ivh 软件包名 当然我们的rpm也可以支持多包同时操作 rpm -ivh 软件包1 软件包 ...

  8. IP与以太网的包收发操作

    你好,这是<网络是怎样连接的>的第3篇读书笔记,第二章<用电信号传输TCP/IP>后半部分:IP与以太网的包收发操作. 先看下经典的TCP/IP四层模型: 通常,下层模块支撑上 ...

  9. poj 1161 Floyd+枚举

    题意是: 给出n个点,围成m个区域.从区域到另一个区域间需穿过至少一条边(若两区域相邻)——边连接着两点. 给出这么一幅图,并给出一些点,问从这些点到同一个区域的穿过边数最小值. 解题思路如下: 将区 ...

  10. Hadoop MapReduce编程 API入门系列之统计学生成绩版本2(十八)

    不多说,直接上代码. 统计出每个年龄段的 男.女 学生的最高分 这里,为了空格符的差错,直接,我们有时候,像如下这样的来排数据. 代码 package zhouls.bigdata.myMapRedu ...