Python编程-第十一章-测试代码

测试函数

# name_funciton.py
def get_formatted_name(first, last):
    """Generates neat names. """
    full_name = f"{first} {last}"
    return full_name.title()
# main.py
from function.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((f"\tNeatly formatted name: {formatted_name}."))

Warning

这种测试方式,许哟啊每次修改get_formatted_name() 函数,调试比较玛法。

单元测试和测试用例

import unittest
from function.name_function import get_formatted_name


class NameTestCase(unittest.TestCase):
    """test name_function.py."""

    def test_first_last_name(self):
        """能够正确处理Janis Joplin这样的姓名?"""
        formatted_name = get_formatted_name('janis', 'jopin')
        """"""
        self.assertEqual(formatted_name, 'Janis Jopin')


if __name__ == '__main__':
    unittest.main()
❶ 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.000s  
  
❺ FAILED (errors=1)

里面包含很多信息,因为测试未通过时,需要让你知道的事情可能有很多。第一行输出只有一个字母E (见❶),指出测试用例中有一个单元测试导致了错误。接下来,我们看到NamesTestCase 中的test_first_last_name() 导致了错误(见❷)。测试用例包含众多单元测试时,知道哪个测试未通过至关重要。在❸处,我们看到了一个标准的traceback,指出函数调用get_formatted_name('janis', 'joplin') 有问题,因为缺少一个必不可少的位置实参。

测试未通过时怎么办

测试未通过时怎么办呢?如果你检查的条件没错,测试通过意味着函数的行为是对
的,而测试未通过意味着编写的新代码有错。因此,测试未通过时,不要修改测

试,而应修复导致测试不能通过的代码:检查刚刚对函数所做的修改,找出导致函
数行为不符合预期的修改。

def get_formatted_name(first, last, middle=''):
    """Generates neat names. """
    if middle:
        full_name = f"{first} {middle} {last}"
    else:
        full_name = f"{first} {last}"
    return full_name.title()

添加新测试

import unittest
from function.name_function import get_formatted_name


class NameTestCase(unittest.TestCase):
    """test name_function.py."""

    def test_first_last_name(self):
        """能够正确处理Janis Joplin这样的姓名?"""
        formatted_name = get_formatted_name('janis', 'jopin')
        """"""
        self.assertEqual(formatted_name, 'Janis Jopin')
    def test_frist_last_middle_name(self):
        """能够正确处理像Wolfgang Amadeus Mozart这样的姓名么?"""
        formatted_name = get_formatted_name('wolfang', 'mozart', 'amadeus')
        self.assertEqual(formatted_name,'Wolfang Amadeus Mozart')



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

练习题

练习11-1:城市和国家 编写一个函数,它接受两个形参:一个城市名和一个国家名。这个函数返回一个格式为 City , Country 的字符串,如Santiago, Chile 。将这个函数存储在一个名为city_functions.py的模块中。创建一个名为test_cities.py的程序,对刚才编写的函数进行测试(别忘了,需要导入模块unittest 和要测试的函数)。编写一个名为test_city_country() 的方法,核实使用类似于'santiago' 和'chile'这样的值来调用前述函数时,得到的字符串是正确的。运行test_cities.py,确认测试test_city_country() 通过了
test_cties.py

import unittest
from function.city_function import get_formatted_country_name


class NameTestCase(unittest.TestCase):
    """test name_function.py."""

    def test_city_country(self):
        """能够正确处理Janis Joplin这样的姓名?"""
        formatted_name = get_formatted_country_name('santiage', 'chile')
        """"""
        self.assertEqual(formatted_name, 'Santiage Chile')

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

city_function.py

def get_formatted_country_name(city, country):
    """Generates neat names. """
    city_name = f"{city} {country}"
    return city_name.title()

练习11-2:人口数量 修改前面的函数,加上第三个必不可少的形参population ,并返回一个格式为 City , Country – population xxx的字符串,如Santiago, Chile – population 5000000 。运行test_cities.py,确认测试test_city_country() 未通过。修改上述函数,将形参population 设置为可选的。再次运行test_cities.py,确认测试test_city_country() 又通过了。再编写一个名为test_city_country_population() 的测试,核实可以使用类似于'santiago' 、'chile' 和'population=5000000' 这样的值来调用这个函数。再次运行test_cities.py,确认测试test_city_country_population() 通过了
test_cties.py

import unittest
from function.city_function import get_formatted_country_name


