unittest单元测试框架简介

1、unittest单元测试框架的介绍

  unittest是python语言的单元测试框架,unittest单元测试框架提供了创建测试用例、测试套件和批量执行测试用例的方案。各个组件之间的关系如下图:

 

 

 

  1、TestFixture:测试固件。用于处理初始化的操作。

  2、TestCase:测试用例。在unittest中,一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。我们可以使用unittest提供的一个基类TestCase来创建测试用例。

  3、TestSuite:测试套件。可以根据所测试的特性,将测试用例组合在一起执行。

  4、TestRunner:测试执行。用于执行和输出测试结果。

  从上图中,我们大概可以了解unittest测试框架在自动化测试中的整个流程。首先,我们需要设计出优秀的测试用例。同时,我们可以使用TestFixture(测试固件)来在其中做一些初始化的工作,如打开浏览器,关闭浏览器。然后我们需要将需要执行的测试用例加入到一个TestSuite(测试套件)中,将这些用例组合在一起使用TestRunner进行执行。在输出结果后,我们还需要输出TestReport(测试报告)

 

2、测试固件

  测试固件用于处理初始化的操作。例如我们要测试一个网站,在测试某个功能点之前,首先要做的是打开浏览器,在结束测试之后,我们还需要关闭浏览器。测试固件提供了两种执行形式,一种是每执行一条测试用例,都会执行一次测试用例,使用setUp()和tearDown();还有一种是不管多少个测试用例,测试固件只执行一次,使用setUpClass()和tearDownClass()。

2.1 setUp()和tearDown()

  上面已经说到setUp()和tearDown(),结下来看一个实际的例子:

 1 import unittest
 2 
 3 class BaiduTest(unittest.TestCase):
 4    def setUp(self):
 5       print('setUp')
 6 
 7    def tearDown(self):
 8       print('tearDown')
 9 
10    def test_001(self):
11       print('这是第一条测试用例')
12 
13    def test_002(self):
14       print('这是第二条测试用例')
15 
16 if __name__ == '__main__':
17    unittest.main(verbosity=2)

运行后得到结果:

