Python 测试代码

Python 测试代码

  • 通过测试,可确定代码面对各种输入都能够按要求的那样工作
  • 序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。

1、测试函数:

  • 要学习测试,得有要测试的代码

    • 首先再当前目录下创建一个name_fun.py的文件,内容如下:

      def get_test_name(first, last):
          """测试名字的代码"""
          full_name = first + ' ' + last
          return full_name.title()
      
    • 再同级目录中再创建.py执行文件:

      from name_fun import get_test_name  # 先导入测试名字代码的模块
      
      while True:
          first = raw_input("请输入您的姓:")
          if first == 'q':
              break
          last = raw_input('请输入您的名:')
          if last == 'q':
              break
      
          full_name = get_test_name(first, last)  # 调用该模块
          print(full_name)
      
      
      • 这里python 2使用raw_input()
    • 现在假设我们要修改get_test_name() ,使其还能够处理中间名,这样做时,我们要确保不破坏这个函数处理只有名和姓的姓名的方式。为此,我们可以在每次修改get_test_name() 后都进行测试,并再次输入first,last,这太繁琐了。

  • 所幸Python提供了一种自动测试函数输出的高效方式。

1.1、单元测试和测试用例:

  • Python标准库中的模块unittest 提供了代码测试工具
  • 单元测试用于核实函数的某个方面没有问题
  • 测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求
  • 全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。

1.2、可通过的测试:

  • 要为函数编写测试用例,可先导入模块unittest 以及要测试的函数,再创建一个继承unittest.TestCase 的类

  • 下面是一个只包含一个方法的测试用例:

    import unittest
    
    from name_fun import get_test_name
    
    
    class NamesTestCase(unittest.TestCase):
        """测试name_fun.py"""
    
        def test_first_last_name(self):
            """能够正确的处理姓名"""
    
            full_name = get_test_name('fe', 'cow')
            self.assertEqual(full_name, 'Fe Cow')
    
    unittest.main()
    
    • 首先,我们导入了模块unittest和要测试的函数get_test_name()

    • 我们创建了一个名为NamesTestCase 的类(名字最好有意义),用于包含一系列针对get_test_name() 的单元测试,这个类必须继承unittest.TestCase 类,这样Python才知道如何运行你编写的测试。

    • NamesTestCase 只包含一个方法,用于测试get_test_name() 的一个方面

      • 我们将这个方法命名为test_first_last_name() ,因为我们要核实的是只有名和姓的姓名能否被正确地格式化
    • 我们运行.py执行模块时,所有以test 打头的方法都将自动运行,这个方法中,我们调用了要测试的函数,并存储了要测试的返回值

    • 我们使用实参'fe', 'cow'调用get_test_name(),并将结果存储到变量full_name中

    • unittest 类最有用的功能之一。断言 方法

      • 断言方法用来核实得到的结果是否与期望的结果一致
      • 为检查是否确实如此,我们调用unittest的方法assertEqual()
    • 代码行self.assertEqual(full_name, ‘Fe Cow’) 的意思是说:将full_name的值同字符串'Fe Cow' 进行比较,如果它们相等,就大吉大利,今晚吃鸡,如果不相等就gg

    • 运行unittest.main() 让Python运行这个文件中的测试

      .
      ----------------------------------------------------------------------
      Ran 1 test in 0.000s
      
      OK
      
      
      • 指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK 表明该测试用例中的所有单元测试都通过了。

