用python搭建自动化测试框架,我们需要组织用例以及测试执行,这里博主推荐Python的标准库--unittest。

unirtest是xUnit系列框架中的一员,如果你了解xUnit的其他成员,那你用unittest来应该是很轻松的,他们的工作方式都差不多。

unittest核心工作原理

unittest中最核心的几个个概念是:testCase、testSuiteTestLoadertestRunner、testFixture

下面我们分别来解释这四个概念的意思,先来看一张unittest的静态类图

 

Android Unittest框架_Android Unittest框架

  • testCase:
  • 一个testCase的实例就是一个测试用例。
  • 什么是测试用例呢?
  • 就是一个完整的测试流程,包括测试前准备环境的搭建setUp,执行测试代码run,以及测试后环境的还原tearDown。
  • 单元测试的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证
  • testSuite:
  • 多个testCase集合在一起就是testSuite,而且testSuite也可以嵌套TestSuite
  • testLoader:
  • testLoader是用来加载testCase到testSiute中的,其中有几个loadTestForm方法,就是从各个地方寻找testCase,创建他们的实例,然后add到testSuite中,再返回一个testSuite实例
  • testRunner:
  • testRunner是用来执行测试用例的,其中的run(test)会执行TestCase中的run(result)方法。
  • 测试结果会保存在textTestResult实例中,包括运行了多少测试用例,成功了多少、失败了多少等信息
  • testFixture:
  • 一个测试用例环境的搭建和销毁就是一个fixture
一个class继承了unittest.TestCase,便是一个测试用例,但是如果类中有多个以test开头的方法,
那么,每有一个这样的方法,在load的时候便会生成一个TestCase实例。
如一个class中有四个test_XXX方法,最后load到suite中也有四个测试用例。

到这里,整个流程就很清楚了:

  1. 写好TestCase
  2. 由TestLoader加载发TestCase到TestSuite中
  3. 由textTestRunner来运行TestSuite
  4. 运行的结果保存在textTestResult中

补充:

  • 我们通过命令或者unittest.main()执行测试用例时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过textTestRunner来执行用例
  • 在执行Runner时,默认将执行结果输出到控制台,我们可以设置其输出到指定文件中,在文件中查看结果

unittest实例

先准备一个带测试类mathfunc.py

class mathfunc(object):

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

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

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

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

 接下来为这个类中的方法写一个测试类TestMathfunc.py

import unittest

from mathfunc import *


class TestMathfunc(unittest.TestCase):

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

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

    def test_multi(self):
        self.assertEqual(4, mathfunc.multi(self, 2, 2))

    def test_divide(self):
        self.assertEqual(4, mathfunc.divide(self, 8, 2))
        self.assertEqual(2.5, mathfunc.divide(self, 5, 2))


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

执行结果:四个测试用例都执行通过了 

Android Unittest框架_ide_02

 设置case执行顺序

  • 上面的代码示例了如何编写一个简单的测试用例,但是有两个问题 :
  • 怎么控制测试用例执行的顺序?
  • 怎么批量执行测试文件?
  • 为了解决这两个问题,我们需要用到TestSuite了
  • 添加到TestSuite中的case是会按照添加的顺序执行的

在同一个文件夹中新建一个文件,test_suite.py

#-*-coding:utf-8-*-

import unittest
from test_mathfunc import TestMathFunc

if __name__=='__main__':
    suite = unnittest.TestSuite()
    test = [TestMathFunc('test_add'),TestMathFunc('test_minus')]
    suite.addTests(tests)
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

 执行结果:由输出结果可知,执行顺序跟我们添加case的顺序保持一致

test_add (test_mathfunc.TestMathfunc)
Test method add(a, b) ... ok
test_divide (test_mathfunc.TestMathfunc)
Test method divide(a, b) ... ok
test_minus (test_mathfunc.TestMathfunc)
Test method minus(a, b) ... ok
test_multi (test_mathfunc.TestMathfunc)
Test method multi(a, b) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

执行单个指定的case:

# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc('test_multi'))

将测试结果保存在文件中 

import unittest
from test_mathfunc import TestMathfunc

if __name__ == '__main__':
    suite = unittest.TestSuite()
    # tests = [TestMathfunc('test_add'), TestMathfunc('test_minus')]
    # suite.addTests(tests)
    #
    # runner = unittest.TextTestRunner(verbosity=2)
    # runner.run(suite)
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathfunc))

    with open('test.txt', 'a') as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(suite)

