4 min read

Python Learning 7 函数测试

为了保证软件的可靠性和安全性,我们可以为函数或类编写测试代码,这样不仅仅可以简化我们手动测试的工作,也帮助我们未来对软件的维护和再开发更加方便。

比如我们创建一个函数,将用户的姓和名作为输入,然后返回一个格式化之后的全名:

# name_function.py
def get_formatted_name(first, last):
    full_name = f"{first} {last}"
    return full_name.title()

若要测试函数的正确性,我们在names.py中创建一个程序不断地提示用户输入名字,并打印出格式化之后的全名,然后我们可以自己来判断输出的正确性:

from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
    first = input("\nfirst name: ")
    if first == 'q':
        break
    last = input("\nlast name: ")
        break
    formatted_name = get_formatted_name(first, last)
    print(f"\tNeatly formatted name: {formatted_name}.")

测试函数

目的:把上述测试进行自动化

若要创建一个测试用例,我们需要创建一个类,继承unittest.TestCase,然后在类的内部根据程序的需求,加入测试的具体方法:

注意:单元测试需要直接python xxx.py

# name_function_testing.py
import unittest
from name_function import get_formatted_name

class NamesTestCase(unittest.TestCase):
    def test_first_last_name(self):
        formatted_name = get_formatted_name('andrew', 'yang')
        self.assertEqual(formatted_name, 'Andrew Yang')

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

在上面的代码中,我们使用简单的self.assertEqual就能检验代码的正确性,运行代码后,我们会发现终端有以下的信息出现,来告诉我们测试的通过率:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
  • 第一行的句号告诉我们一个测试通过了
  • 下一行告诉我们所有测试花费的时间
  • 最后一行的OK告诉我们这个测试用例通过了。

如果我们把格式化名字的函数进行以下的修改,需要用户输入自己的middle name:

# name_function.py
def get_formatted_name(first, middle, last):
    full_name = f"{first} {middle} {last}"
    return full_name.title()

这个时候如果我们运行测试代码,我们就会得到以下的错误信息:

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代表测试中有一个错误(Error)
  • 下一行告诉我们具体失败的测试函数 test_first_last_name
  • 之后的Trackback告诉我们具体的错误信息,在这个例子中就是漏了一个参数last。
  • 最后两行则是测试数量统计。当我们运行很多的测试用例时,这些具体信息对我们debug非常有帮助。

为了修复上面测试出现的问题,我们可以对格式化函数做以下的修改:

# name_function.py
def get_formatted_name(first, last, middle=''):
    """Generate a neatly formatted full name.""" 
    if middle:
        full_name = f"{first} {middle} {last}" 
    else:
        full_name = f"{first} {last}" return full_name.title()

我们再给测试用例添加额外的测试:

# test_name_function.py
class NamesTestCase(unittest.TestCase):

def test_first_last_name(self):
    formatted_name = get_formatted_name('andrew', 'yang')
    self.assertEqual(formatted_name, 'Andrew Yang')

def test_first_last_middle_name(self):
    get_formatted_name( 'wolfgang', 'mozart', 'amadeus') 
    self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')

if __name__ == '__main__':

unittest.main()

当我们再次运行测试代码的时候,我们则会得到以下信息:

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

OK

测试一个类

除了为单个函数写测试外,我们也可以为类写测试,来帮助类的内部函数运行正确。以下是常用的assert方法,帮助我们进行各种不同的测试:

测试类和测试函数很相似,大部分测试工作也是测试类内部的方法。我们先来创建一个能统计问卷调查的类:

class AnonymousSurvey:
    """Collect anonymous answers to a survey question."""

    def __init__(self, question):
        """Store a question, and prepare to store responses."""
        self.question = question
        self.responses = []

    def show_question(self):
        """Show the survey questions."""
        print(self.question)
    
    def store_response(self, new_response):
        """Store a single response to the survey."""
        self.responses.append(new_response)
    
    def show_results(self):
        """Show all the responses that have been given."""
        print("Survey results:")
        for response in self.responses:
            print(f"- {response}")

这个类可以根据调查的问题,收集答案,并且可以把全部回答打印出来。如果要测试答案,我们可以写以下的测试:

# test_survey.py
import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """Tests for the class AnonymousSurvey"""

    def test_store_single_response(self):
        """Test that a single response is stored properly."""
         
        self.assertIn('Fuzhouness', my_survey.responses)

    def test_store_three_responses(self):
        question = "What language did you first learn to speak?"
        my_survey = AnonymousSurvey(question)
        responses = ['Fuzhouness', "Chinese", "English"]
        for response in responses:
            my_survey.store_response(response)
        self.assertIn('English', my_survey.responses)
    
if __name__ == '__main__':
    unittest.main()

在上面的代码中,我们在每一个测试函数内创建了一个AnonymousSurvey实例,为了简化测试,我们可以在测试类中创建一个setUp()函数,并在其中创建一个公用的AnonymousSurvey实例。

在Python中,当我们在一个TestCase类中创建setUp函数后,再次运行测试函数时,Python会在运行内部方法前优先运行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 = ['Fuzhounese', 'Chinese', 'English']

    def test_store_single_response(self):
        question = "What language did you first learn to speak?"
        slef.my_survey.store_response(self.response(self.response[0])
        self.assertIn('Fuzhouness', my_survey.responses)

    def test_store_responses(self):
        question = "What language did you first learn to speak?"
        for response in responses:
            self.my_survey.store_response(response)
        for response in responses:
            self.assertIn(response, self.my_survey.responses)
    
if __name__ == '__main__':
    unittest.main()

Homework

请大家在先创建一个要被测试的字典类:

# mydict.py
class MyDict(dict):
    def __init__(self, **kw):
        super().__init__(**kw)

    def put(self, key, value):
        self[key] = value

    def get(self, key):
        return self[key]

然后请大家编写自己的测试用例,来测试字典的set和get函数。

from mydict import MyDict
test1 = MyDict()
test1.put('man', 'zhiwei zhou')
test1.get('man')
'zhiwei zhou'

注意点:

最后需要隐藏程序入口,需要加上:

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