setUp
这是第一条测试用例
tearDown
setUp
这是第二条测试用例
tearDown
test_001 (__main__.BaiduTest) ... ok
test_002 (__main__.BaiduTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

  从输出结果中,我们可以看出先执行了setUp()然后执行测试用例,再执行tearDown()。不过,每次执行完一条测试用例后,测试固件都会重复的执行一次。如果我们有N个测试用例,那么也就意味着会重复执行N次测试固件的内容。比如需要测试一个网页,如果我们一只重复的打开关闭网页,都会占用一定的资源和时间,所以这并不是一个理想的选择

2.2 setUpClass和tearDown()

  使用setUpClass()和tearDownClass()方法可以达到不管有几条测试用例,都只执行一次的目的。在使用setUpClass()和tearDownClass()方法时,我们需要在方法上面加装饰器@classmethod。案例如下:

 1 import unittest
 2 
 3 class BaiduTest(unittest.TestCase):
 4    @classmethod
 5    def setUpClass(cls):
 6       print('setUpClass')
 7 
 8    @classmethod
 9    def tearDownClass(cls):
10       print('tearDownClass')
11 
12    def test_001(self):
13       print('这是第一条测试用例')
14 
15    def test_002(self):
16       print('这是第二条测试用例')
17 
18 if __name__ == '__main__':
19    unittest.main(verbosity=2)

运行后得到结果:

setUpClass
这是第一条测试用例
这是第二条测试用例
tearDownClass
test_001 (__main__.BaiduTest) ... ok
test_002 (__main__.BaiduTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

我们可以在结果中看到,先执行了setUpClass(),然后在执行完所有测试用例之后,执行tearDownClass()

 

3、测试套件

  测试套件可以根据所测试的特性将测试用例组合在一起,它由unittest模块中的TestSuite类表示。下面我们来介绍测试套件的几种构建方式。

3.1 按顺序执行测试用例

  TestSuite类种提供了addTest()方法可以实现按顺序的执行测试用例,但是我们必须将测试用例按照顺序添加至测试套件中。如下例子:

 1 import unittest
 2 
 3 class BaiduTest(unittest.TestCase):
 4    @classmethod
 5    def setUpClass(cls):
 6       print('setUpClass')
 7 
 8    @classmethod
 9    def tearDownClass(cls):
10       print('tearDownClass')
11 
12    def test_001(self):
13       print('这是第一条测试用例')
14 
15    def test_002(self):
16       print('这是第二条测试用例')
17 
18 if __name__ == '__main__':
19   '''实例化TestSuite类'''
20    suite = unittest.TestSuite()
21   '''调用addTest方法,分别加入测试用例'''
22    suite.addTest(BaiduTest('test_002'))
23    suite.addTest(BaiduTest('test_001'))
24    unittest.TextTestRunner(verbosity=2).run(suite)

运行后得到结果:

setUpClass
这是第二条测试用例
这是第一条测试用例
tearDownClass
test_002 (__main__.BaiduTest) ... ok
test_001 (__main__.BaiduTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

  从上面的输出结果中,我们看到测试用例不再像之前一样先执行test_001,再执行test_002,而是按照我们加入到测试套件中的顺序进行执行。但是在实际的自动化测试实行中,测试用例往往多达上千个,所以这样一个一个地加入到测试套件中手动执行的方式显然是不合理的

3.2 按测试类执行测试用例

  在实际的应用中,我们会将测试用例放在测试类中。在unittest中,makeSuite就可以实现将整个测试类组成测试套件来执行其中的所有测试用例,这样也避免了上面那样逐一向测试套件中添加测试用例。如下例子

import unittest

class BaiduTest(unittest.TestCase):
   @classmethod
   def setUpClass(cls):
      print('setUpClass')

   @classmethod
   def tearDownClass(cls):
      print('tearDownClass')

   def test_001(self):
      print('这是第一条测试用例')

   def test_002(self):
      print('这是第二条测试用例')

if __name__ == '__main__':
  '''实例化TestSuite类,并在类中调用makeSuite方法'''
    suite = unittest.TestSuite(unittest.makeSuite(BaiduTest))
    unittest.TextTestRunner(verbosity=2).run(suite)

运行后得到结果:

setUpClass
这是第一条测试用例
这是第二条测试用例
tearDownClass
test_001 (__main__.BaiduTest) ... ok
test_002 (__main__.BaiduTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

   从输出结果中,我们可以确认BaiduTest测试类中所有的测试用例都成功执行了。

3.3 加载测试类

  加载测试的用法与上面的有些相似,可以使用TestLoader类来加载测试类,应用代码如下:

 1 import unittest
 2 
 3 class BaiduTest(unittest.TestCase):
 4    @classmethod
 5    def setUpClass(cls):
 6       print('setUpClass')
 7 
 8    @classmethod
 9    def tearDownClass(cls):
10       print('tearDownClass')
11 
12    def test_001(self):
13       print('这是第一条测试用例')
14 
15    def test_002(self):
16       print('这是第二条测试用例')
17 
18 if __name__ == '__main__':
19   suite = unittest.TestLoader().loadTestsFromTestCase(BaiduTest)
20     unittest.TextTestRunner(verbosity=2).run(suite)

运行结果如下:

setUpClass
这是第一条测试用例
这是第二条测试用例
tearDownClass
test_001 (__main__.BaiduTest) ... ok
test_002 (__main__.BaiduTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

  可以看出输出结果仍然与之前是一模一样的,不过在上面的代码中,我们是直接使用了TestLoader类,TestLoader类加载测试类,并将它们返回到TestSuite中。

 

4、批量执行测试用例

  在实际的测试中,我们可能会根据业务、模块将测试用例以多个文件进行区分。例如在testCase这个包中,分别有用来测试百度的test_baidu.py和test_google.py两个文件,在run_all.py中编写批量执行这两个测试用例的代码。

test_baidu.py文件代码如下:

 1 import unittest
 2 
 3 class BaiduTest(unittest.TestCase):
 4    @classmethod
 5    def setUpClass(cls):
 6       print('setUpClass百度')
 7 
 8    @classmethod
 9    def tearDownClass(cls):
10       print('tearDownClass百度')
11 
12    def test_001(self):
13       print('这是第一条测试百度的用例')
14 
15    def test_002(self):
16       print('这是第二条测试百度的用例')

 

test_google.py文件代码如下:

 1 import unittest
 2 
 3 class GoogleTest(unittest.TestCase):
 4    @classmethod
 5    def setUpClass(cls):
 6       print('setUpClass谷歌')
 7 
 8    @classmethod
 9    def tearDownClass(cls):
10       print('tearDownClass谷歌')
11 
12    def test_001(self):
13       print('这是第一条测试谷歌的用例')
14 
15    def test_002(self):
16       print('这是第二条测试谷歌的用例')

 

run_all.py文件的代码如下:

 1 import unittest
 2 import os
 3 
 4 
 5 def allCase():
 6   '''将测试用例文件加载到测试套件中'''
 7    suite = unittest.TestLoader().discover(
 8       start_dir=os.path.dirname(__file__),
 9       pattern='test_*.py',
10       top_level_dir=None
11    )
12    return suite
13 
14 if __name__ == '__main__':
15    unittest.TextTestRunner(verbosity=2).run(allCase())

  在run_all.py文件中,我们使用TestLoader这个类中的discover()方法来批量获取测试模块。在discover方法中有三个参数,分别是用来表示测试模块路径的start_dir,pattern用来获取testCase包中所有以test开头的测试用例文件,top_level_dir表示测试模块的顶层目录,如果没有顶层目录,默认为None。

 

5、断言

  在自动化测试中,我们必须要去对测试用户的输出结果与我们的预期结果进行校验。这个时候,我们就需要用到断言来判断我们的测试用例是否是通过的。断言是用来检查你认为应该满足的条件是否满足条件,如果满足条件则得到确认;如果不满足,python就会为我们抛出异常。

 

  上面的表格是一些我们常用的断言方法,那么来简单地看个例子:

 1 import unittest
 2 
 3 
 4 class lagouTest(unittest.TestCase):
 5    def test_lagou(self):
 6       r = {'id': '1', 'name': '张三'}
 7       self.assertEqual('张三', r['name'])
 8 
 9 
10 if __name__ == '__main__':
11    unittest.main(verbosity=2)

运行后得到结果:

1 Ran 1 test in 0.001s
2 
3 OK

  assertEqual()是用来验证两个值是否相等的。在这个r的字典中,name的值是张三,然后我们使用assertEqual()方法来验证我们期望的结果是否与实际的结果相等,运行后我们可以在返回结果看到这次的验证是通过的。

  那假设不通过呢?我们看下下面的例子:

 1 import unittest
 2 
 3 
 4 class lagouTest(unittest.TestCase):
 5    def test_lagou(self):
 6       r = {'id': '1', 'name': '李四'}
 7       self.assertEqual('张三', r['name'])
 8 
 9 
10 
11 if __name__ == '__main__':
12    unittest.main(verbosity=2)

运行后得到结果:

Ran 1 test in 0.004s

FAILED (failures=1)


李四 != 张三

Expected :张三
Actual   :李四
<Click to see difference>

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/teamcity/diff_tools.py", line 32, in _patched_equals
    old(self, first, second, msg)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 839, in assertEqual
    assertion_func(first, second, msg=msg)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 1220, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 680, in fail
    raise self.failureException(msg)
AssertionError: '张三' != '李四'
- 张三
+ 李四

  我们将字典r中的张三改成了李四,但是在断言中,我们预期结果仍然是张三,这个时候显然是与实际的结果是不符合的。我们运行后程序之后,python也很清晰的告诉我们最终的结果是失败的,同时也告诉了我们李四 != 张三。

 

6、测试报告的生成

  自动化测试最终的目的我们需要得到一个结果,所以我们需要用一个测试报告来清晰的体现出我们最终的测试结果。这里我们需要借助第三方库HTMLTestRunner.py,需要的可以通过下面的地址下载。

https://pan.baidu.com/s/1oD5E1o7LhkASx5U_vqZUQQ

下载文件后,把该文件放到python安装路径的Lib子文件夹中就可以在程序中使用它来。

我们创建一个report的文件夹来存放测试报告,接下来完善我们上面的run_all.py文件。代码如下:

 1 import unittest
 2 import os
 3 import HTMLTestRunner
 4 import time
 5 
 6 '''创建测试套件'''
 7 def allCase():
 8    suite = unittest.TestLoader().discover(
 9       start_dir=os.path.dirname(__file__),
10       pattern='test_*.py',
11       top_level_dir=None
12    )
13    return suite
14 
15 '''获取当前时间'''
16 def now_time():
17    nowtime = time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())
18    return nowtime
19 
20 def run():
21    '''获取当前文件的上一个目录,作为文件'''
22    report_name = os.path.join(os.path.dirname(__file__), 'report', 'report'+now_time()+'.html')
23    print('请稍后,测试报告生成中。。。')
24    fp = open(report_name, 'wb')
25    runner = HTMLTestRunner.HTMLTestRunner(
26       stream=fp,
27       title='自动化测试报告',
28       description='这是一个测试报告的例子'
29    )
30    runner.run(allCase())
31    fp.close()
32 
33 if __name__ == '__main__':
34    run()

运行后得到结果:

请稍后,测试报告生成中。。。
setUpClass百度
tearDownClass百度
setUpClass谷歌
tearDownClass谷歌
....
Time Elapsed: 0:00:00.079354

Process finished with exit code 0

同时在report文件夹下生成了一个html测试报告,如图:

 

posted @ 2019-11-01 22:10  何乐而不为  阅读(211)  评论(0)    收藏  举报