【Lecture 4.3】Test Cases and Exception

18.1. Introduction: Test Cases

Test Cases以一种可以自动检查的方式表达了对程序的需求。具体地说,测试test断言asserts 程序在其执行的某个特定点上的状态。

我们之前曾建议,在实际编写代码之前,最好先写下关于代码应该做什么的注释。在编写程序之前写下一些测试用例是一个更好的主意。

理由:

  1. 在我们编写代码之前,我们已经知道它应该做什么

  2. ,但是这些想法可能有点模糊。写下测试用例迫使我们对应该发生的事情更加具体。

  3. 当我们编写代码时,测试用例可以提供自动的反馈。实际上,在本书的一些activecode窗口和几乎所有的练习中,您都是这种通过测试用例自动反馈的受益者。我们为那些测试用例编写了代码,但是将它隐藏起来,以避免让您感到困惑,同时也避免泄露答案。您可以从编写自己的测试用例中获得一些相同的好处。

  4. 在较大的软件项目中,每次对代码库进行更改时,都可以运行测试用例集。单元测试Unit tests检查小段代码是否正确实现。功能测试 Functional tests检查较大的代码块是否能正确工作。

    运行测试可以帮助识别某个地方的代码更改破坏了其他代码的正确操作的情况。我们不会在这本教科书中看到测试用例的优势,但是请记住,如果您正在参与一个更大的软件,那么对测试用例的介绍将为基本的软件工程实践奠定基础

现在该学习如何为测试用例编写代码了。

Python provides a statement called assert.

  • Following the word assert there will be a python expression.
  • If that expression evaluates to the Boolean False, then the interpreter will raise a runtime error.
  • If the expression evaluates to True, then nothing happens and the execution goes on to the next line of code.

为什么要编写这样一行代码,它永远不能计算出任何对您有用的东西,但有时会导致运行时错误?由于我们上面所描述的关于自动化测试价值的所有原因。你想要一个测试,它会提醒你,某些你认为是正确的条件实际上并不是正确的。立即注意到这个事实要比在程序执行的更晚的时候出现一些意想不到的结果要好得多,因为您将很难跟踪到代码中出现错误的地方。

为什么不 assert 打印出一些表示测试通过的内容呢? 原因是您不希望让通过的自动化测试的结果塞满输出窗口。您只想知道您的某个测试何时失败。在较大的项目中,使用其他测试用例来代替 assert ,例如python unittest 模块。它们提供了一些输出的摘要,总结了已通过和未通过的测试。在本教材中,我们将使用简单的 assert 语句来进行自动化测试。

要编写一个测试,我们必须知道在程序执行的某个特定点上我们期望的值是什么。在本章的其余部分,我们将看到一些关于 assert 语句的示例,以及关于可能要在程序中添加哪种assert 的想法。。

我们已经了解了如何实现函数的测试。您只需调用test。testEqual中,第一个输入是对函数的调用,第二个输入是正确的值,即函数的正确输出。在这段视频中,我们将考虑哪些测试是可以实现的。

18.6. Writing Test Cases for Functions

return value test

测试函数是否返回正确的值是最容易定义的测试用例。您只需检查在特定输入上调用函数的结果是否产生您所期望的特定输出。它得到一个输入,它产生一个输出,它不会改变任何列表或字典的内容。

Or, to give a more concrete example, if you have a function square, you could have a test case assert square(3) == 9. Call this a return value test.

def square(x):
    return x*x

assert square(3) == 9
assert square(-3) == 9
assert square(0) == 0

检测正确不输出

Side Effect Tests

要测试一个函数是否对可变对象做出了正确的更改,您需要不止一行代码。

首先将可变对象设置为某个值,然后运行该函数,然后检查该对象是否具有期望值。

将此称为副作用测试Side Effect Tests,因为您要检查函数调用是否对可变对象产生了正确的副作用。

下面是一个示例,测试update counts函数(它被故意错误实现)。此函数接受一个名为letters的字符串,并更新与 letters字符串中的 每个字符 在counts_diction中的数目。

'''错误版 每次会把字符先置1再增加,初始计数就不起作用了'''
def update_counts(letters, counts_d):
    for c in letters:
        counts_d[c] = 1
        if c in counts_d:
            counts_d[c] = counts_d[c] + 1

counts = {'a': 3, 'b': 2}
update_counts("aaab", counts)
# 3 more occurrences of a, so 6 in all
assert counts['a'] == 6

# 1 more occurrence of b, so 3 in all
assert counts['b'] == 3

为了进行Side Effect Tests,我们首先创建一个字典,其中包含一些字母的初始计数。 然后我们调用函数update_counts()。 然后,我们测试字典中某些字母的正确计数(在编写测试时,这些正确的计数是手动计算的。为了编写测试,我们必须知道正确的答案是什么)。

'''正确版'''
def update_counts(letters, counts_d):
    for c in letters:
        
        if c in counts_d:
            counts_d[c] = counts_d[c] + 1
        else:
            counts_d[c] = 1


counts = {'a': 3, 'b': 2}
update_counts("aaab", counts)
# 3 more occurrences of a, so 6 in all
assert counts['a'] == 6
# 1 more occurrence of b, so 3 in all
assert counts['b'] == 3

