解释和实现程序需求是编程过程的一个重要部分,随着你学习本课程和其他课程,您将越来越熟悉这一过程。然而,即使经过实践,你也会发现提升策略高度来减少需求应用中的不确定性是很重要的。
在程序员团队共同编写软件的大型编程项目中,编写测试的实践是非常普遍的。事实上,有些编程范例,如Agile、Cleanroom、Spiral和Waterfall,已经将测试直接集成到他们的方法中。在这门课中,我们不会学习范例和模型,但是重要的是要认识到测试在整个软件开发行业中是多么普遍。正如您可以想象的那样,这种普遍性的存在是因为针对您的代码编写测试的过程是有效的。测试可以显著减少开发时间、代码复杂度和bug跟踪。
那么我们怎么开始呢?
我们要做的第一件事是创建一个假设,关于我们的程序和其中的函数,预期的行为。例如,一个测试可能是输入一个值并观察输出。输出是否符合项目要求中设定的期望?如果是这样,那么这个测试就可以说“pass”。“如果没有,那么显然测试失败了,但我们如何确定原因呢?”当使用作业1的有效性检查器时,这对很多人来说是一个困惑的来源,因为有效性检查器只能在程序没有返回预期输出时报告失败。一个更好的测试应该在函数级而不是程序级检查输入和输出。
然而,测试单个功能确实需要一些计划。如果程序中的函数执行许多操作,则很难配置一个可信赖的测试。因此,在程序设计之前或与程序设计同时考虑测试的好处之一是,函数的职责将不可避免地限制在一个可管理的范围内。
那么我们如何在函数级别上进行测试呢?
让我们以一个管理电子邮件列表的程序为例。我们假设这个程序的要求之一是支持从邮件列表中删除某些电子邮件地址。
我们将从改编Alex Thornton为ICS 32编写的这个小函数开始。' remove_from '函数接受两个参数:一个列表和一个值。它返回相同的列表,除非没有传递的值,如果它存在的话。
def remove_from(the_list: list, value) -> list:
new_list = []
for element in the_list:
if element != value:
new_list.append(element)
return new_list
至少,这是我们所希望的。我们应该先对它进行测试,而不是假设它是这样的,然后继续构建我们的电子邮件列表管理程序:
>>> emails = ["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
>>> remove_from(emails, "user2@example.com")
["user1@example.com", "user3@example.com", "user4@example.com"]
>>> emails
["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
>>> remove_from(emails, "user4@example.com")
["user1@example.com", "user2@example.com", "user3@example.com"]
>>> emails
["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
到目前为止一切都好。我们从一个测试列表开始,然后向函数传递一些测试条件,看看它是否返回我们期望的结果。在这里,每次调用remove_from后,我们都会收到一个新列表,其中丢失了传递给它的值。但我们测试过所有可能的情况了吗?如果传递的值不存在,会发生什么?
>>> emails = ["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
>>> remove_from(emails, "user5@example.com")
["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
这是我们希望发生的吗?如果我们的程序需要确认删除确实发生了呢?好吧,我们可以将电子邮件与结果进行比较,或者我们可以将这一职责交给remove_from函数:
def remove_from(the_list: list, value) -> list:
new_list = []
for element in the_list:
if element != value:
new_list.append(element)
else:
raise ValueError('value not found in list')
return new_list
因此,现在我们有了一个函数,如果传递的值不存在,它将引发一个异常,这为我们提供了一个惯例(异常),我们可以使用它来处理这个新条件。
>>> emails = ["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"]
>>> remove_from(emails, "user5@example.com")
Traceback (most recent call last):
File "maillist.py", line 39, in <module>
remove_from(emails, "user5@example.com")
File "maillist.py", line 33, in remove_from
raise ValueError('value not found in list')
ValueError: value not found in list
关于for...else的理解,请见:
https://blog.csdn.net/Ying_new/article/details/104572433https://blog.csdn.net/Ying_new/article/details/104572433
怀疑:但是这样不是必定会报错吗?可能写错了吧!我觉得改成下面这样比较好。
def remove_from(the_list: list, value) -> list: new_list = [] for element in the_list: if element != value: new_list.append(element) if (the_list == new_list): # for与elif不能合在一起 raise ValueError('value not found in list') else: print(new_list) return new_list
你还能想到其他的吗?如果电子邮件列表有多个相同地址的电子邮件,会发生什么?目前,值的所有实例都将从新列表中删除,但如果我们只想删除重复的值呢?通过编写针对函数的测试,我们暴露了可能不会考虑的条件。
现在,每次我们想要测试一个程序时都必须编写这些测试用例,这很快就会变得非常笨拙。幸运的是,Python为我们提供了一种自动化大部分工作的方法!
assert语句是在程序中插入调试测试的一种方便快捷方式。这里我们测试了一个普通的case,或者代表remove_from函数的典型行为的case:
assert remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user4@example.com") == ["user1@example.com", "user2@example.com", "user3@example.com"]
在这里,我们测试一个错误情况,或者生成我们意想不到的结果的情况:
try:
remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user5@example.com")
assert False, "ValueError should have been thrown"
except ValueError:
pass
如果两个assertions都pass,那么在运行程序时,我们不应该期望从shell中看到任何东西。然而,如果assertion失败,调试器将通知你失败:
>>> assert remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user4@example.com") == ["user1@example.com", "user2@example.com", "user3@example.com"]
Traceback (most recent call last):
File "maillist.py", line 38, in <module>
assert remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user4@example.com") == ["user1@example.com", "user2@example.com", "user2@example.com"]
AssertionError
关于assert用法,请见:
Python assert断言函数及用法 (biancheng.net)http://c.biancheng.net/view/2219.html关于pass的用法,请见:
Python pass语句及其作用 (biancheng.net)http://c.biancheng.net/view/2216.html关于上面笔记中的说法比较绕,我用程序试了一下:
(1)若用这种形式的assert:try: remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user5@example.com") assert False, "ValueError should have been thrown" except ValueError: pass
如果为:
def remove_from(the_list: list, value) -> list: new_list = [] for element in the_list: if element != value: new_list.append(element) if (the_list == new_list): raise ValueError('value not found in list') # pass else: print(the_list) return new_list
不会报错
若果为:
def remove_from(the_list: list, value) -> list: new_list = [] for element in the_list: if element != value: new_list.append(element) if (the_list == new_list): # raise ValueError('value not found in list') pass else: print(the_list) return new_list
会抛出AssertionError: ValueError should have been thrown。
(2)若为这种
assert remove_from(["user1@example.com", "user2@example.com", "user3@example.com", "user4@example.com"], "user4@example.com") == ["user1@example.com", "user2@example.com", "user3@example.com"]
可以自己试试加深对assert的理解 ,同样分2种情况。
一旦您有机会使用更大、更复杂的程序,我们将在几周后进入软件开发的更正式的测试驱动方法。但是现在,重点是考虑您编写的代码应该如何运行,然后编写一些assert语句来确认它的运行是否符合您的预期。我想你会发现新的边缘条件将会出现,否则你可能不会考虑到。记住,测试与数量无关!有100个测试的程序并不一定比有10个测试的程序好。您的目标应该是确定值得测试的不同条件,而不是同一测试的变体。