Рассмотрим функцию foo
, предоставляемую пакетом X
в Python. Я хочу протестировать различные функциональные возможности X.foo
, а затем использовать X.foo
в своем коде. Чтобы убедиться, что я использую X.foo
так, как он был протестирован, я думаю инкапсулировать его в функцию, а затем вызвать эту функцию вместо него. Однако я не уверен, что это излишество, и что мне следует просто вызывать X.foo
напрямую.
Например, для тестирования X.foo
он помещается внутрь функции main
, которая, в свою очередь, находится в файле example.py
:
# example.py
import X
def main(arg1,arg2,arg3):
X.foo(arg1,arg2,arg3,...) # X.foo may have other arguments other than arg1,arg2, and arg3
Далее, для юнит-тестирования X.foo
в том же каталоге, что и test_example.py
, создается новый файл example.py
, содержащий различные функции, которые тестируют различные функциональные возможности X.foo
:
# test_example.py
from example import main
def test_foo1():
main(...)
# some assertions here
def test_foo2():
main(...)
# some assertions here
def test_foo3():
main(...)
# some assertions here
Затем я запустил бы каждый из test_foo1,test_foo2,
и test_foo3
, чтобы убедиться, что X.foo
работает так, как ожидалось. Однако, чтобы использовать X.foo
в моем коде, я не уверен, вызывать ли X.foo
напрямую, или вызывать main
, как я делал в тестах выше. То есть, в другом файле я могу написать либо
import X
X.foo(...)
или
from example import main
main(...)
Мне кажется, что последний вариант "безопаснее", потому что я ограничиваю использование X.foo
тем, как он был протестирован, и не более того.
Инкапсуляция библиотечных функций имеет смысл, когда ваш «собственный» интерфейс main
предлагает упрощенный доступ к библиотеке или, по крайней мере, предоставляет более точное или более читаемое имя (при условии, что в реальном коде функции вызываются не просто main
и X.foo
). Это может быть особенно полезно в ситуациях, когда
документация библиотеки имеет низкое качество, или
некоторые способы использования библиотеки работают, а другие обнаруживают ошибки (но вы можете жить с этими ошибками, потому что вы нашли некоторые обходные пути), или
функции, которые вы собираетесь вызывать, значительно сложнее, чем то, что вам нужно
Об этом нельзя судить только по бессмысленным именам, таким как main
или foo
. Итак, давайте приведем лучший пример: скажем, это библиотека графического рисования, а X.foo
— универсальная функция рисования (ее настоящее имя может быть gfx.Draw
) с множеством перегрузок и параметров для рисования точек, линий, кривых, прямоугольников и других фигур. Теперь все, что вам нужно, это функция рисования линий. Итак, вы решили написать функцию DrawLine
, которая вызывает gfx.Draw
с параметрами для рисования линии между двумя конечными точками.
Чтобы убедиться, что ваша инкапсуляция работает правильно, вы решили написать несколько простых тестов для DrawLine
, которые показывают, что она работает, как описано в документации. Теперь должно быть ясно, что ваш реальный код также должен вызывать DrawLine
, что будет менее подвержено ошибкам и более выразительно, чем просто вызов gfx.Draw
.
Ситуация изменится, если вы просто напишете функцию с именем MyDraw
с теми же параметрами, что и gfx.Draw
. Должно быть очевидно, что вызов MyDraw
не принесет особой пользы в этой ситуации, независимо от того, тесты это или нет.
Итак, вкратце - никто не использует капсулу вокруг библиотечных функций из-за того, что написал тесты, он использует капсулу для упрощения доступа (и пишет тесты, чтобы убедиться, что это работает как задумано). Обратите внимание, что это также может быть верно, когда вы вообще не пишете модульные тесты (но где-то в процессе вам нужно протестировать капсулу вручную или автоматически, или как часть какого-либо интеграционного теста).