为了处理日益复杂的程序,我们将建议一种称为增量开发incremental development的技术。 增量开发的目的是通过一次仅添加和测试少量代码来避免冗长的调试会话。

如果在进行增量开发之前编写单元测试,随着代码通过越来越多的测试,您将能够跟踪进度。 另外,您可以在增量开发的每个阶段编写其他测试。

unit test = (assert statement).

19.1. What is an exception?

An exception is a signal that a condition has occurred that can’t be easily handled using the normal flow-of-control of a Python program. Exceptions are often defined as being “errors” but this is not always the case. All errors in Python are dealt with using exceptions, but not all exceptions are errors.

异常是一个条件已经发生的信号,不能使用Python程序的常规控制流轻松地处理它。异常通常被定义为错误,但情况并非总是如此。Python中的所有错误都是使用异常处理的,但并不是所有的异常都是错误。

19.2 异常处理Flow-of-contro

为了解释异常的作用,让我们回顾一下Python程序中的正常控制流。在正常操作中,Python依次执行语句,一个接一个。对于三种构造,即if语句、循环和函数调用,这种顺序执行被中断:

  • 对于if语句,只执行几个语句块中的一个,然后流控制跳转到if语句之后的第一个语句。
  • 对于循环,当到达循环的末尾时,流控制跳回到循环的开始,然后使用一个测试来确定循环是否需要再次执行。如果循环完成,则控制流跳到循环之后的第一个语句。
  • 对于函数调用,流控制跳转到被调用函数中的第一个语句,执行该函数,流控制跳转到函数调用后的下一个语句。

看到规律了吗?如果控制流不是完全连续的,那么它总是在改变了的控制流之后立即执行第一个语句。这就是为什么我们可以说Python的控制流是连续的。但是,在某些情况下,这种连续的控制流并不能很好地工作。异常为我们提供了一种方法,使我们可以处理非连续点的异常(异常)。

19.2.1. Raising and Catching Errors 引发捕获错误

try / except控制结构提供了一种处理运行时错误并继续执行程序的方法。 到目前为止,任何运行时错误(例如,要求仅包含3个项目的列表中的第8个项目或除以0)都导致程序执行停止。

With try/except, you tell the python interpreter:

  • 尝试执行一个代码块,即Try子句。

    如果整个代码块执行时都没有任何运行时错误,则只需在try / except语句之后继续执行程序的其余部分即可。

  • 如果在执行代码块时发生运行时错误

    • skip the rest of that block of code (but don’t exit the whole program)
    • execute a block of code in the “except” clause
    • then carry on with the rest of the program after the try/except statement
try:
   <try clause code block>
except <ErrorType>:
   <exception handler code block>

语法相当简单。惟一需要技巧的部分是,在except之后,可以选择指定要处理的错误类型。catch是类异常。如果你写了except Exception:所有的运行时错误都会被处理。如果指定了更受限制的错误类别,则只处理那些错误;任何其他类型的错误仍然会导致程序停止运行并打印错误消息。

举个例子:

try:
    items = ['a', 'b']
    third = items[2]
    print("This won't print")
except:
    print("got an error")

print("continuing")

If we catch only IndexEror, and we actually have a divide by zero error, the program does stop executing.

try:
    items = ['a', 'b']
    third = items[2]
    print("This won't print")
except IndexError:
    print("error 1")

print("continuing")

try:
    x = 5
    y = x/0
    print("This won't print, either")
except IndexError:
    print("error 2")


print("continuing again")
---
报错

还有一个有用的特性。异常代码可以访问包含关于错误确切信息的变量。因此,例如,在except子句中,您可以打印出通常作为错误消息打印的信息,但继续执行程序的其余部分。为此,要在正在处理的异常类之后指定一个变量名。exception子句代码可以引用该变量名。

try:
    items = ['a', 'b']
    third = items[2]
    print("This won't print")
except Exception as e:
    print("got an error")
    print(e)

print("continuing")
---
got an error
IndexError: list index out of range on line 3
continuing

19.3. 👩‍💻 When to use try/except

再举一个例子,假设你从一个网站获取了一些嵌套的数据到一个字典d中。当你试图提取特定的元素时,一些可能会丢失:例如 d may not include a particular key。如果您预期某个特定的键可能不存在,您可以编写一个If …否则请检查并处理。

if somekey in d:
    # it's there; extract the data
    extract_data(d)
else:
    skip_this_one(d)

但是,如果您要提取大量不同的数据,则检查所有这些数据可能会很麻烦。 您可以将所有数据提取包装在try / except中。

try:
    extract_data(d)
except:
    skip_this_one(d)

以这种方式捕获所有异常被认为是糟糕的实践。相反,python提供了一种机制来指定您将捕获的某些类型的异常(例如,仅捕获KeyError类型的异常,当字典中缺少一个键时就会发生这种情况。

try:
    extract_data(d)
except KeyError:
    skip_this_one(d)

image-20200617204516622

19.4. Standard Exceptions

image-20200617204538799

image-20200617204602190

image-20200617204625441

所有的异常都是对象。定义对象的类被组织在一个层次结构中,如下所示。这很重要,因为一组相关异常的父类将为自己及其子异常捕获所有异常消息。例如,一个算术错误异常将捕获自身和所有FloatingPointError、OverflowError和ZeroDivisionError异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值