Python单元测试框架 unittest详解

一 整体结构概览

unittest原名为PyUnit,是由java的JUnit衍生而来。对于单元测试,需要设置预先条件,对比预期结果和实际结果。

  • TestCase :通过继承TestCase类,我们可以创建一个test,或者一组tests. 一个TestCase的实例就是一个测试用例,是一个完整的测试流程,包括测试前准备环境的搭建(setUp),实现测试过程的代码(run),测试后环境的还原(tearDown).
  • Test Suites : 测试套件, 把多个测试用例集合在一起来执行。可以通过addTest加载TestCase到Test Suite中,从而返回一个TestSuite实例。
  • Test Fixtures : setup + test case + teardown结构 , 对一个测试用例环境的搭建和销毁。通过覆盖TestCase的setUp()和tearDown()方法来实现。tearDown()为下一个测试用例提供一个干净的环境。
  • Test Runner : 测试的执行,通过TextTestRunner类提供的run()方法来执行Test Suite/TestCase。Test Runner可以使用图形界面,文本界面,或者返回一个特殊的值的方式来表示测试执行的结果。
所有的测试函数以test开头,test_XXX。

简单的示例:
# -*-coding:utf-8-*-
import unittest

# 被测试的函数,姓名格式化输出
def get_formatted_name(first,last):
    """Generate a neatly formatted full name."""
    full_name = first + " " + last
    return full_name.title()

class NameTestCase(unittest.TestCase):
    # 从unitteset的包中继承TestCase这个类,这样Python能够识别你编写的测试
    def test_first_last_name(self):
        """
        测试用例:所有以test开头的
        """
        formatted_name = get_formatted_name("jian","yu")
        self.assertEqual(formatted_name,"Jia Yu")

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

如果被测试的函数(测试用例)本身有错误

 会报E

如果 测试用例本身没错 而在判断比对 self.assertEqual 的时候 不一致 则会报F

以上是函数的测试,类的测试与函数的测试及其相似 

 1 import unittest
 2 
 3 #-*-coding:utf-8-*-
 4 class AnonymousSurvey():
 5     """收集匿名调查问卷答案"""
 6     def __init__(self,question=None):
 7         """
 8         Args:
 9             question:restore the answers for question
10         Return:
11             None
12         """
13         self.question = question
14         self.responses = []
15 
16     def show_question(self):
17         """
18         print question
19         """
20         print(self.question)
21 
22     def store_response(self,new_response):
23         self.responses.append(new_response)
24 
25     def show_results(self):
26         """
27         显示收集到的所有答案
28         """
29         print("Survey Results")
30         for response in self.responses:
31             print("-"+response)
32 
33 
34 class TestAnonymousSurvey(unittest.TestCase):
35     """
36     A test according to AnonymousSurvey 
37     """
38     def test_store_single_response(self):
39         question = "what lauguage did you first learn to speak?"
40         my_survey = AnonymousSurvey(question)
41         my_survey.store_response("Chinese")
42         self.assertIn("Chinese",my_survey.responses)
43 
44     def test_store_multi_responses(self):
45         question = "what lauguage did you first learn to speak?"
46         my_survey = AnonymousSurvey(question)
47         responses = ["Chinese","English","French","German"]
48         for response in responses:
49             my_survey.store_response(response)
50         for response in responses:
51             self.assertIn(response,my_survey.responses)
52 
53 if __name__ == "__main__":
54     unittest.main()

这里可以看到 两个测试样例有一定的重复部分,可以利用unittest.TestCase类方法setUp(),作为共享变量初始化,Python运行TestCase的类会首先运行setUp() (相当于unittest.TestCase版的def __init__())

#-*-coding:utf-8-*-
import unittest
class AnonymousSurvey():
    """收集匿名调查问卷答案"""
    def __init__(self,question=None):
        """
        Args:
            question:restore the answers for question
        Return:
            None
        """
        self.question = question
        self.responses = []

    def show_question(self):
        """
        print question
        """
        print(self.question)

    def store_response(self,new_response):
        self.responses.append(new_response)

    def show_results(self):
        """
        显示收集到的所有答案
        """
        print("Survey Results")
        for response in self.responses:
            print("-"+response)