Android Unittest框架_Android Unittest框架_03

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

test fixture之setUp、tearDown

上面整个测试基本跑下来了,但是可能会遇到点特殊的情况:

  • 如果我的测试需要在每次执行之前准备侧睡环境,或者在每次执行完之后需要进行一些清理怎么办?
  • 比如执行前需要链接数据库,执行完之后需要还原数据库、断开链接 

不可能每个测试方法中都天际准备环境、清理环境的代理。这就要涉及到我们之前说过的test fixture了

修改test_mathfunc.py

import unittest

from mathfunc import *


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)"""
        self.assertEqual(3, mathfunc.add(self, 1, 2))
        self.assertNotEqual(3, mathfunc.add(self, 2, 2))

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

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

    def test_divide(self):
        """Test method divide(a, b)"""
        self.assertEqual(4, mathfunc.divide(self, 8, 2))
        self.assertEqual(2.5, mathfunc.divide(self, 5, 2))


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

 运行结果:

Ran 4 tests in 0.002s

OK
do something before test.Prepare environment.
do something after test.Clean up.
do something before test.Prepare environment.
do something after test.Clean up.
do something before test.Prepare environment.
do something after test.Clean up.
do something before test.Prepare environment.
do something after test.Clean up.

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

跳过某个case

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

  1. skip装饰器
@unittest.skip("jump")
    def test_divide(self):
        """Test method divide(a, b)"""
        self.assertEqual(4, mathfunc.divide(self, 8, 2))
        self.assertEqual(2.5, mathfunc.divide(self, 5, 2))

 执行结果:可以看到总的test数量还是4个,但divide()方法被skip了

Ran 4 tests in 0.003s

OK (skipped=1)

Skipped: jump
do something before test.Prepare environment.
do something after test.Clean up.
do something before test.Prepare environment.
do something after test.Clean up.

2.TestCase.skipTest()方法

  • skip装饰器一共有三个unittest.skip(reason)、unittest.skipIf(condition, reason)、unittest.skipUnless(condition, reason)
  • skip无条件跳过
  • skipIf当condition为True时跳过
  • skipUnless当condition为False时跳过
def test_multi(self):
        """Test method multi(a, b)"""
        self.skipTest('Do not run this.')
        self.assertEqual(4, mathfunc.multi(self, 2, 2))

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

do something before test.Prepare environment.
do something after test.Clean up.

Skipped: jump
do something before test.Prepare environment.
do something after test.Clean up.
do something before test.Prepare environment.
do something after test.Clean up.

Skipped: Do not run this.


Ran 4 tests in 0.003s

OK (skipped=2)

Process finished with exit code 0

 用HTMLTestRunner输出漂亮的HTML报告

HTMLTestRunner是一个第三方的unittest HTML报告库,首先我们下载HTMLTestRunner.py,并放到当前目录下

下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html

修改test_suite.py

import unittest
from test_mathfunc import TestMathfunc
from  HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    suite = unittest.TestSuite()
    # suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathfunc))
    suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathfunc'))
    with open('HTMLReport.html', 'wb') as f:
        runner = HTMLTestRunner(stream=f,
                                title='MathFunc Test Report',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                )
        runner.run(suite)

运行结果

Android Unittest框架_ide_04

 

生成的html报告

Android Unittest框架_python_05

 总结

  • unittest是Python自带的单元测试框架,我们可以用其来作为我们自动化测试框架的用例组织执行框架。
  • unittest的流程:写好TestCase,然后由TestLoader加载TestCase到TestSuite,然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例。
  • 一个class继承unittest.TestCase即是一个TestCase,其中以 test 开头的方法在load时被加载为一个真正的TestCase。
  • verbosity参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告。
  • 可以通过addTest和addTests向suite中添加case或suite,可以用TestLoader的loadTestsFrom__()方法。
  • 用 setUp()、tearDown()、setUpClass()以及 tearDownClass()可以在用例执行前布置环境,以及在用例执行后清理环境
  • 我们可以通过skip,skipIf,skipUnless装饰器跳过某个case,或者用TestCase.skipTest方法。
  • 参数中加stream,可以将报告输出到文件:可以用TextTestRunner输出txt报告,以及可以用HTMLTestRunner输出html报告。