Python 自动化测试之unittest详解

 


1、unittest核心介绍和测试流程分析

       什么叫unittest? unittest是Python单元测试框架,类似于JUnit框架,官方网站中也说明了这个框架就是基于java的junit框架。测试框架-unittest,相当于是一个 python 版的 junit。python 里面的单元测试框架除了 unittest,还有一个 pytest 框架,目前pytest运用的较少(当然后期我们也会介绍)。

                 

为什么要用unittest?

该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果

如果想要了解更多请自行转到官方网站:

英文网站:https://docs.python.org/3/library/unittest.html

为了照顾下托福雅思满级的同志呢,我就看下中文的:

中文网站:https://docs.python.org/zh-cn/3/library/unittest.html

1.1 unittest核心工作原理:

unittest中最核心的四个概念是:test case, test suite, test runner, test fixture

下面我们分别来解释这四个概念的意思,先来看一张unittest的静态类图(下面的类图以及解释均来源于网络):

         

           写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。

2、编写unittest实例

下面我们通过一些实例来更好地认识一下unittest,在我们不熟悉这个模块之前我们最好的做法就是看底层代码,鼠标放到unittest,ctrl+鼠标左键,进入代码底层可看到如下图所示

        

    让我们直接copy当前实例实现这个效果

import unittest

class IntegerArithmeticTestCase(unittest.TestCase):
    def testAdd(self):  # test method names begin with 'test'
        self.assertEqual((1 + 2), 3)
        self.assertEqual(0 + 1, 1)
    def testMultiply(self):
        self.assertEqual((0 * 10), 0)
        self.assertEqual((5 * 8), 40)

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

运行结果如下:

代码分析以及问题解决:

Class定义一个测试类,继承unittest.Testcase

def testAdd(self):def testMultiply(self): 代表两个测试用例,心细的同学可以发现# test method names begin with 'test'  翻译过来:测试方法的名字要以test开头。

注意:如果不以test开头的方法,unittest不会检测到

self.assertEqual((1 + 2), 3):这个就是断言方法,判断前后两个数据是否等,后面也可以跟一个msg对当前断言的结果说明

unittest.main():运行后会看到测试结果

问题:可能部分同学右键点击运行脚本之后会出现如下结果

解决方法:

问题就完美解决了。

紧接着我们在原有的基础上添加自己的思想,示例如下:

def add(a,b):
    return a+b
def minus(a,b):
    return a-b
def multi(a,b):
    return a*b
def divide(a,b):
    return a/b

import unittest