class TestAnonymousSurvey(unittest.TestCase):
    """
    A test according to AnonymousSurvey 
    """
    def setUp(self):
        question = "what lauguage did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ["Chinese","English","French","German"]

    def test_store_single_response(self):
        self.my_survey.store_response("Chinese")
        self.assertIn("Chinese",self.my_survey.responses)

    def test_store_multi_responses(self):
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response,self.my_survey.responses)

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

但值得注意的时 setUp()内的my_survey和responses都需要增加前缀self(意为储存在类属性中),

因此可在这个类的任何地方使用

否则无法传递

二 命令行

  •  从命令行中可以运行单元测试的模块,类,甚至单独的测试方法。
 python -m unittest test_module1 test_module2 
 #同时测试多个module
 python -m unittest test_module.TestClass
 python -m unittest test_module.TestClass.test_method
  • 显示更详细的测试结果的说明使用 [-v] flag:
 python -m unittest -v test_module
  • 查看所有的命令行选项使用命令
 python -m unittest -h

三 TestCase

  • Testcase类  <形如class xxxTestCase(unittest.TestCase)  > 
TestCase的实例是最小的可测试单元。 testcase 是由unittest的TestCase类的实例表示的。要编写自己的测试用例必须继承TestCase类,或者使用FunctionTestCase。且Testcase类提供了各种assert的方法来检测预期结果和实际结果。
看下面的例子(创建一个测试类DefaultWidgetSizeTestCase):
import unittest
class DefaultWidgetSizeTestCase(unittest.TestCase): 
#unittest.TestCase表示某个测试函数
  def runTest(self):
    widget = Widget('The widget')
    self.assertEqual(widget.size(), (50, 50), 'incorrect default size')

创建实例建立这样一个测试用例的一个实例,使用该类的构造函数,且不带参数(这样会执行所有的测试方法):
testCase = DefaultWidgetSizeTestCase()

我们也可以创建多个实例,且每个实例之间都是独立的。当我们需要对不同的实例执行不同的测试方法时,我们可以将要执行的测试用例方法在创建实例时通过类参数传入。
建了两个WidgetTestCase的实例,每个实例只运行WidgetTestCase类中的一个测试方法(通过参数传入)
defaultSizeTestCase = WidgetTestCase('test_default_size')
resizeTestCase = WidgetTestCase('test_resize')

常用断言方法 <查看官方文档>
unittest库提供了很多实用方法来检测程序运行的结果和预期。包括三种类型的方法,每一种都覆盖了典型的类型

  • 检测元素是否相等:assertEqual(a,b [,msg]):
assertEqual(element.text, "10")
assertNotEqual(a,b [,smg]):检测a!==b.
  • 检测表达式是否为Ture,或者 False:
assertTrue(x [,msg]) 
assertFalse(x [,msg])
  • 检测异常assertRaises(exc, fun, *args, **kwds)
assertRaiseRegexp(exc, r, fun, *args, **kwds)
  • 逻辑运算
assertGreater(a, b) # 检测a > b.
assertGreaterEqual(a ,b) # 检测a >= b.
assertLess(a, b) #检测a < b.
assertLessEqual(a, b) #检测a <= b.="" <="" code=""></=>
  • 正则表达式,检测正则是否匹配给定的text
assertRegexpMatches(s, r) #检测r.search(s).
assertNotRegexpMatches(s, r) #检测not r.search(s).

四 Test fixtures

方法固定装置:
如果要对一个模块中的每一个测试函数都做同样的初始化操作和结尾清除等操作,那么创建n个测试用例就得写n遍一样的代码,为了减少重复的代码,可以使用下面两个函数:

setUp(): 每次执行测试用例之前调用。无参数,无返回值。该方法抛出的异常都视为error,而不是测试不通过。没有默认的实现。

tearDown(): 每次执行测试用例之后调用。无参数,无返回值。测试方法抛出异常,该方法也正常调用,该方法抛出的异常都视为error,而不是测试不通过。只用setUp()调用成功,该方法才会被调用。没有默认的实现。通过setup 和 tesrDown组装一个module成为一个固定的测试装置。注意:如果setup运行抛出错误,则测试用例代码则不会执行。但是,如果setpu执行成功,不管测试用例是否执行成功都会执行teardown。