1.3、不能通过的测试:

  • 测试未通过时结果是什么样的呢?

    def get_test_name(first, middle, last):
        """测试名字的代码"""
        full_name = first + ' ' + last
        return full_name.title()
    
    • 我们修改了测试名字的函数,加了个中间名,再次运行代码:

      E
      ======================================================================
      ERROR: test_first_last_name (__main__.NamesTestCase)
      ----------------------------------------------------------------------
      Traceback (most recent call last):
        File "C:/Users/lh9/PycharmProjects/request/my_test01.py", line 1060, in test_first_last_name
          full_name = get_test_name('fe', 'cow')
      TypeError: get_test_name() takes exactly 3 arguments (2 given)
      
      ----------------------------------------------------------------------
      Ran 1 test in 0.001s
      
      FAILED (errors=1)
      
      • 其中包含的信息很多,因为测试未通过时,需要让你知道的事情可能有很多
      • 第1行输出只有一个字母E,它指出测试用例中有一个单元测试导致了错误。接下来,我们
        看到NamesTestCase 中的test_first_last_name() 导致了错误
      • 测试用例包含众多单元测试时,知道哪个测试未通过至关重要
      • 我们看到了一个标准的traceback,它指出函数调用get_test_name('fe', 'cow') 有问题,因为它缺少一个必不可少的位置实参。
      • 最后,还看到了一条消息,它指出整个测试用例都未通过,因为运行该测试用例时发生了一个错误,FAILED (errors=1)

1.4、测试未通过时怎么办?

  • 测试未通过时,不要修改测试,而应修复导致测试不能通过的代码

    • 检查刚对函数所做的修改,找出导致函数行为不符合预期的修改
  • get_test_name()以前只需要两个实参,名和姓,但现在它要求提供名、中间名和姓。新增的中间名参数是必不可少的,这导致get_test_name() 的行为不符合预期。最佳的选择是让中间名变为可选的

  • 要将中间名设置为可选的,可在函数定义中将形参middle 移到形参列表末尾,并将其默认值指定为一个空字符串

    def get_test_name(first, last, middle=''):
        """测试名字的代码"""
        if middle:
            full_name = first + ' ' + middle + ' ' + last
        else:
            full_name = first + ' ' + last
        return full_name.title()
    
    • 再测试用力代码中:

      class NamesTestCase(unittest.TestCase):
      
          def test_first_last_name(self):
              full_name = get_test_name('fe', 'cow')
              self.assertEqual(full_name, 'Fe Cow')
      
      unittest.main()
      
      # 输出结果如下:
      .
      ----------------------------------------------------------------------
      Ran 1 test in 0.000s
      
      OK
      

1.5、添加新测试:

  • 确定get_test_name() 又能正确地处理简单的姓名后,我们再编写一个测试,用于测试包含中间名的姓名

    class NamesTestCase(unittest.TestCase):
        """测试用例"""
    
        def test_first_last_name(self):
            """测试名字跟姓"""
            full_name = get_test_name('fe', 'cow')
            self.assertEqual(full_name, 'Fe Cow')
    
        def test_first_middle_last_name(self):
            """测试名字中间名跟姓"""
            full_name = get_test_name('fe', 'cow', 'big')
            self.assertEqual(full_name, 'Fe Big Cow')
            
    unittest.main()
    # 输出结果如下:
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    # 两个测试用例都通过了
    
    
    • 我们将这个方法命名为test_first_middle_last_name() 。方法名必须以test_打头,这样它才会在我们运行.py时自动运行
    • 在TestCase 类中使用很长的方法名是可以的
      • 些方法的名称必须是描述性的,这才能让你明白测试未通过时的输出

2、测试类:

  • 前半部分,你编写了针对单个函数的测试,下面来编写针对类的测试

2.1、各种断言方法:

  • Python在unittest.TestCase 类中提供了很多断言方法

    • 断言方法检查你认为应该满足的条件是否确实满足
  • 6个常用的断言方法

    • 使用这些方法可核实返回的值等于或不等于预期的值、返回的值为True 或False 、返回的值在列表中或不在列表中

    • 你只能在继承unittest.TestCase 的类中使用这些方法

      方法用途
      assertEqual(a, b)核实a == b
      assertNotEqual(a, b)核实a != b
      assertTrue(x)核实x 为True
      assertFalse(x)核实x 为False
      assertIn(item , list )核实 item 在 list 中
      assertNotIn(item , list )核实 item 不在 list 中