class TestMathFunc(unittest.TestCase):
    '''test'''

    def test_add(self):
        self.assertEqual(3,add(1,2))
        self.assertNotEqual(3,add(2,2))

    def test_minus(self):
        self.assertEqual(1,minus(3,2))

    def test_multi(self):
        """Test method multi(a, b)"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2, divide(5, 2))
if __name__ == '__main__':
    unittest.main()

运行结果如下:

 

能够看到一共运行了4个测试,失败了1个,并且给出了失败原因,2.5 != 2 也就是说我们的divide方法是有问题的。

这就是一个简单的测试,有几点需要说明的:

1、在第一行给出了每一个用例执行的结果的标识,成功是 .,失败是 F,出错是 E,跳过是 S。从上面也可以看出,测试的执行跟方法的顺序没有关系,test_divide写在了第4个,但是却是第2个执行的。

2、每个测试方法均以 test 开头,否则是不被unittest识别的。

3、在unittest.main()中加 verbosity 参数可以控制输出的错误报告的详细程度,默认是 1,如果设为 0,则不输出每一用例的执行结果,即没有上面的结果中的第1行;

      问题:为什么我的divide [dɪˈvaɪd]:除法在最后一个写,跑测试用例的时候却在第二个出现F并指明test_divide?

       因为测试用例的顺序按照ascii码进行排序的(指定排序必不可少,在后面我们会有讲到)

3、深入探索test fixture之setUp和tearDown

上面整个测试基本跑了下来,但可能会遇到点特殊的情况:如果我的测试需要在每次执行之前准备环境,或者在每次执行完之后需要进行一些清理怎么办?比如执行前需要连接数据库,执行完成之后需要还原数据、断开连接。总不能每个测试方法中都添加准备环境、清理环境的代码吧。

所以说我们引用连个方法:setUp(前置条件)和tearDown(后置条件) 固定写法

这就要涉及到我们之前说过的test fixture了,修改test_mathfunc.py

import unittest


def add(a,b):
    return a+b

def minus(a,b):
    return a-b

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

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

class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""
    def setUp(self):
        print ("do something before test.Prepare environment.")


    def tearDown(self):
        print ("do something after test.Clean up.")

    def test_add(self):
        """Test method add(a, b)"""
        print ("add")
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        print ("minus")
        self.assertEqual(1, minus(3, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        print ("multi")
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        print ("divide")
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

我们添加了 setUp() 和 tearDown() 两个方法(其实是重写了TestCase的这两个方法),这两个方法在每个测试方法执行前以及执行后执行一次,setUp用来为测试准备环境,tearDown用来清理环境,已备之后的测试。

我们再执行一次:

可以看到setUp和tearDown在每次执行case前后都执行了一次。

如果想要在所有case执行之前准备一次环境,并在所有case执行结束之后再清理环境,我们可以用 setUpClass() tearDownClass():

执行结果如下:

class TestMathFunc(unittest.TestCase):

    """Test mathfuc.py"""
    @classmethod
    def setUpClass(cls):

        print ("This setUpClass() method only called once.")

    @classmethod
    def tearDownClass(cls):
        print ("This tearDownClass() method only called once too.")

 

 

可以看到setUpClass以及tearDownClass均只执行了一次

4、Skip跳过某个case

如果我们临时想要跳过某个case不执行怎么办?unittest也提供了几种方法:

skip装饰器

@unittest.skip("I don't want to run this case")

def test_divide(self):

    """Test method divide(a, b)"""

    print ("divide")

    self.assertEqual(2, divide(6, 3))

    self.assertEqual(2.5, divide(5, 2))

执行:

可以看到总的test数量还是4个,但divide()方法被skip了。

skip装饰器一共有三个 unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason),skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过。

TestCase.skipTest()方法

def test_divide(self):
    """Test method divide(a, b)"""
    self.skipTest('Do not run this')
    print ("divide")
    self.assertEqual(2, divide(6, 3))
    self.assertEqual(2.5, divide(5, 2))

输出:

效果跟上面的装饰器一样,跳过了divide方法。

测试补充:

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.TestCase:TestCase类,所有测试用例类继承的基本类。

unittest.main():使用她可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行他们。执行方法的默认顺序是:根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。所以以A开头的测试用例方法会优先执行,以a开头会后执行。

unittest.TextTextRunner():unittest框架的TextTextRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件。

unittest.skip():装饰器,当运行用例时,有些用例可能不想执行等,可用装饰器暂时屏蔽该条测试用例。一种常见的用法就是比如说想调试某一个测试用例,想先屏蔽其他用例就可以用装饰器屏蔽。

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

assertEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,相等则测试用例通过。

assertNotEqual(a,b,[msg='测试失败时打印的信息']):断言a和b是否相等,不相等则测试用例通过。

assertTrue(x,[msg='测试失败时打印的信息']):断言x是否True,是True则测试用例通过。

assertFalse(x,[msg='测试失败时打印的信息']):断言x是否False,是False则测试用例通过。

assertIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,是则测试用例通过。

assertNotIs(a,b,[msg='测试失败时打印的信息']):断言a是否是b,不是则测试用例通过。

assertIsNone(x,[msg='测试失败时打印的信息']):断言x是否None,是None则测试用例通过。

assertIsNotNone(x,[msg='测试失败时打印的信息']):断言x是否None,不是None则测试用例通过。

assertIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,在b中则测试用例通过。

assertNotIn(a,b,[msg='测试失败时打印的信息']):断言a是否在b中,不在b中则测试用例通过。

assertIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,是则测试用例通过。

assertNotIsInstance(a,b,[msg='测试失败时打印的信息']):断言a是是b的一个实例,不是则测试用例通过。

原创文章 49 获赞 23 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Smile_Mr/article/details/103127024
今日推荐