Class固定装置:
必须为类实现

setUpClass():一个类方法在单个类测试之前运行。setUpClass作为唯一的参数被调用时,必须使用classmethod()作为装饰器。

tearDownClass():一个类方法在单个类测试之后运行。setUpClass作为唯一的参数被调用时,必须使用classmethod()作为装饰器。

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

五 使用Text Suite组织测试代码

unittest.TestSuite(tests=())

该类聚合测试用例和测试套件,运行一个TestSuite实例遍历套件,和单独运行每个testcase是相同的。TestSuite对象的行为就像TestCase对象,除了他们不实现一个测试。
一些方法可以将testcase添加到TestSuite实例:

addTest(test):Add a TestCase or TestSuite to the suite.

addTests(tests):添加所有的tests从可迭代的TestCase和TestSuite实例测试套件。这相当于迭代调用addTest()来添加每个元素。

根据不同的业务可能需要在不同的module中选择某一个或者几个测试用例,此时可以根据每个测试实例的特征对测试方法打包:
widgetTestSuite = unittest.TestSuite()
#创建一个测试套件实例
widgetTestSuite.addTest(WidgetTestCase('test_default_size'))
#添加测试用例到套件,抽取WidgetTestCase类中的test_default_size测试用例添加到
testsuitewidgetTestSuite.addTest(WidgetTestCase('test_resize'))
#添加测试用例到套件,抽取WidgetTestCase类中的test_resize测试用例添加到testsuite

可以返回该测试套件的get入口:
def suite():
suite = unittest.TestSuite()
suite.addTest(WidgetTestCase('test_default_size'))
suite.addTest(WidgetTestCase('test_resize'))
return suite

或者更简洁的写法:
def suite():
tests = ['test_default_size', 'test_resize']
return unittest.TestSuite(map(WidgetTestCase, tests))

测试套件中也可以包含测试套件:
suite1 = module1.TheTestSuite()
suite2 = module2.TheTestSuite()
alltests = unittest.TestSuite([suite1, suite2])

使用TestLoader
lass unittest.TestLoader

TestLoader 用来从clases和modules创建test suites,通常也需要创建一个该类的实例,unittest模块提供了一个实例,可以作为unittest.defaultTestLoader共享。使用一个子类或实例,允许定制可配置属性。该类有以下方法:loadTestsFromTestCase(testCaseClass):

loadTestsFromModule(module):返回一个给定的模块中所有测试用例,打包成一个套件返回。该类创建一个testsuites然后加载一个module并执行其中所有的测试用例,执行的顺序是根据测试用例的名称来的。
suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
#执行WidgetTestCase中所有的测试用例

你可以将测试用例和测试套件放在一个module中,最好是分开放置,方便重构管理,如果测试策略改变了,也方便维护。

六 跳过测试和预期的失败

Unittest支持跳过单个的测试方法甚至整个类的测试。使用 skip() decorator来设置特定跳过的条件,如指定操作系统不执行该测试。

执行的时候如果满足跳过条件,控制台会将后面的说明打印出来,并跳过该测试用例。跳过类也是相似的写法。也可以自定义skipping装饰器。

定义预期的失败使用unittest.expectedFailure(),运行时 ,如果测试失败,测试不算作失败。

class TestDtuOp(unittest.TestCase):
    def setUp(self):
        print("
=======================================")

    def tearDown(self):
        pass

    @tt.show_test_case_name
    def test_upper(self):
        """ This test should be passed. """
        self.assertEqual('foo'.upper(), 'FOO')

    def test_error(self):
        """ This test should be marked as error one. """
        raise ValueError

    def test_fail(self):
        """ This test should fail. """
        self.assertEqual(1, 2)

    @unittest.skip("This is a skipped test.")
    def test_skip(self):
        """ This test should be skipped. """
        pass
    
    @unittest.expectedFailure
    def test_expectedFailure(self):
        """ This test should be expectedFailure. """
        pass

七 使用HTMLTestRunner生成报告

unittest本身并不具备这个功能,需要使用HTMLTestRunner库使用步骤 (这里暂时不进行拓展)

原文地址:https://www.cnblogs.com/clemente/p/10209919.html