2.2、一个要测试的类:

  • 类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处

    class AnonymousSurvey():
        """收集匿名调查问卷的答案"""
        def __init__(self, question):
            """存储一个问题,并为存储答案做准备"""
            self.question = question
            self.response = []
    
        def show_question(self):
            """显示调查问卷"""
            print(self.question)
            
        def store_response(self, new_response):
            """存储单份调查答卷"""
            self.response.append(new_response)
            
        def show_results(self):
            """显示收集到的所有答卷"""
            for response in self.response:
                print('-' + response)
    
    • 为证明AnonymousSurvey 类能够正确地工作,我们来编写一个使用它的程序

      from name_fun import AnonymousSurvey
      question = '这是一个很严肃的问题!'
      my_survey = AnonymousSurvey(question)
      my_survey.show_question() # 显示问题并存储答案
      while True:
          response = raw_input('请提出你的质疑:')
          if response == 'q':
              break
      
          my_survey.store_response(response)
      
      print('\n显示调查结果')
      my_survey.show_results()
      

2.3、测试AnonymousSurvey 类:

  • 编写一个测试,对AnonymousSurvey 类的行为的一个方面进行验证,使用方法assertIn() 来核实它包含在答案列表中

    from name_fun import AnonymousSurvey
    
    
    class TestAnonymousSurvey(unittest.TestCase):
        """针对AnonymousSurvey类的测试"""
        def test_store_single_response(self):
            """测试单个答案被妥善的存储"""
            question = 'That is a question'
            my_survey = AnonymousSurvey(question)
            my_survey.store_response('English')
    
            self.assertIn('English', my_survey.response)
    
    unittest.main()
    # 输出结果:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    
    • 我们首先导入了模块unittest 以及要测试的类AnonymousSurvey
    • 我们将测试用例命名为TestAnonymousSurvey ,它也继承了unittest.TestCase
    • 第一个测试方法验证调查问题的单个答案被存储后,会包含在调查结果列表中
    • 要测试类的行为,需要创建其实例
    • 然后使用方法store_response() 存储了单个答案English
    • 接下来,我们检查English 是否包含在列表my_survey.responses 中,以核实这个答案是否被妥善地存储了
  • 下面核实用户提供三个答案时:

    class TestAnonymousSurvey(unittest.TestCase):
        """针对AnonymousSurvey类的测试"""
        def test_store_three_response(self):
            """测试单个答案被妥善的存储"""
            question = 'That is a question'
            my_survey = AnonymousSurvey(question)
            responses = ['Spanish', 'Mandarin', 'English']
            for response in responses:
                my_survey.store_response(response)
    
            for response in responses:
                self.assertIn(response, my_survey.response)
    
    unittest.main()
    # 输出结果如下:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    
  • 做法的效果很好,但这些测试有些重复的地方。下面使用unittest 的另一项功能来提高它们的效率

2.4、方法setUp():

  • unittest.TestCase 类包含方法setUp() ,让我们只需创建这些对象一次,并在每个测试方法中使用它们
  • 如果你在TestCase 类中包含了方法setUp() ,Python将先运行它,再运行各个以test_打头的方法
import unittest

from name_fun import AnonymousSurvey


class TestAnonymousSurvey(unittest.TestCase):
    """针对AnonymousSurvey类的测试"""

    def setUp(self):
        """
        创建一个调查对象和一组答案
            供使用的测试方法使用
        """
        question = 'That is a question'
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['Spanish', 'Mandarin', 'English']

    def test_store_single_response(self):
        """测试单个答案被妥善的存储"""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.response)

    def test_store_three_responses(self):
        """测试三个答案会被妥善处理"""
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.response)


unittest.main()
# 输出结果如下:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
  • 方法setUp() 做了两件事情:创建一个调查对象;创建一个答案列表,存储这两样东西的变量名包含前缀self,因此可在这个类的任何地方使用
  • 方法test_store_three_response() 核实self.responses 中的第一个答案—self.responses[0] —被妥善地存储,而方法test_store_three_response() 核实self.responses 中的全部三个答案都被妥善地存储。
  • 测试自己编写的类时,方法setUp() 让测试方法编写起来更容易
    • 可在setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。
    • 相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。
  • 注意 :运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点测试引发错误时打印一个E测试导致断言失败时打印一个F 。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值