Я хочу протестировать развивающееся приложение базы данных SQLite, которое параллельно используется «продуктивно». Фактически я изучаю кучу больших текстовых файлов, импортируя их в базу данных и возиться с ней. Я привык разрабатывать тесты, и я не хочу отказываться от этого расследования. Но запущенные тесты против «производственной» базы данных кажутся несколько странными. Поэтому моя задача - запустить тесты против тестовой базы данных (реальной базы данных SQLite, а не макета), содержащей контролируемый, но значительный объем реальных данных, показывающих все виды изменчивости, которые я встречал во время расследования.«Стыковка где это определено» в python mock?
Чтобы поддержать этот подход, у меня есть центральный модуль myconst.py
, содержащий функцию, возвращающую имя базы данных, которая используется как так:
import myconst
conn = sqlite3.connect(myconst.get_db_path())
Сейчас в unittest
TestCase
с, я думал о насмешливый, как так :
@patch("myconst.get_db_name", return_value="../test.sqlite")
def test_this_and_that(self, mo):
...
где тестовые вызовы функций, которые, во вложенных функций, доступ к базе данных с помощью myconst.get_db_path()
.
Сначала я попытался немного высмеивать for myself, но он имеет тенденцию быть неуклюжим и подверженным ошибкам, поэтому я решил погрузиться в модуль python mock
, как показано выше.
К сожалению, я нашел warningsallover, что я должен «издеваться, где он используется, и не там, где он определен» так:
@patch("mymodule.myconst.get_db_name", return_value="../test.sqlite")
def test_this_and_that(self, mo):
self.assertEqual(mymodule.func_to_be_tested(), 1)
Но mymodule
, скорее всего, не вызывать функции базы данных самостоятельно, но делегировать это другому модулю. Это, в свою очередь, означало бы, что мои модульные тесты должны знать дерево вызовов, в котором база данных фактически находится в доступе - что-то я хочу, действительно хочу избежать, потому что это приведет к ненужной проверке рефакторинга, когда код будет реорганизован.
Итак, я попытался создать минимальный пример, чтобы понять поведение mock
и где он не позволяет мне высмеивать «в источнике». Поскольку здесь много неудобств, я предоставил исходный код also on github для удобства каждого. Смотрите это:
myconst.py
----------
# global definition of the database name
def get_db_name():
return "../production.sqlite"
# this will replace get_db_name()
TEST_VALUE = "../test.sqlite"
def fun():
return TEST_VALUE
inner.py
--------
import myconst
def functio():
return myconst.get_db_name()
print "inner:", functio()
test_inner.py
-------------
from mock import patch
import unittest
import myconst, inner
class Tests(unittest.TestCase):
@patch("inner.myconst.get_db_name", side_effect=myconst.fun)
def test_inner(self, mo):
"""mocking where used"""
self.assertEqual(inner.functio(), myconst.TEST_VALUE)
self.assertTrue(mo.called)
outer.py
--------
import inner
def functio():
return inner.functio()
print "outer:", functio()
test_outer.py
-------------
from mock import patch
import unittest
import myconst, outer
class Tests(unittest.TestCase):
@patch("myconst.get_db_name", side_effect=myconst.fun)
def test_outer(self, mo):
"""mocking where it comes from"""
self.assertEqual(outer.functio(), myconst.TEST_VALUE)
self.assertTrue(mo.called)
unittests.py
------------
"""Deeply mocking a database name..."""
import unittest
print(__doc__)
suite = unittest.TestLoader().discover('.', pattern='test_*.py')
unittest.TextTestRunner(verbosity=2).run(suite)
test_inner.py
работает как источники, связанные выше, скажем, и поэтому я ожидал, что это пройдет. test_outer.py
должен потерпеть неудачу, если я правильно понимаю оговорки. Но все тесты проходят без жалобы! Таким образом, мой макет рисуется все время, даже когда издеваемую функцию вызывается из-под стопки, как в test_outer.py
. Из этого примера я бы пришел к выводу, что мой подход безопасен, но, с другой стороны, предупреждения в разных источниках согласуются, и я не хочу безрассудно рисковать своей «производственной» базой данных, используя концепции, которые я не grok.
Итак, мой вопрос: неправильно ли я понимаю предупреждения или эти предупреждения слишком осторожны?
Я скептически отношусь к вам из-за насмешек. Mocking полезно, когда вам нужно проверить, что объект * получил сообщение * (обычно это какая-то команда изменения состояния). Вы извлекаете данные конфигурации в середине функции. Это значительно улучшает тест на полную функцию, начинайте заканчивать. Для инструмента, который переносит данные из одного механизма хранения в другой, единственными вещами, которые я бы тестировал, являются фактические преобразования данных, которые вы выполняете в коде (т. Е. Не в SQL). И я бы удостоверился, что логика является чисто основанной на вводе/выводе, устраняющей необходимость в издевательствах. – jpmc26
На самом деле, я хотел это сделать. Но преобразования создают «боковое содержимое» для базы данных (например, недавно найденные основные данные) и немедленно обновляют ее до базы данных. Это кажется странным с первого взгляда, но это делает код понятным, поэтому я не хочу реорганизовывать это. Однако во время других рефакторингов и улучшений я хочу иметь регрессию. Поэтому я произвел идею насмешки над конфигурацией. – flaschbier
Что-то, что я нашел, поскольку я разработал больше, так это то, что очень часто очень полезно отделять «что» от «когда». Это означало бы, что вы можете иметь функции/классы, которые делают ваше преобразование, и возвращать данные назад, а затем другие функции для выполнения обновлений. Если вы не хотите менять организацию своего кода, я бы рекомендовал пройти полный интеграционный/функциональный/приемочный тест. Вместо того, чтобы издеваться над конфигурацией, переключитесь на фактический файл конфигурации. Используйте конфигурационный файл, содержащий параметры тестового env, когда вы тестируете. – jpmc26