美文网首页
阅读《Python编程从入门到实践》Day14

阅读《Python编程从入门到实践》Day14

作者: 晓梅_aa3b | 来源:发表于2018-03-29 15:31 被阅读29次

    第十一章

    编写函数或类时,还可为其编写测试。通过测试,可确定代码面对各种输入都能够按要求的那样工作。

    1、测试函数

    下面是要用于测试的函数,它接受名和姓并返回整洁的姓名,存储在文件name_function.py中:

    def get_formatted_name(first, last):
        full_name = first + ' ' + last
        return full_name.title()
    

    函数将名和姓合并成姓名,在名和姓之间加上一个空格,并将它们的首字母都大写,再返回结果。为核实函数是否像期望的那样工作,下面编写一个使用这个函数的程序:

    from name_function import get_formatted_name
    
    print("Enter 'q' at any time to quit.")
    while True:
        first = input("\nPlease give me a first name: ")
        if first == 'q':
            break
        last = input("Please give me a last name: ")
        if last == 'q':
            break
    
        formatted_name = get_formatted_name(first, last)
        print("\tNeatly formatted name: " + formatted_name + ".")
    

    测试如下:

    Enter 'q' at any time to quit.
    
    Please give me a first name: janis
    Please give me a last name: joplin
        Neatly formatted name: Janis Joplin.
    
    Please give me a first name: bob
    Please give me a last name: dylan
        Neatly formatted name: Bob Dylan.
    
    Please give me a first name: q
    

    从测试的结果可以知道,合并得到的姓名是正确的。如果现在需要添加处理中间名的功能,就需要在保证不破坏原来功能的基础上,添加新的功能,然后再进行测试。这样显得就太繁琐了,不过Python您提供了一种自动测试函数输出的高效方式,可对相应的函数进行自动测试。

    (1)单元测试和测试用例

    Python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例是一组单元测试,这些单元测试一起核实函数在各种情形下的的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。

    (2)可通过的测试

    要为函数编写测试用例,可先导入模块unittest以及要测试的函数,再创建一个unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。下面是只包含一个方法的测试用例:

    import unittest
    from name_function import get_formatted_name
    
    class NamesTestCase(unittest.TestCase):
        def test_first_last_name(self):
            formatted_name = get_formatted_name('janis', 'joplin')
            self.assertEqual(formatted_name, 'Janis Joplin')
    
    unittest.main()
    # 输出:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    首先,需要导入模块unittest和要测试的函数。然后创建一个类,用于包含一系列针对被测试函数的单元测试,这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试。
    这个类只包含了一个方法,用于核实姓名能否被正确地格式化。在运行上述文件时,所有以test_大头的方法都将自动运行。
    这里使用了unittest类中最有用的功能之一:一个断言方法。断言方法用来核实得到的结果是否与期望的结果一致。self.assertEqual()方法就是将第一个参数和第二个参数进行比较。
    在输出的结果中,第一行的句点表明有一个测试通过了。接下来的一行指出Python运行了一个测试,消耗的时间不到0.001秒。最后的OK表明该测试用例中的所有单元测试都通过了。

    (3)不能通过的测试

    我们故意只在函数中添加可以处理中间名的形参middle,而没有修改测试用例中的实参。运行结果:

    E
    ======================================================================
    ERROR: test_first_last_name (__main__.NamesTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "test_name_function.py", line 8, in test_first_last_name
        formatted_name = get_formatted_name('janis', 'joplin')
    TypeError: get_formatted_name() missing 1 required positional argument: 'last'
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.002s
    
    FAILED (errors=1)
    

    由于测试没有通过,返回了很多信息。第一行输出只有一个字母E,它指出测试用例中有一个单元测试导致了错误。然后可以看到类中的函数导致了错误。当测试用例包含很多单元测试时,准确知道那个测试没通过至关重要。在往下,我们看到一个标准的traceback,它准确指出函数调用中出现了问题,因为它缺少了一个必不可少的位置实参。最后显示运行了一个单元测试,并指出整个测试用例都没有通过。

    (4)测试未通过时怎么办

    测试未通过说明你编写的新代码有错,此时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。
    对于上述未能通过的测试,我们知道是新增的中间名参数导致的,所以可以让中间名变为可选的,即添加默认值,然后再适当地添加if判断语句,就可以让测试通过了。

    (5)添加新测试

    下面再编写一个测试,用于测试包含中间名的姓名。

    import unittest
    from name_function import get_formatted_name
    
    class NamesTestCase(unittest.TestCase):
        def test_first_last_name(self):
            formatted_name = get_formatted_name('janis', 'joplin')
            self.assertEqual(formatted_name, 'Janis Joplin')
    
        def test_first_last_middle_name(self):
            formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
            self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
    
    unittest.main()
    # 输出:
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    这个新添加方法的方法名必须以test_开头,这样它才会在我们运行整个测试文件时自动运行。在TestCase类中使用很长的方法名是可以的;这些方法名必须具有描述性的,这才能让你明白测试未通过时的输出;这些方法由Python自动调用,你根本不用编写调用它们的代码。

    2、测试类

    (1)unittest Module中常用的6个断言方法
    方法 用途
    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中

    Python在unittest.TestCase类中提供了很多断言方法。如果你认为应该满足的条件实际上并不满足,Python将引发异常。

    (2)一个要测试的类

    类的测试与函数的测死相似,但也存在一些不同之处。下面一个帮助管理匿名调查的类:

    class AnonymousSurvey():
        def __init__(self, question):
            self.question = question
            self.responses = []
        
        def show_question(self):
            print(self.question)
        
        def store_response(self, new_response):
            self.responses.append(new_response)
        
        def show_results(self):
            print("Survey results:")
            for response in self.responses:
                print('- ' + response)
    

    要创建这个类的实例,只需提供一个问题即可。下面编写一个使用它的程序:

    from survey import AnonymousSurvey
    
    question = "What language did you first learn to speak?"
    my_survey = AnonymousSurvey(question)
    my_survey.show_question()
    print("Enter 'q' at any time to quit.\n")
    while True:
        response = input("Language: ")
        if response == 'q':
            break
        my_survey.store_response(response)
    
    print("\nThank you to everyone who participated in the survey!")
    my_survey.show_results()
    # 输出:
    What language did you first learn to speak?
    Enter 'q' at any time to quit.
    
    Language: English
    Language: Spanish
    Language: English
    Language: Mandarin
    Language: q
    
    Thank you to everyone who participated in the survey!
    Survey results:
    - English
    - Spanish
    - English
    - Mandarin
    
    (3)测试AnonymousSurvey类

    下面编写一个测试:如果用户面对调查问题时只提供一个答案,这个答案也能被妥善地存储。

    import unittest
    from survey import AnonymousSurvey
    
    class TestAnonymousSurvey(unittest.TestCase):
        def test_store_single_response(self):
            question = "What language did you first learn to speak?"
            my_survey = AnonymousSurvey(question)
            my_survey.store_response('English')
            self.assertIn('English', my_survey.responses)
    
    unittest.main()
    # 测试输出:
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    这里的测试与测试函数时类似,这里的第一个方法验证调查问题的单个答案被存储后,会包含在调查结果列表中。要测试类的行为,需要创建其实例。由输出知道,测试顺利通过了。
    下面来核实用户提供三个答案时,它们也将被妥善地存储。

        def test_store_three_responses(self):
            question = "What language did you first learn to speak?"
            my_survey = AnonymousSurvey(question)
            responses = ['English', 'Spanish', 'Mandarih']
            for response in responses:
                my_survey.store_response(response)
                
            for response in responses:
                self.assertIn(response, my_survey.responses)
    # 测试输出:
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    

    在测试类中添加上述的方法,有结果可知,测试也顺利通过了。但是这些测试有些重复的地方。下面使用unittest的另一项功能来提高它们的效率。

    (4)方法setUp()

    在前面的示例中,我们在每个测试的方法中都创建了一个AnonymousSurvey实例,并在每个方法中都创建了答案。unittest.TestCase类中包含了方法setUp(),Python将先运行它,再运行各个以test_开头的方法。这样,在你编写的每个测试方法中都可使用在方法setUp()中创建的对象。
    下面使用setUp()来创建一个调查对象和一组答案,供两个测试方法使用:

    import unittest
    from survey import AnonymousSurvey
    
    class TestAnonymousSurvey(unittest.TestCase):
        
        def setUp(self):
            question = "What language did you first learn to speak?"
            self.my_survey = AnonymousSurvey(question)
            self.responses = ['English', 'Spanish', 'Mandarih']
        
        def test_store_single_response(self):
            self.my_survey.store_response(self.responses[0])
            self.assertIn(self.responses[0], self.my_survey.responses)
    
        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.responses)
    
    unittest.main()
    

    方法setUp()做了两件事情:创建一个调查对象;创建一个答案列表。存储这两样东西的变量名包含前缀self(即存储在属性中),因此可在这个类的任何地方使用。
    测试自己编写的类时,方法setUp()让测试方法编写起来更容易:可在setUp()方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。

    注意:运行测试用例时,每完成一个单元测试,Python都打印一个字符:测试通过时打印一个句点;测试引发错误时打印一个E;测试导致断言失败时打印一个F。这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。

    相关文章

      网友评论

          本文标题:阅读《Python编程从入门到实践》Day14

          本文链接:https://www.haomeiwen.com/subject/pxhmcftx.html