Использование mocking для тестирования производных классов в Python

У меня есть код, который выглядит так:

import xmlrpclib

class Base(object):
    def __init__(self, server):
        self.conn = xmlrpclib.ServerProxy(server)

    def foo(self):
        return self.conn.do_something()

class Derived(Base):
    def foo(self):
        if Base.foo():
            return self.conn.do_something_else()

Как использовать имитацию для проверки поведения класса Derived? Я не хочу предполагать, что все, о чем говорит соединение XML-RPC, действительно существует, но я чувствую, что издевательство над модулем xmlrpclib требует слишком больших знаний о реализации класса Base (для которого у меня есть другие тесты).

Или, я думаю, мне даже использовать насмешку, чтобы проверить это? Если нет, как бы вы это протестировали?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
1 858
2

Ответы 2

Вы можете создать поддельный класс ServerProxy и заменить его для тестирования.

Что-то вроде этого:

class FakeServerProxy(object):
    def __init__(self, server):
        self.server = server
    def do_something(self):
        pass
    def do_something_else(self):
        pass

def test_derived():
    xmlrpclib.ServerProxy = FakeServerProxy
    derived = Derived(None)
    derived.foo()

Но тогда мне нужно знать, что класс Base использует xmlrpclib. Я хотел бы попытаться избежать этого, поскольку кажется неправильным, что при изменении реализации базового класса мне придется изменить тесты для производного класса, но не фактическую реализацию производного класса.

Zach Hirsch 05.01.2009 11:49

Derived тесно связан с Base, так что вам придется копаться во внутренностях Base (не прибегая к вуду). Как насчет того, чтобы сделать Base членом, а не предком, и подделать Base?

Ryan Ginstrom 05.01.2009 12:05

Я предполагаю, что это работает в Python из-за утиной печати, но это не очень неудовлетворительное решение для моей стороны C++, поскольку оно не допускает принцип подстановки.

Zach Hirsch 05.01.2009 12:09

@Zach Hirsch: все тестирование подкласса также является тестом суперкласса. Вы не можете относиться к Derived как к не имеющему отношения к Base. Ваши тесты для Derived будут либо подклассом ваших тестов для Base, либо клонами с копированием и вставкой.

S.Lott 05.01.2009 14:04

С помощью некоторого тривиального рефакторинга (вызов do_something_else извлечен) вы можете протестировать логику Derived.foo без необходимости «знать» о XMLRPC.

import xmlrpclib

class Base(object):
    def __init__(self, server):
        self.conn = xmlrpclib.ServerProxy(server)

    def foo(self):
        return self.conn.do_something()

class Derived(Base):
    def foo(self):
        if Base.foo(self):
            return self._bar()
    def _bar(self):
        # moved to its own method so that you
        # you can stub it out to avoid any XMLRPCs
        # actually taking place.
        return self.conn.do_something_else()

import mox

d = Derived('http://deep-thought/unanswered/tagged/life+universe+everything')

m = mox.Mox()
m.StubOutWithMock(Base, 'foo')
m.StubOutWithMock(d, '_bar')
Base.foo(d).AndReturn(True)
d._bar() # Will be called becase Boo.foo returns True.
m.ReplayAll()

d.foo()

m.UnsetStubs()
m.VerifyAll()

Альтернативно или даже предпочтительно вы можете извлечь вызов do_something_else в метод bar на Base. Что имеет смысл, если мы согласны с тем, что Base инкапсулирует все ваши действия XMLRPC.

В примере используется насмешливая библиотека пимокс, но суть ее остается неизменной независимо от вашего стиля насмешки.

Другие вопросы по теме