python 单元测试——unittest

发表于:2017-8-09 11:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:orientlu    来源:51Testing软件测试网采编

#
Python
分享:
  单元测试是对程序中的单个子程序、函数、过程进行的测试,面向白盒测试
  单元测试测试覆盖常用子程序的输入组合,边界条件和异常处理,尽可能保证单元测试代码简洁,避免单测本身代码有 bug 影响对测试对象的测试结果。
  python 提供单元测试框架 unittest,
  简单编写一个模块 calculator.py ,作为单元测试对象
  #!/usr/bin/env python# coding=utf-8
  def my_print(str):
      pass
      #print(str)
  class Calculator():
      __version__ = 4
      def __init__(self, a, b):
          my_print("cal init")
          self.a = int(a)
          self.b = int(b)
      def __del__(self):
          my_print("cal del")
      def add(self):
          return self.a + self.b
      def sub(self):
          return self.a - self.b
      def mul(self):
          return self.a * self.b
      def div(self):
          return self.a / self.b
  编写测试用例 (test case)
  如上, 我们为该模块编写对应的单元测试,取名 testCalculator.py :
  #!/usr/bin/env python# coding=utf-8import unittestfrom calculator import Calculator
  class CalculatorTest(unittest.TestCase):
      def test_add_0(self):
          cal = Calculator(8, 4)
          result = cal.add()
          self.assertEqual(result, 12)
      def test_add_1(self):
          cal = Calculator(8, 4)
          result = cal.add()
          self.assertNotEqual(result, 12)
          
      def will_not_callme(self):
          print("lalalla")
          if __name__ == "__main__":
      unittest.main()
  简单地编写了两个对模块方法 add() 的测试用例。编写单元测试,我们需要对应测试的对象实现一个类,继承 unittest.TestCase。
  测试类 CalculatorTest 中的测试用例都是以 test_, 其他方法在执行脚本的时候框架不会直接调用执行。
  对应目标模块的各个方法编写测试用例,使用断言判断结果,注意使用的断言是 unittest.TestCase内置的,这样才能保证不会由于某个用例断言失败而直接退出执行。
  执行 运行结果如下,可以看到,没有通过的例子断言了错误的行号,可以快速定位问题。
  $  python testCalculator.py -v
  test_add_0 (__main__.CalculatorTest) ... ok
  test_add_1 (__main__.CalculatorTest) ... FAIL
  ======================================================================
  FAIL: test_add_1 (__main__.CalculatorTest)
  ----------------------------------------------------------------------
  Traceback (most recent call last):
    File "aa.py", line 16, in test_add_1
      self.assertNotEqual(result, 12)
  AssertionError: 12 == 12
  ----------------------------------------------------------------------
  Ran 2 tests in 0.002s
  FAILED (failures=1)
  内置的断言
  测试初始化和清理(test fixture)
  看到上面的例子,每次写一个测试用例都要重新定义一个测试实例 cal, 显得很重复,但是直接在测试类初始化函数定义的话又怕用例之间相互干扰。还有就是,有些测试,需要测试前构建测试场景,测试结束后清理。
  类似以上的问题,unittest 提供了几个方法实现。
  ●setUp() : 执行每个测试用例前都会调用,执行准备
  ●tearDown() : 执行完每个用例后都会调用,执行清理
  对应上面两个方法,下面两个在测试类函数开始和结束调用
  ●setUpClass()
  ●tearDownClass()
  测试套件 (test suit)
  测试套件是多个测试用例的集合
  结合上面内容,看个相对完整的测试套件 :
  #!/usr/bin/env python# coding=utf-8
  import sysimport unittestfrom calculator import Calculator
  class CalculatorTest(unittest.TestCase):
      def setUp(self):
          self.cal = Calculator(8, 4)
          #assert 1 == 2, "test if setUp error"
      def tearDown(self):
          self.cal = None
      def test_add(self):
          '''
          des : test add
          '''
          result = self.cal.add()
          self.assertEqual(result, 12)
          # if use python builtin assert , it will stop run
          # assert result == 12, "add error"
      def test_sub(self):
          result = self.cal.sub()
          self.assertEqual(result, 4)
      def test_mul(self):
          result = self.cal.mul()
          self.assertEqual(result, 32)
      def test_div(self):
          result = self.cal.div()
          self.assertEqual(result, 2)
      @unittest.skip('just skip')
      def test_div_1(self):
          result = self.cal.div()
          self.assertEqual(result, 3)
      @unittest.skipIf(Calculator.__version__ < 5, 'not support this library')
      def test_div_2(self):
          result = self.cal.div()
          self.assertEqual(result, 2)
      @unittest.skipIf(Calculator.__version__ < 2, 'not support this library')
      def test_div_3(self):
          result = self.cal.div()
          self.assertEqual(result, 2)
      @unittest.skipUnless(sys.platform.startswith('win'),  'windows')
      def test_div_4(self):
          result = self.cal.div()
          self.assertEqual(result, 2)
      @unittest.skipUnless(sys.platform.startswith('linux'),  'linux')
      def test_div_5(self):
          result = self.cal.div()
          self.assertEqual(result, 2)
  def suite1():
      ## 执行测试用例构建条件
      suite = unittest.TestSuite()
      suite.addTest(CalculatorTest("test_add"))
      suite.addTest(CalculatorTest("test_sub"))
      return suite
  def suite2():
      # 指定前缀构建测试套件
      suite = unittest.makeSuite(CalculatorTest, 'test')
      return suite
  if __name__ == "__main__":
      ## 运行用例方法1
      #my_print("Test suit : run some testcase")
      #runner = unittest.TextTestRunner()
      #runner.run(suite1())
      ## 运行用例方法2
      #my_print("Test suit : run all testcase")
      #runner.run(suite2())
      ## 运行用例方法3
      # python ./testCalculator.py -v
      # python ./testCalculator.py -v testCalculator.test_add
      my_print("Run all testcase dircetly")
      unittest.main()
  关注的点:
  ●测试用例指定条件,在不符合条件的情况下跳过不执行(见最后几个带修饰器的用例,对于跨平台什么实用)
  ●所有测试用例执行顺序与其在类中的定义顺序没有关系,不能依靠这个先后关系;并且不同用例之间最好不要相互依赖。
  如上,运行所有测试用例,或者指定某个测试用例运行。
  $ python testCalculator.py -v
  test_add (__main__.CalculatorTest) ... ok
  test_div (__main__.CalculatorTest) ... ok
  test_div_1 (__main__.CalculatorTest) ... skipped 'just skip'
  test_div_2 (__main__.CalculatorTest) ... skipped 'not support this library'
  test_div_3 (__main__.CalculatorTest) ... ok
  test_div_4 (__main__.CalculatorTest) ... skipped 'windows'
  test_div_5 (__main__.CalculatorTest) ... ok
  test_mul (__main__.CalculatorTest) ... ok
  test_sub (__main__.CalculatorTest) ... ok
  ----------------------------------------------------------------------
  Ran 9 tests in 0.001s
  OK (skipped=3)

  $ python testCalculator.py -v CalculatorTest.test_add
  test_add (__main__.CalculatorTest) ... ok
  ----------------------------------------------------------------------
  Ran 1 test in 0.001s
  OK
  多个测试套件运行
  $ python -m unittest discover ### 结合一下参数, python自动匹配运行所有符合条件的测试用例
  -v, --verbose
  Verbose output
   
  -s, --start-directory directory
  Directory to start discovery (. default)
   
  -p, --pattern pattern
  Pattern to match test files (test*.py default)
   
  -t, --top-level-directory directory
  Top level directory of project (defaults to start directory)
  或者直接新建一个文件,import 所有测试类
  使用 mock
  前面提到,单元测试测试最小单元,但是有时候测试,遇到测试模块需要依赖于其他模块,一个是不确定依赖的模块是否有问题;另一个是依赖模块容易构造获取,没有实现。比如网络消息分析包,需要接收网络字节之类的。
  这种情况下,就需要对依赖的模块进行 mock,虚拟一个依赖模块供我们测试。
  如上面的例子,假如原理的 add() 还没有实现
  def  add(self):
      pass
  测试用例类似如下, 对其进行mock
  from unittest import mock
      def test_add(self):
          result = self.cal.add()
          self.assertNotEqual(result, 12)
          self.cal.add = mock.Mock(return_value=12)
          result = self.cal.add()
          self.assertEqual(result, 12)
  大概的意思,详细根据实际需求了解使用
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号