class NameTestCase(unittest.TestCase):
    """test name_function.py."""

    def test_city_country(self):
        """能够正确处理Janis Joplin这样的姓名?"""
        formatted_name = get_formatted_country_name('santiage', 'chile')
        """"""
        self.assertEqual(formatted_name, 'Santiage, Chile.')
    def test_city_population(self):
        formatted_name = get_formatted_country_name('santiage', 'chile', '400')
        self.assertEqual(formatted_name, 'Santiage,  Chile - Population 400.')

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

city_function.py

def get_formatted_country_name(city, country, population=''):
    """Generates neat names. """
    if population:
        city_name = f"{city},  {country} - population {population}."
    else:
        city_name = f"{city}, {country}."
    return city_name.title()

测试类

在本章前半部分,你编写了针对单个函数的测试,下面来编写针对类的测试。很多程序中都会用到类,因此证明你的类能够正确工作大有裨益。如果针对类的测试通过了,你就能确信对类所做的改进没有意外地破坏其原有的行为。

断言方法

表 unittest 模块中的断言方法。

方法 用途
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 中

创建一个测试类

创建一个调查问卷的类

class_survey.py

class AnonymousSurvey:
    def __init__(self, question):
        """Store a question, And prepare for storing the answer. """
        self.question = question
        self.responses = []

    def show_question(self):
        """Display the questionnaire. """
        print(self.question)

    def store_response(self, new_response):
        """Store a single copy of the questionnaire. """
        self.responses.append(new_response)

    def show_results(self):
        """Displays all answers collected. """
        print("Survey results:")
        for response in self.responses:
            print(f"- {response}")

创建一个程序,调用类。进行测试

language_survey.py

from _class.class_survey import  AnonymousSurvey
# define a problem, and create a survey.
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
# Display the question and store the answer.
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)
# Display the survey results.
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()

测试AnonymousServey 类

只测试一个方法的类

import unittest
from _class.class_survey import AnonymousSurvey


class TestAnonymousSurvey(unittest.TestCase):
    """A test for the Anonymous Survey class. """

    def test_store_single_response(self):
        """Test individual answers are stored properly. """
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)


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

测试三个答案的类

import unittest
from _class.class_survey import AnonymousSurvey


class TestAnonymousSurvey(unittest.TestCase):
    """A test for the Anonymous Survey class. """

    def test_store_single_response(self):
        """Test individual answers are stored properly. """
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        my_survey.store_response('English')
        self.assertIn('English', my_survey.responses)
        
    def test_store_three_response(self):
        """Test individual answers are stored properly. """
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        responses = ['English', 'Spanish', 'Mandarin']
        for response in responses:
            my_survey.store_response(response)
        for response in responses:
            self.assertIn(response, my_survey.responses)


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

方法setUp()

import unittest
from _class.class_survey import AnonymousSurvey


class TestAnonymousSurvey(unittest.TestCase):
    """A test for the Anonymous Survey class. """
    def setUp(self):
        """Create a survey object and a set of answers, Test method for use. """
        question = "What language did you first learn to speak?"
        self.my_survey = AnonymousSurvey(question)
        self.responses = ['English', 'Spanish', 'Mandarin']

    def test_store_single_response(self):
        """Test individual answers are stored properly. """
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.responses)

    def test_store_three_response(self):
        """Test individual answers are stored properly. """
        for response in self.responses:
            self.my_survey.store_response(response)
        for response in self.responses:
            self.assertIn(response, self.my_survey.responses)


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

  1. 创建一个调查对象,以及创建一个答案列表。
  2. 存储这两个变量名都包含前缀self ,所以可以在任何地方使用

练习题

练习11-3:雇员 编写一个名为Employee 的类,其方法__init__() 接受名、姓和年薪,并将它们存储在属性中。编写一个名为give_raise() 的方法,它默认将年薪增加5000美元,但也能够接受其他的年薪增加量。为Employee 编写一个测试用例,其中包含两个测试方法:test_give_default_raise() 和test_give_custom_raise() 。使用方法setUp() ,以免在每个测试方法中都新建雇员实例。运行这个测试用例,确认两个测试都通过了

Employee.py

from _class.Employee import Employee
import unittest


class TestEmloyee(unittest.TestCase):
    def setUp(self) -> None:
        self.my_employee = Employee('chen', 'cheng', '3000')

    def test_give_default_raise(self):
        self.my_employee.give_raise()

    def test_give_custom_raise(self):
        self.my_employee.give_raise('500')

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

test_empployee.py

class Employee:
    def __init__(self, first_name, last_name, salary):
        self.first_name = first_name
        self.last_name = last_name
        self.salary = salary

    def give_raise(self, salary ='5000'):
        if salary != 5000:
            self.salary += salary
        else:
            self.salary += 5000