如何在这个类中使用Python unittest的setUp和tearDown

7 投票
2 回答
24978 浏览
提问于 2025-04-18 09:55

我在学习测试驱动开发(TDD)时遇到了很大的困难。

我正在写一个类,这个类可以接收一个文件名或者文件描述和大小作为输入,然后从文件中返回指定大小的数据块。

在开始写测试的时候,我只能想到测试一下传入的参数是否为空,以及检查这些参数是否是有效的文件对象。

我写出来的代码如下,我想知道我使用 setUp 和 tearDown 方法是否正确,还是完全错误?我在 setUp() 中创建了一个临时文件和一个类的实例来读取数据,我是不是应该在 tearDown() 中以某种方式删除这个对象?

以下是代码

class Test_FileChunk(unittest.TestCase):
    """
    """
    def setUp(self):
        self.fhandle, self.fname = mkstemp()
        self.fc_obj = FileChunk(filename=self.fname)

    def tearDown(self):
        try:
            os.remove(self.fname)
        except OSError as oserr:
            print(oserr)

    def test_instance_variables(self):
        self.assertIsNotNone(self.fc_obj.filename)
        self.assertIsNone(self.fc_obj.filehandle)
        self.assertEqual(self.fc_obj.chunk_size, 8192)

    def test_check_if_instance_variables_are_valid_file_objects(self):
        handle = open(self.fc_obj.filename
        self.assertEqual(
            hasattr
                (handle, "r"), 'seek'), True,
                    msg="Is not a valid file object")
        handle.close()

我在 StackOverflow 上看了很多关于 TDD 的问题和推荐的教程,但感觉跟着 TDD 教程学习很有趣,但实际上做 TDD 却非常困难。我能想到我想在 ReadChunk 类中做的事情,但就是无法理清思路,先找出测试,然后再写代码。我能想到通过 TDD 检查传入的值是否是有效的文件对象,这在我不使用 TDD 的时候是想不到的,但我不确定我是否正确使用了 unittest。我无法看到整体的情况。有没有人能建议我该怎么做,以及上面的代码片段是否正确?

2 个回答

3

和TemporaryFile()不同,使用mkstemp()的用户需要在完成后自己删除临时文件。

所以在tearDown中删除文件是正确的做法。

我个人会写一些测试函数,比如

def test_filename(self):
    self.assertEqual(self.fc_obj.filename, self.fname)
    self.assertIsTrue(os.path.isfile(self.fc_obj.filename))


def test_handle(self):
    self.assertIsNone(self.fc_obj.filehandle)

def open_file(self):
    # if you implemented an "open" method
    # you can use this method every time you test the opened file
    result = self.fc_obj.open()
    self.assertIsTrue(result, "File open failed")
    self.assertIsNotNone(self.fc_obj.filehandle)

# if you plan to implement a read and a write method you can add the following test
def test_read_write_file(self):
    self.open_file()
    random_data = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(N))
    self.fc_obj.write(random_data)
    self.assertEqual(self.fc_obj.read(), random_data)

等等,并为你计划实现的每一项功能定义一个测试,实施它并运行测试。到目前为止,你的类看起来不错,但正如你可能看到的,你应该尽量让测试具体一些,比如使用

self.assertEqual(self.fc_obj.filename, self.fname)

而不是

self.assertIsNotNone(self.fc_obj.filename)

如果你想对打开的FileChunk对象进行很多测试,你也可以添加第二个unittest.TestCase

class TestOpenFileChunk(unittest.TestCase);
    def setUp(self):
        self.fhandle, self.fname = mkstemp()
        self.fc_obj = FileChunk(filename=self.fname)
        self.fc_obj.open()

    def tearDown(self):
        # if you have this method
        self.fc_object.close()
        # and then
        try:
            os.remove(self.fname)
        except OSError as why:
            print(why)

    def test_read_write(self):
        #...

如果你只想创建一次FileChunk对象,你也可以使用setUpClasstearDownClass方法。

class TestOpenFileChunk(unittest.TestCase);
    @classmethod
    def setUpClass(cls):
        cls.fhandle, cls.fname = mkstemp()
        cls.fc_obj = FileChunk(filename=self.fname)
        cls.fc_obj.open()

    @classmethod
    def tearDownClass(cls):
        # if you have this method
        cls.fc_obj.close()
        # and then
        try:
            os.remove(cls.fname)
        except OSError as why:
            print(why)

在测试方法中像往常一样使用self.fc_obj。

6

关于你的代码

setUp 和 tearDown 是可选的方法,用来帮助你设置和清理测试环境。它们通常用来创建和删除临时文件夹,以存放输出结果,或者在测试时设置一个(模拟的)数据库连接。

这两个方法不应该用来测试任何功能。所以在 setUp 中不应该调用 FileChunk 对象。在每个测试中,你想要测试 FileChunk 方法的某个特定情况,理想情况下这些测试应该是相互独立的。因此,在每个新的测试案例中,你应该创建一个新的 FileChunk 实例。所以在这个例子中,在 test_instance_variablestest_check_if_instance_variables_are_valid_file_objects 中都应该这样做。

关于 TDD

使用纯粹的 TDD 其实是一种思维方式的转变。没有简单的教程可以帮助你,因为关于如何使用 TDD 的书籍已经写了很多。

不过,我可以给你一些指导原则。

  1. 确定你类的公共接口。外部类应该能使用哪些方法?这些方法的输入和输出应该是什么?
  2. 识别方法的不同情况。什么时候应该输出真/假?什么时候应该抛出异常?
  3. 根据你在第二步找到的情况,编写测试用例。
  4. 为 FileChunk 编写一个最基本的虚拟类,这个类基本上什么都不做,但具备所有单元测试所需的功能。这样所有测试都可以运行,尽管它们可能会失败。
  5. 开始改进 FileChunk 类,直到所有测试都通过。

在我看来,TDD 的一个重要方面是,你不能仅仅开始编写测试。你真的需要知道这个类应该是什么样子的。

撰写回答