Подтвердить что ты не робот

Как я издеваться над файловой системой в модульных тестах python

Есть ли стандартный способ (без установки сторонних библиотек) для работы с файловой системой кросс-платформы, издевательской в ​​python? Если мне нужно пойти со сторонней библиотекой, какая библиотека является стандартом?

4b9b3361

Ответ 1

Стандартная mocking framework в Python 3.3+ unittest.mock; вы можете использовать это для файловой системы или чего-то еще.

Вы также можете просто рулонировать его, насмехаясь через патч обезьяны:

Тривиальный пример:

import os.path
os.path.isfile = lambda path: path == '/path/to/testfile'

Немного больше (непроверено):

import classtobetested                                                                                                                                                                                      
import unittest                                                                                                                                                                                             

import contextlib                                                                                                                                                                                           

@contextlib.contextmanager                                                                                                                                                                                  
def monkey_patch(module, fn_name, patch):                                                                                                                                                                   
    unpatch = getattr(module, fn_name)                                                                                                                                                                      
    setattr(module, fn_name)                                                                                                                                                                                
    try:                                                                                                                                                                                                    
        yield                                                                                                                                                                                               
    finally:                                                                                                                                                                                                
        setattr(module, fn_name, unpatch)                                                                                                                                                                   


class TestTheClassToBeTested(unittest.TestCase):                                                                                                                                                              
    def test_with_fs_mocks(self):                                                                                                                                                                           
        with monkey_patch(classtobetested.os.path,                                                                                                                                                          
                          'isfile',                                                                                                                                                                         
                          lambda path: path == '/path/to/file'):                                                                                                                                            
            self.assertTrue(classtobetested.testable())                 

В этом примере фактические mocks тривиальны, но вы можете поддержать их тем, что имеет состояние, которое может представлять действия файловой системы, такие как сохранение и удаление. Да, это все немного уродливо, так как оно влечет за собой репликацию/имитацию базовой файловой системы в коде.

Обратите внимание, что вы не можете монтировать патчи python для обезьян. Это сказано...

Для более ранних версий, если это вообще возможно, используйте стороннюю библиотеку, я бы пошел с Майклом Фоудом удивительным Mock, что теперь unittest.mock в стандартной библиотеке с 3.3+ благодаря PEP 0417, и вы можете получить его на PyPI для Python 2.5+. И он может высмеивать встроенные функции!

Ответ 2

pyfakefs (домашняя страница) делает что вы хотите - поддельная файловая система; его сторонняя сторона, хотя эта сторона - Google. См. Как заменить ссылки на файл для тестируемого модуля для обсуждения использования.

Для насмешек unittest.mock является стандартной библиотекой для Python 3.3+ (PEP 0417); для более ранней версии см. PyPI: mock (для Python 2.5+) (Домашняя страница).

Терминология в тестировании и издевательстве противоречива; используя Test Double терминологию Джерарда Мезароса, вы запрашиваете "подделку": что-то, что ведет себя как файловая система (вы можете создавать, открывать, и удалять файлы), но не является фактической файловой системой (в данном случае ее встроенной памятью), поэтому вам не нужно иметь тестовые файлы или временный каталог.

В классическом издевательстве вы бы вместо этого выкрикивали системные вызовы (в Python, mock out функции в модуле os, такие как os.rm и os.listdir), но это гораздо более странно.

Ответ 3

pytest набирает много тяги, и он может все это использовать, используя tmpdir и monkeypatching (насмешливо).

Вы можете использовать аргумент функции tmpdir, который предоставит временный каталог, уникальный для вызова вызова, созданный в базовом временном каталоге (который по умолчанию создается как подкаталоги временного каталога системы).

import os
def test_create_file(tmpdir):
    p = tmpdir.mkdir("sub").join("hello.txt")
    p.write("content")
    assert p.read() == "content"
    assert len(tmpdir.listdir()) == 1

Аргумент функции monkeypatch помогает вам безопасно устанавливать/удалять атрибут, элемент словаря или переменную среды или изменять sys.path для импорта.

import os
def test_some_interaction(monkeypatch):
    monkeypatch.setattr(os, "getcwd", lambda: "/")

Вы также можете передать ему функцию вместо использования лямбда.

import os.path
def getssh(): # pseudo application code
    return os.path.join(os.path.expanduser("~admin"), '.ssh')

def test_mytest(monkeypatch):
    def mockreturn(path):
        return '/abc'
    monkeypatch.setattr(os.path, 'expanduser', mockreturn)
    x = getssh()
    assert x == '/abc/.ssh'

# You can still use lambda when passing arguments, e.g.
# monkeypatch.setattr(os.path, 'expanduser', lambda x: '/abc')

Если ваше приложение имеет много взаимодействия с файловой системой, тогда может быть проще использовать что-то вроде pyfakefs, как издевательски станет утомительным и повторяющимся.

Ответ 4

Pyfakefs позволяет создавать поддельную файловую систему, записывать и читать файлы, устанавливать разрешения и многое другое, даже не касаясь вашего реального диска.

Он также содержит практический пример и учебное пособие, показывающее, как применить его к unittest и doctest.

Ответ 5

Faking или Mocking?

Лично я нахожу, что в файловой системе много реберных дел (например, открытие файла с правильными разрешениями, string-vs-binary, режим чтения/записи и т.д.), и с помощью точной поддельной файловой системы можно найти много ошибок, которых вы, возможно, не найдете, насмехаясь. В этом случае я бы посмотрел memoryfs модуль pyfilesystem (он имеет различные конкретные реализации одного и того же интерфейса, поэтому вы можете их заменить в своем коде).

Издевательская (и без патчей обезьян!):

Тем не менее, если вы действительно хотите насмехаться, вы можете сделать это легко с помощью библиотеки Python unittest.mock:

# production code file; note the default parameter
def make_hello_world(path, open_func=open):
    with open_func(path, 'w+') as f:
        f.write('hello, world!')

# test code file
def test_make_hello_world():
    file_mock = unittest.mock.Mock(write=unittest.mock.Mock())
    open_mock = unittest.mock.Mock(return_value=file_mock)

    # When `make_hello_world()` is called
    make_hello_world('/hello/world.txt', open_func=open_mock)

    # Then expect the file was opened and written-to properly
    open_mock.assert_called_once_with('/hello/world.txt', 'w+')
    file_mock.write.assert_called_once_with('hello, world!')

В приведенном выше примере демонстрируется только создание и запись в файлы путем издевательства над методом open(), но вы можете так же легко высмеять любой метод.