Как издеваться над os.listdir, чтобы притворяться файлами и каталогами на Python?

#python #unit-testing #pytest #listdir

Вопрос:

У меня есть собственный формат репозитория, и я пытаюсь разработать модуль Python для обработки этих репозиториев. Формат репо выглядит следующим образом:

 /home/X/
       |
         alpha/
       |
         beta/
       |
         project.conf
 

Вот, X это проект. alpha и beta являются папками внутри этого проекта, и они представляют группы в этом проекте. Группа является контейнером в этом репо, и то, что она представляет, на самом деле не имеет отношения к этому вопросу. В репозитории X также есть файлы на корневом уровне; project.conf это пример такого файла.

У меня есть класс под названием Project , который абстрагирует такие проекты, как X . У Project класса есть метод load() , который создает представление в памяти.

 class Project(object):

    def load(self):
        for entry in os.listdir(self.root):
            path = os.path.join(self.root, entry)
            if os.path.isdir(path):
                group = Group(path)
                self.groups.append(group)
                group.load()
            else:
                # process files ...
 

Чтобы модульно протестировать load() метод, издеваясь над файловой системой, у меня есть:

 import unittest
from unittest import mock
import Project

class TestRepo(unittest.TestCase):

    def test_load_project(self):
        project = Project("X")

        with mock.patch('os.listdir') as mocked_listdir:
            mocked_listdir.return_value = ['alpha', 'beta', 'project.conf']
            project.load()
            self.assertEqual(len(project.groups), 2)
 

Это действительно os.listdir успешно издевается. Но я не могу обмануть Python, чтобы он рассматривался mocked_listdir.return_value как состоящий из файлов и каталогов.

Как мне имитировать или os.listdir или os.path.isdir в одном и том же тесте , чтобы тест видел alpha и beta как каталоги, и project.conf как файл?

Комментарии:

1. Что бы вы ни делали os.listdir , это не заставит другие функции думать, что эти имена называют реальные файлы и каталоги. os.listdir просто выплевывает кучу имен.

2. Я согласен с тем, что сказал @user2357112. Есть ли у вас альфа-и бета-версии в качестве каталогов в проекте? Если это так, то вам действительно не нужно насмехаться os.path.isdir .

3. альфа и бета не являются частью проекта. Они являются фиктивными каталогами для объяснения структуры репозитория, обрабатываемого этим проектом.

Ответ №1:

Вы можете использовать pyfakefs, это очень удобная библиотека для тестирования работы с поддельной файловой системой.

если вы используете pytest, у него есть плагин, все функции файловой системы уже исправлены, вам просто нужно использовать его fs приспособление:

 import os

def test_foo(fs):
    fs.CreateFile('/home/x/alpha/1')
    fs.CreateFile('/home/x/beta/2')
    fs.CreateFile('/home/x/p.conf')    
    assert os.listdir('/home/x/')) == ['alpha', 'beta', 'p.conf']
 

или, если вы предпочитаете unittest :

 import os
import unittest

from pyfakefs import fake_filesystem_unittest

class TestRepo(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()

    def test_foo(self):
        os.makedirs('/home/x/alpha')
        os.makedirs('/home/x/beta')
        with open('/home/x/p.conf', 'w') as f:
            f.write('foo')
        self.assertEqual(os.listdir('/home/x/'), ['alpha', 'beta', 'p.conf'])


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

Ответ №2:

Мне удалось добиться желаемого поведения, передав итерируемый side_effect атрибут издевательского isdir объекта.

 import unittest
from unittest import mock
import Project

class TestRepo(unittest.TestCase):

  def test_load_project(self):
      project = Project("X")

      with mock.patch('os.listdir') as mocked_listdir:
        with mock.patch('os.path.isdir') as mocked_isdir:
          mocked_listdir.return_value = ['alpha', 'beta', 'project.conf']
          mocked_isdir.side_effect = [True, True, False]
          project.load()
          self.assertEqual(len(project.groups), 2)
 

Ключ — это mocked_isdir.side_effect = [True, True, False] линия. Логические значения в итерируемом файле должны соответствовать порядку записей каталогов и файлов, передаваемых mocked_listdir.return_value атрибуту.

Ответ №3:

Конечно, это будет зависеть от того, какие именно os функции вы используете, но, похоже mock.patch.multiple os , это именно то, что вам нужно. (Обратите внимание, что вам, возможно, не потребуется исправлять path ; многие из его функций являются только лексическими и не заботятся о фактической файловой системе.)