Смешивание StateT и MonadUnliftIO - PullRequest
2 голосов
/ 04 августа 2020

Я не уверен в правильном названии для своего вопроса, но, похоже, я несколько загнал себя в угол. Или, по крайней мере, я столкнулся с немного неудобным дизайнерским решением в моем наборе тестов hspe c для моего проекта.

Мой проект должен выполнять некоторые вызовы сторонних API, а в HSpe c я пытаюсь чтобы их можно было подделать с помощью преобразователя, реализованного с использованием StateT, который можно поместить в тестовый стек, чтобы обеспечить возможность «подделывать» эти вызовы API. Я всегда хочу заглушить эти вызовы в модульных тестах, я никогда не хочу попадать в реальный API.

Я «заглушаю» вызовы, определяя все эффекты моей системы с помощью классов типов и предоставляя другой экземпляр при тестировании. Так, например, у меня был бы такой класс:

class FireApiGetRequest m where
  fireApiGetRequest :: GetRequest -> m (Either ApiError GetReponse)

instance FireApiGetRequest Hander where
  fireApiGetRequest = -- real world implementation

У меня есть два стека трансформаторов для моих тестов, в настоящее время один известен как AppTestIO и AppTestPureM.

AppTestIO состоит из ReaderT, который содержит пул соединений с реальной тестовой базой данных и некоторые настройки тестовой конфигурации, а AppTestPureM - это псевдоним типа для StateT FakeDatabaseCalls Gen, почти так же, как я записываю вызовы API с классы типов, вызовы базы данных также разделяются на классы типов и таким же образом «заглушаются». Это работает хорошо, только я хотел бы добавить в оба стека слой-преобразователь, который имитирует сторонний API. На мой взгляд, я должен быть в состоянии определить трансформатор, который может находиться в обоих этих стеках трансформаторов и дать мне возможность имитировать эти вызовы API:

type AppTestPureM = StateT FakedDatabaseCalls (ThirdPartyApiMocksT Gen)

type AppTestIO = ThirdPartyApiMocksT (AppTestT IO)

newtype AppTestT m a = AppTestT
  { unAppTestT :: ReaderT TestEnv m a
  }

На первый взгляд это кажется отличным идея, потому что это означает, что я могу заглушить эти вызовы API независимо от того, попадает ли тест в реальную базу данных, в основном.

Для экземпляров у меня нет проблем с определением экземпляров для вызовов API в тесте:

instance FireApiGetRequest ThirdPartyApiMocksT where
  fireApiGetRequest = -- test implementation, access state and return value based on that

instance FireApiGetRequest m => FireApiGetRequest (StateT s m) where
  fireApiGetRequest = lift . fireApiGetRequest

И я могу определить вспомогательные функции в моем тесте, чтобы заставить экземпляры возвращать фальшивые данные, которые мне нужны:

stubApiGetRequest :: Monad m => (Either ApiError GetResponse) -> ThirdPartyApiMocksT m ()
stubApiGetRequest returnVal = undefined -- store `returnVal` in the state for use in typeclass instance

Моя проблема возникает, когда я действительно начинаю использовать эти экземпляры внутри мои тесты. Для тех функций в моем приложении, которые попадают в базу данных (которые еще не заглушены экземплярами классов типов), они в конечном итоге используют runSqlPool, в котором используется MonadUnliftIO, а MonadUnliftIO фактически запрещает мне смешивать их вместе.

Подход к достижению «состояния» при использовании MonadUnliftIO заключается в использовании вместо него ReaderT + MVar. Обычно у меня нет большой проблемы с этим, моя единственная проблема здесь в том, что мой стек PureM основан на StateT, а также не работает в IO, потому что он используется для выполнения `` эффективных '' вычислений использование поддельных данных для написания теста в чистом виде, без ввода-вывода. Это также означает, что я могу использовать quickcheck для проверки этих функций, если я хочу это сделать. Я знаю, что существует quickcheck-monadic, поэтому наличие этих функций в результате IO не может быть концом света, но я хотел бы сохранить AppTestPureM a -> Gen a. Использование ReaderT + MVar здесь в этой ситуации приведет к тому, что эти тесты будут зависеть от IO. Итак, эти два набора тестов противоречат друг другу.

Полагаю, это иллюстрирует ситуацию, в которой я сейчас нахожусь. Я не знаю, как именно действовать дальше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...