简体   繁体   English

Python 单元测试一个复杂的方法

[英]Python unit-testing a complicated method

I am looking for any advice on how to unit test the following method.我正在寻找有关如何对以下方法进行单元测试的任何建议。

def delete_old_files(self):
        """Deletes all recordings older then a predefined time period.

        Checks the mtime of files in _store_dir and if it is older then the predefined time period
        delete them.
        """
        now = time.time()
        self.logger.info(
            f"Delete: Looking for files older then {self.config.delete.older_then}"
            f" days in: {self._store_dir}."
        )
        try:
            for root, dirs, files in os.walk(self._store_dir):
                for file in files:
                    try:
                        if file == ".gitignore":
                            continue
                        file = os.path.join(root, file)
                        # check if the mtime is older then x days (x * hours * minutes * seconds)
                        if os.stat(file).st_mtime < now - (
                            self.config.delete.older_then * 24 * 60 * 60
                        ):
                            if os.path.isfile(file):
                                os.remove(file)
                                self.logger.info(f"Delete:\t{file}")
                    except FileNotFoundError:
                        self.logger.warning(
                            f'Could not find "{file}". Continue without deleting this file.'
                        )
                    except PermissionError:
                        self.logger.warning(
                            f'Permission denied when trying to delete "{file}".'
                            " Continue without deleting this file."
                        )
        # TODO: Do we want to handle the exception here or higher up?
        # Should we treat this as unacceptable or not?
        except FileNotFoundError:
            self.logger.warning(
                f"Directory not found! Could not find {self._store_dir}"
                " Continue without deleting old files."
            )
        except PermissionError:
            self.logger.warning(
                f"Permission denied! Could not access {self._store_dir}"
                " Continue without deleting old files."
            )

A little bit of background information: I am writing a audio recorder which saves all files into self._store_dir and the method I want to test is kind of an optional garbage collector.一些背景信息:我正在编写一个录音机,它将所有文件保存到self._store_dir中,我想测试的方法是一种可选的垃圾收集器。 Files older then self.config.delete.older_then should be deleted.应该删除早于self.config.delete.older_then的文件。 Any advice on how to unit test such a method or refactor it so it becomes easier to test is very welcome.非常欢迎任何有关如何对此类方法进行单元测试或重构以使其更易于测试的建议。

Ideas I have are:我的想法是:

  • mock the filesystem and provided all the different cases.模拟文件系统并提供所有不同的情况。 But that is the opposite of what a unit-test should do, right?但这与单元测试应该做的相反,对吧?
  • split up the method into smaller method and test them on their own.将方法拆分为更小的方法并自行测试。

I kind of figured it out myself.我有点想通了自己。 I refactored my code so it does not lie about the information it needs to do what it does.我重构了我的代码,这样它就不会谎报它需要做的事情的信息。 Then I added a return value I can test for if something breaks or everything goes well.然后我添加了一个返回值,我可以测试是否出现问题或一切正常。 I found this great blog about unit testing in python which helped me a lot.我在 python 中找到了这个关于单元测试的很棒的博客,它对我帮助很大。

For completeness, here is my refactored code:为了完整起见,这是我重构的代码:

    def delete_old_files(self, store_dir: str, older_then: int) -> bool:
        """Deletes all recordings older then a predefined time period.

        Checks the mtime of files in store_dir and if it is older then the predefined time period,
        delete them.

        Args:
            store_dir: The file path as a string to the directory to check for files to delete.
            older_then: The minimum age of the files in days which will be deleted.

        Returns:
            True, if the method could delete all files older than the minimum age.
            This is also True if no files were found and therefore no files were deleted.

            False, if the method encounters an FileNotFoundError or PermissionError
            for at least one file.
        """
        now = time.time()
        self.logger.info(
            f"Delete: Looking for files older then {older_then}" f" days in: {store_dir}."
        )
        no_deletion_problems = True
        for root, dirs, files in os.walk(store_dir):
            for file in files:
                try:
                    if file == ".gitignore":
                        continue
                    file = os.path.join(root, file)
                    # check if the mtime is older then x days (x * hours * minutes * seconds)
                    if os.stat(file).st_mtime < now - (older_then * 24 * 60 * 60):
                        os.remove(file)
                        self.logger.info(f"Delete:\t{file}")
                except FileNotFoundError:
                    self.logger.warning(
                        f'Could not find "{file}". Continue without deleting this file.'
                    )
                    no_deletion_problems = False
                except PermissionError:
                    self.logger.warning(
                        f'Permission denied when trying to delete "{file}".'
                        " Continue without deleting this file."
                    )
                    no_deletion_problems = False
        return no_deletion_problems

And if it helps anyone I will post my tests as well:如果它对任何人有帮助,我也会发布我的测试:

    def test_delete_old_files(self):
        """Test to delete old files."""
        raw_dict = {"name": "log_test", "file": None, "level": "INFO"}
        log_config = recorder.Config.Logger(raw_dict)
        log_handler = recorder.LogHandler(log_config)
        logger = log_handler.get_logger()
        self.recorder.logger = logger

        with mock.patch("os.walk") as mock_oserror:
            mock_oserror.return_value = [("root", "dirs", ["file1, file2", ".gitignore"])]

            self.assertFalse(self.recorder.delete_old_files("dir_to_check", 1))

    class stat_obj:
        """Dummy stat class."""

        st_mtime = 0

    @patch.multiple(
        "os",
        walk=MagicMock(return_value=[("root", "dirs", ["file1, file2", ".gitignore"])]),
        stat=MagicMock(return_value=stat_obj()),
        remove=MagicMock(return_value=None),
    )
    def test_delete_old_files_mocking_os_stat(self, **mocks):
        """Test to delte old files when mocking os.stat()."""
        raw_dict = {"name": "log_test", "file": None, "level": "INFO"}
        log_config = recorder.Config.Logger(raw_dict)
        log_handler = recorder.LogHandler(log_config)
        logger = log_handler.get_logger()
        self.recorder.logger = logger

        self.assertTrue(self.recorder.delete_old_files("dir_to_check", 1))

    @patch.multiple(
        "os",
        walk=MagicMock(return_value=[("root", "dirs", ["file1, file2", ".gitignore"])]),
        stat=MagicMock(return_value=stat_obj()),
        remove=MagicMock(side_effect=PermissionError),
    )
    def test_delete_old_files_mocking_permission_error(self, **mocks):
        """Test to delte old files when mocking for a permission error."""
        raw_dict = {"name": "log_test", "file": None, "level": "INFO"}
        log_config = recorder.Config.Logger(raw_dict)
        log_handler = recorder.LogHandler(log_config)
        logger = log_handler.get_logger()
        self.recorder.logger = logger

        self.assertFalse(self.recorder.delete_old_files("dir_to_check", 1))

I went with mocking and moved some unnecessary checks further up in my code.我使用 mocking 并在我的代码中进一步移动了一些不必要的检查。 All in all it's not perfect but good enough for me right now.总而言之,它并不完美,但现在对我来说已经足够好了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM