断言错误:未引发自定义异常

4

我正在尝试为我编写的一个包含try/except块的函数编写单元测试。

如果我的字符串包含少于3个字符,我已经引发了一个自定义异常。我想测试当输入一个长度小于3个字符的字符串时,错误是否会被引发。

运行函数时,如果我输入一个长度小于3个字符的字符串,例如"ha" - 我会得到正确的错误消息:"您的句子中没有足够的字母",这让我相信我已经正确地引发了自定义异常,然而,通过搜索我得知这意味着我在函数中没有引发自定义异常。我只是看不到或理解我哪里出错了。

函数文件:

from collections import Counter


# set custom exception to raise when a word with less than 3 letters is given
class NotEnoughLetters(Exception):
    pass


# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed

        if len(string) < 3:
            raise NotEnoughLetters("There are not enough letters in your sentence")

        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

    except ValueError as err:
        return err
    except NotEnoughLetters as e:
        return e
    else:
        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

测试文件:
import unittest
from unittest import TestCase
from common_letters import three_most_common, NotEnoughLetters


class TestCommonLetters(TestCase):

    # valid input
    def test_good_string(self):
        expected_input = "cheesy puff"
        expected_output = "Here are your most common letters: 1) e  2) f  3) c"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)  # add assertion here

    # invalid input
    def test_bad_string(self):
        expected_input = "cheesy puff"
        false_output = "Here are your most common letters: 1) f  2) f  3) e"
        result = three_most_common(expected_input)
        self.assertNotEqual(false_output, result)

    # edge case 1, having 3 letters
    def test_having_three_letters(self):
        expected_input = "hay"
        expected_output = "Here are your most common letters: 1) h  2) a  3) y"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    def test_having_two_letters(self):
        with self.assertRaises(NotEnoughLetters):
            three_most_common(string="ha")


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

这给我提供了以下输出:
Traceback (most recent call last):
  File "C:\Homework-LivvyW\Homework-LivvyW\Homework_week7-LivvyW\test_common_letters.py", line 31, in test_having_two_letters
    with self.assertRaises(NotEnoughLetters):
AssertionError: NotEnoughLetters not raised

我尝试过查看类似的stackoverflow问题/答案,但遗憾的是仍然不明白我哪里出错了。谢谢!

1
three_most_common并没有引发那个异常,这正是测试失败所告诉你的。 - mkrieger1
2
这个回答解决了你的问题吗?返回(return)和抛出(raise)异常有什么区别? - mkrieger1
2
这个回答解决了你的问题吗?返回和引发异常有什么区别吗? - mkrieger1
1
我觉得你对所学内容有误解。你不应该在抛出异常的地方捕获它们。你在函数中抛出异常是为了告诉调用你的函数的代码,它无法完成其工作。如果你在函数中捕获异常,就会破坏这个目的。 - mkrieger1
1
我觉得你对你所学的东西有误解。你不应该在抛出异常的地方捕获异常。在函数中抛出异常是为了告诉调用该函数的代码,它无法完成其任务。如果你在函数中捕获异常,就会背离这个目标。 - mkrieger1
显示剩余9条评论
2个回答

2
你没有因为这个代码块而引发异常。
except NotEnoughLetters as e:
    return e

不过你是捕获它然后只是返回它。移除这个代码块,它就会被抛出。对于 ValueError 也是一样的。 通过这些改动,你的代码变成了:
from collections import Counter

# set custom exception to raise when a word with less than 3 letters is given
class NotEnoughLetters(Exception):
    pass


# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed

        if len(string) < 3:
            raise NotEnoughLetters("There are not enough letters in your sentence")
            
        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

通过这些修改,你的4个测试都通过了。

好的,那么是最佳实践将代码块删除呢,还是通过其他方式测试异常而不是使用assertRaises?到目前为止,我已经学会了在try/except块中捕获异常。 - Livvy Wilson
好的,那么是最佳实践将该块移除,还是通过其他方式测试异常而不是使用assertRaises呢?到目前为止,我一直被教导在try/except块中捕获异常。 - Livvy Wilson
抛出异常然后捕获并返回它绝对不是一个好的做法。在我看来,你应该只是抛出它(并使用assertRaises进行测试)。或者你可以在if块中返回某种错误代码,并测试函数是否返回了该代码(而不是抛出异常),但我认为抛出异常更好。 - Andrei Evtikheev
好的,谢谢你,@andrei。在复习过程中,我看到了这个视频:https://youtu.be/NIWwJbo-9_8,在最后两分钟,从8:30开始,他提出了一个自定义异常,并且他说你可以稍后捕获它,这是错误的吗?还是因为他使用了一个打印语句所以不同?对不起,这对我来说非常新颖和令人困惑。 - Livvy Wilson
我已经做了一些调查,可能有所进展。为了梳理清楚,我能不能简单地把return e改成raise e?这样它就会像我之前说的那样引发自定义异常。或者由于我在if语句中已经引发了异常,所以在except段落中我不应该再引发异常,而是只捕获其中可能出现的其他异常呢? - Livvy Wilson
显示剩余5条评论

1

不要引发自定义异常

另一种可能性是移除您的自定义异常并直接返回错误消息。因此,您的代码变为:

from collections import Counter

# create a function that will return the 3 most common letters
def three_most_common(string: str):
    string = string.replace(" ", "")
    try:
        if not all(char.isalpha() for char in string):
            #raise ValueError("Your input must contain a string")  # using all because in this instance I haven't accounted for strings and ints mixed
            return "Your input must contain a string"

        if len(string) < 3:
            #raise NotEnoughLetters("There are not enough letters in your sentence")
            return "There are not enough letters in your sentence"

        most_common = Counter(string).most_common(3)
        letters = [key for key, value in most_common]

    #except ValueError as err:
    #    return err
    #except NotEnoughLetters as e:
    #    return e
    #else:
        return f"Here are your most common letters: 1) {letters[0]}  2) {letters[1]}  3) {letters[2]}"

    finally:
        print("The program is running, please wait for your output")

如何更改测试代码

相应的测试代码如下:

import unittest
from unittest import TestCase
#from common_letters import three_most_common, NotEnoughLetters
from common_letters import three_most_common

class TestCommonLetters(TestCase):

    # valid input
    def test_good_string(self):
        expected_input = "cheesy puff"
        expected_output = "Here are your most common letters: 1) e  2) f  3) c"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)  # add assertion here

    # invalid input
    def test_bad_string(self):
        expected_input = "cheesy puff"
        false_output = "Here are your most common letters: 1) f  2) f  3) e"
        result = three_most_common(expected_input)
        self.assertNotEqual(false_output, result)

    # edge case 1, having 3 letters
    def test_having_three_letters(self):
        expected_input = "hay"
        expected_output = "Here are your most common letters: 1) h  2) a  3) y"
        result = three_most_common(expected_input)
        self.assertEqual(expected_output, result)

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    """def test_having_two_letters(self):
        with self.assertRaises(NotEnoughLetters):
            three_most_common(string="ha")"""

    # edge case 2, having 2 letters TODO this didn't work so get clarification tomorrow as to why not
    def test_having_two_letters(self):
        result = three_most_common(string="ha")
        self.assertEqual("There are not enough letters in your sentence", result)

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

在测试代码中,测试用例 test_having_two_letters() 验证了函数 three_most_common() 返回的错误消息(与其他测试用例一样),而不是验证是否引发了异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接