测试函数

给编写的程序写测试,我之前一直都没有这个习惯,有几个原因:
1. 连基本单元测试都不会,还是不写了吧。(没有接触!)
2. 接触后,感觉挺机械重复的,挺浪费时间的,又不想写。(这是错误的思想!)

但经常看一些程序员写的文章说,给自己写的代码编写测试,是一种优良的编程规范和习惯。一想,自己励志成为一名优秀的程序员,是很应该学会写测试代码的,不仅是一种优秀的习惯还能使自己的代码出错误率更低,更加健壮。为了进步,学会如何写测试。

1. unittest.TestCase类中的常用的断言方法

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

断言方法是用来检查你认为该满足的条件是否满足。如果该条件确实满足,你对程序行为的假设就得到了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,Python将引发异常。

2. 编写函数

编写一个函数,它接受两个形参:一个城市名和一个国家名。这个函数返回一个格式为City, Country的字符串,如 Beijing,China。

# 这是city_functions.py文件

def city(city, country):
    """一个城市及其国家"""
    repo_city = city + ', ' + country
    return repo_city.title()

编写另一个程序,导入city_functions.py文件的city()函数。

# 这是city.py文件

from city_functions import city

print('可以在任何时候输入 q 退出程序:')
while True:
    city_name = input("\n输入城市名 -> ")
    if city_name == 'q':
        break
    country_name = input("输入国家名 -> ")
    if country_name == 'q':
        break

    city_country = city(city_name, country_name)
    print('\t这个城市和国家是 ' + city_country)

3. 写测试用例

3.1 先来了解下Python标准库中的模块unittest提供的代码测试工具:

  • 单元测试
  • 测试用例
  • 全覆盖式测试用例

1.单元测试 用于核实函数的某个方面没有问题的。
2.测试用例 是一组单元测试,这些单元测试一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。
3.全覆盖式测试用例 那就是测试用例的终极版本了。它包含一整套单元测试,涵盖了各种可能的函数使用方式。对大型项目而言,要实现全覆盖式测试用例是有一定难度的。(我也不了解)

3.2 开始写测试了

# 这是test_city_function.py文件

from city_functions import city
import unittest

class TestCity(unittest.TestCase):
    """测试city_functions.py"""

    def test_city_country_by_chinese(self):
        """测试'深圳, 中国'能否通过"""
        city_country = city('深圳', '中国')
        self.assertEqual(city_country, '深圳, 中国')

unittest.main()
  1. 首先,在这里要导入模块unittest和要进行测试的函数 city()

  2. 接着,创建一个名为TestCity的类,类名可以随便起,但当这测试的函数过多时,为了能方便快速知道这是测试哪个函数的,起类名时尽量取个与要测试的函数的相关的类名,同时包含Test字样。这个类必须继承unittest.TestCase类,这样Python才知道如何运行你编写的测试。

  3. 在类中,编写一个名为test_city_country_by_chinese的方法,用来测试在输入中文的城市和国家名这个方面,即是一个单元测试。在这个方法中,用 test_开头命名这个方法,是因为当执行 test_city_function.py时,所以以test_开头的方法会自动执行。方法名也应该与要测试的某个方面相关。当然也也不是强迫性的,但这样命名是个良好的编码习惯。

  4. 在这个方法中,我们要调用要测试的函数city(),并给这个函数两个实参‘深圳’和‘中国’。并存储了要测试的函数的返回值给变量city_country

  5. 接下来,使用了unittest最经常使用到的功能:断言方法。用来核实得到的结果是否与期待的结果相一致。如果一致,万事大吉,如果不一致,就会引发异常。

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

现在来执行下这个测试文件test_city_function.py。看看结果:

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

OK

上面有个第一行有个点.表明有一个测试通过了,最后的OK表明该测试用例中所有的单元测试都通过了。

这个输出表明,给定城市名和国家名,函数city()总是能正确地处理。

现在让我们来看看输入英文情况下函数city()能否正确处理即格式。因为在函数city中有个功能是将城市名和国家名的首字母大写返回,而中文的无法这样处理的。

在这个类TestCity中编写另个一个单元测试:

# 这是test_city_function.py文件

def test_city_country_by_english(self):
    """测试‘Shenzhen, China’能否正确显示其格式"""
    city_country = city('shenzhen', 'china')
    self.assertEqual(city_country, 'Shenzhen, China')

其结果:

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

OK

ok表明英文输入情况下函数city()是可以正确处理其格式的。

3.3 不能通过的测试

现在这两个都能正常通过测试,但实际上的代码很有可能存在错误。现在我们假设在程序中还有一个条件是可以加入人口数的。而我们知道现在的程序没有这个功能。我们来写个测试当加入人口数的时候错误是如何提示。

# 这是test_city_function.py文件

def test_city_country_population(self):
    """测试‘Shenzhen, China - population 20000000’能否通过"""
    city_country = city('Shenzhen', 'China', 20000000)
    self.assertEqual(city_country, 'Shenzhen, China - population 20000000')

执行测试文件可以看到这样的错误:

..E
======================================================================
ERROR: test_city_country_population (__main__.TestCity)
测试‘Shenzhen, China, 20000000’能否通过
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_city_function.py", line 21, in test_city_country_population
    city_country = city('Shenzhen', 'China', 20000000)
TypeError: city() takes 2 positional arguments but 3 were given

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=1)

在第一行中,可以看到..E中有两个点和一个字符E,表名前两个单元测试通过了,第三个没有通过。接下来我们看看错误的信息提示:可以知道是在方法test_city_country_population()中出了问题。从TypeError中提示:city() takes 2 positional arguments but 3 were given,知道city()只有两个形参,但我们提供了三个实参。

看到这里,如果不是测试代码本身有问题,一旦测试未通过,不要修改测试,而应该去修改导致测试不能通过的代码。所以我们要去修改city_function.py文件的代码。

但,我们知道了错误的原因,是因为缺少一个参数,但是我们添加参数的时候不能导致前面的两个测试不能通过,所以第三个参数的解决方法是让这个个参数是可选的。

我们修改下代码:

def city(city, country, population=''):
    """一个城市及其国家"""
    if population:
        repo_population = ' - population ' + population
        repo_city = city + ', ' + country
        return repo_city.title() + repo_population
    else:
        repo_city = city + ', ' + country
        return repo_city.title()

运行测试文件:

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

从结果中,我们修改的代码既不影响两个参数输入的测试,又满足添加人口数的测试。

这篇是关于测试函数的博文,而测试类也是很重要的。下次再写。

猜你喜欢

转载自blog.csdn.net/qq_24805141/article/details/77050994