Предположим, я определяю функцию Haskell f (чистую или как действие), и где-то внутри f я вызываю функцию g. Например:
f = ...
g someParms
...
Как заменить функцию g фиктивной версией для модульного тестирования?
Если бы я работал на Java, g был бы методом класса SomeServiceImpl
, который реализует интерфейс SomeService
. Затем я использовал бы инъекцию зависимостей, чтобы сказать f, что нужно использовать SomeServiceImpl
или MockSomeServiceImpl
. Я не уверен, как это сделать в Хаскеле.
Лучший способ сделать это, представить класс типов SomeService:
class SomeService a where
g :: a -> typeOfSomeParms -> gReturnType
data SomeServiceImpl = SomeServiceImpl
data MockSomeServiceImpl = MockSomeServiceImpl
instance SomeService SomeServiceImpl where
g _ someParms = ... -- real implementation of g
instance SomeService MockSomeServiceImpl where
g _ someParms = ... -- mock implementation of g
Затем переопределите f следующим образом:
f someService ... = ...
g someService someParms
...
Кажется, это сработает, но я просто изучаю Хаскель и задаюсь вопросом, это лучший способ сделать это? В целом, мне нравится идея внедрения зависимостей не только для насмешек, но и для того, чтобы сделать код более настраиваемым и многократно используемым. Вообще, мне нравится идея не быть привязанным к одной реализации для каких-либо сервисов, которые использует фрагмент кода. Будет ли хорошей идеей широко использовать вышеописанный трюк в коде, чтобы получить преимущества внедрения зависимостей?
EDIT:
Давайте сделаем еще один шаг вперед. Предположим, у меня есть ряд функций a, b, c, d, e и f в модуле, которые все должны иметь возможность ссылаться на функции g, h, i и j из другого модуля. И предположим, я хочу иметь возможность макетировать функции g, h, i и j. Я мог бы четко передать 4 функции в качестве параметров a-f, но добавить 4 параметра ко всем функциям довольно сложно. Кроме того, если мне когда-нибудь понадобится изменить реализацию любого из a-f для вызова еще одного метода, мне нужно будет изменить его сигнатуру, что может создать неприятное упражнение по рефакторингу.
Какие-нибудь хитрости для того, чтобы заставить этот тип ситуации работать легко? Например, в Java я мог бы создать объект со всеми его внешними службами. Конструктор будет хранить сервисы в переменных-членах. Затем любой из методов может получить доступ к этим службам через переменные-члены. Таким образом, по мере добавления методов в службы ни одна из сигнатур методов не изменяется. А если нужны новые сервисы, меняется только сигнатура метода конструктора.