python测试代码

学习目标

  • 如何使用模块unittest中的工具为函数和类编写测试
  • 如何编写继承unittest.TestCase的类,以及如何编写测试方法,以核实函数和类的行为符合预期
  • 如何使用setUp()来根据类高效地创建实例并设置其属性,以便在类的所有测试方法中都可以使用它们

1. 测试函数

要学习测试,得要有测试的代码。下面是简单的函数,它接收名和姓并返回整洁的姓名。

name_function.py

def get_formatted_name(first, last):
    """生成整洁的姓名"""
    full_name = first + ' ' + last
    return full_name.title()

函数get_formatted_name()将名和姓合并成姓名,在名和姓中添加一个空格,并将它们的首字母都大写。编写一个使用这个函数的程序names.py,让用户输入名和姓,并显示全名。

names.py

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:q

从上述输出可知,合并的全名正确。现在假如我们需要修改函数,使其还能够处理中间名。同时不破坏只有名和姓的处理方式。为此,我们需要修改函数后都进行测试:运行程序names.py,输入Janis Joplin这样的姓名,但这样太繁琐了。所幸python提供了一种自动化测试函数的高效方式。倘若我们对函数进行自动化测试,就能始终信心满满,确信函数能够正确的工作。

1.1 单元测试和测试用例

python标准库中的模块unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;测试用例是一组单元测试。全覆盖式测试用例包括一整套单元测试,涵盖了各种可能的函数使用方式。通常,最初只要对代码的重要行为编写测试即可。

1.2 可通过的测试

以下是一个只包含一个方法的测试用例,它检查函数能否正确工作。

test_name_function.py

import unittest
from name_function import get_formatted_name


class NameTestCase(unittest.TestCase):
    """测试name_function.py"""
    def test_first_last_name(self):
        """能够正确处理想Janis Joplin这样的姓名吗"""
        formatted_name = get_formatted_name('janis', 'joplin')
        self.assertEqual(formatted_name, 'Janis Joplin')


unittest.main()

以下是运行结果:

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

OK

1.3 不能通过的测试

测试未通过结果如何?我们来修改函数,使其能够处理中间名,下面是函数的新版本。

name_function.py

def get_formatted_name(first, middle, last):
     """生成整洁的姓名"""
     full_name = first + ' ' + middle +' ' + last
     return full_name.title()

运行测试代码test_name_function.py时,输出如下:

E
======================================================================
ERROR: test_first_last_name (__main__.NameTestCase)
能够正确处理想Janis Joplin这样的姓名吗
----------------------------------------------------------------------
Traceback (most recent call last):
  File "G:/python/test_name_function.py", line 9, 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.001s

FAILED (errors=1)

1.4 测试未通过怎么办

测试未通过怎么办?测试未通过时,不要修改测试,而是修改导致测试不能通过的代码。找出对函数的修改,找出不符合预期的修改。

name_function.py

def get_formatted_name(first, last,middle=''):
    """生成整洁的姓名"""
    if middle:
        full_name = first + ' ' + middle +' ' + last
    else:
        full_name = first + ' ' + last
    return full_name.title()

再次运行test_name_function.py

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

OK

现在测试用例通过了

1.5 添加新测试

在编写一个测试,用于测试包含中间名的姓名。为此,我们在NamesTestCase类中再添加一个方法:

test_name_function.py

import unittest
from name_function import get_formatted_name


class NameTestCase(unittest.TestCase):
    """测试name_function.py"""

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

    def test_first_last_middle_name(self):
        """能够正确处理想Wolfgang Amadeus Mozart这样的姓名吗"""
        formatted_name = get_formatted_name('wolfgang', 'mozart', 'amadeus')
        self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')


unittest.main()

我们再次运行该代码时,测试都通过了:

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

OK

2. 测试类

下面编写针对类的测试

2.1 各种断言方法

表1 unittest Module中的断言方法
方法 用途
assertEqual(a,b) 核实a == b
assertNotEqual(a,b) 核实a != b
assertTrue(x) 核实x为True
assertFalse(x) 核实x为Flase
assertIn(item,list) 核实item在list中
assertNotIn(item,list) 核实item不在list中

2.2 一个要测试的类

下面编写一个匿名调查的类进行测试。

survey.py

class AnonymousSurvey(object):
    """收集匿名调查问卷的答案"""

    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)

我们编写一个来使用它的程序

language_survey.py

from survey import AnonymousSurvey

# 定义一个问题,并创建一个表示调查的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:Chinese
language:English
language:Spanish
language:Mandarin
language:q

Thank you to everyone who participated in the survey!
Survey results:
-Chinese
-English
-Spanish
-Mandarin

2.3 测试AnonymousSurvey类

编写一个测试,用assertIn()核实它是否在答案列表中。先写一个方法,运行后再添加第二个方法。

test_survey.py

import unittest
from survey import AnonymousSurvey

class TestAnonymousSurvey(unittest.TestCase):
    """针对AnonymousSurvey类的测试"""
    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.response)

    def test_store_three_response(self):
        """测试三个答案会被妥善的存储"""
        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)

unittest.main()

运行结果:

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

OK

前述做法效果很好,但这些测试有重复的地方。下面使用unittest的另一项功能提高他们的效率

2.4 方法setUp()

在TestCase类中setUp()是优先运行的方法。使用setUp()来创建一个调查对象和一组答案,供内部两个方法使用。

test_survey.py

import unittest
from survey import AnonymousSurvey

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

    def setUp(self):
        """创建一个调查对象和一组答案,供使用的测试方法使用"""
        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):
        """测试单个答案会被妥善的存储"""
        self.my_survey.store_response(self.responses[0])
        self.assertIn(self.responses[0], self.my_survey.responses)

    def test_store_three_response(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()

运行结果:

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

OK

扩展

运行测试用例时,每完成一个单元测试,python都打印一个字符:

  • 测试通过时,打印一个句点 ‘.’
  • 测试引发错误时,打印一个字母 ‘E’
  • 测试导致断言失败时,打印一个字母 ‘F’

这就是你运行测试用例时,在输出第一行看到句点和字符数量各不相同的原因。

发布了112 篇原创文章 · 获赞 289 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/WeiLanooo/article/details/102678667