Haskell - утверждать, что функция была вызвана
Можно ли проверить, что функция была вызвана в Haskell HSpec?
Предполагая, что у меня есть две функции foo и bar, которые преобразуют мои данные.
foo :: Stuff -> Stuff
bar :: Stuff -> Stuff
И у меня есть функция, которая применяет либо foo, либо bar к Stuff в зависимости от того, получил ли он 'f' или 'b' в качестве второго аргумента, и возвращает результат примененной функции.
apply :: Stuff -> Char -> Stuff
И в своих тестах я всесторонне протестировал каждую из функций foo и bar, с которыми я не хотел бы тестировать эффект в применении.
Могу ли я проверить, была ли вызвана функция foo или bar? в зависимости от того, какой аргумент передается для применения?
2 ответа
"Я думаю больше о TDD, как на языке ООП. Возможно ли такое в Haskell?"
Лучше спросить: " Нужна ли такая вещь в Хаскеле?";-)
[Я понимаю, что это не тот вопрос, который вы на самом деле задавали. Не стесняйтесь игнорировать этот ответ.]
На языке ОО мы строим объекты, которые общаются с другими объектами, чтобы выполнить свою работу. Чтобы протестировать такой объект, мы создаем кучу поддельных объектов, подключаем реальный объект к поддельным, запускаем методы, которые мы хотим проверить, и утверждаем, что он вызывает искусственные методы с ожидаемыми входными данными и т. Д.
В Хаскеле мы пишем функции. Единственная вещь, которую делает чистая функция, - это взять некоторый ввод и произвести вывод. Таким образом, способ проверить это состоит в том, чтобы просто запустить вещь, подать на нее известные входные данные и проверить, что она возвращает известные выходные данные. Какие другие функции он вызывает в процессе выполнения, не имеет значения; все, что нас волнует, является ли ответ правильным.
В частности, причина, по которой мы обычно не делаем этого в ООП, заключается в том, что вызов некоторого произвольного метода может привести к "реальной работе" - чтению или записи файлов на диске, открытию сетевых подключений, обращению к базам данных и другим серверам и т. Д. Если вы вы просто тестируете одну часть своего кода, вы не хотите, чтобы тест зависел от того, работает ли какая-либо база данных на реальном сетевом сервере; Вы просто хотите протестировать одну небольшую часть своего кода.
С Haskell мы отделяем все, что может повлиять на реальный мир, от вещей, которые просто выполняют преобразование данных. Тестирование вещей, которые просто преобразуют данные в памяти, восхитительно тривиально! (В общем, тестирование частей вашего кода, которые взаимодействуют с реальным миром, все еще сложно. Но, надеюсь, эти части сейчас очень малы.)
Выбор стиля теста Haskell, по-видимому, основан на тестировании свойств. Например, если у вас есть функция для решения уравнения, вы пишете свойство QuickCheck, которое случайным образом генерирует 100 уравнений, и для каждого оно проверяет, действительно ли возвращенное число решает исходное уравнение или нет. Это небольшой набор кода, который автоматически тестирует практически все, что вы когда-либо хотели бы знать! (Но не совсем: вам нужно убедиться, что "случайно" выбранные уравнения действительно проверяют все пути кода, которые вас интересуют.)
(Нет, точно Хаскель, но близко.)
fooP = point . foo
-- testable property: forall s. foo s = runIdenity $ fooP s
barP = point . bar
-- similar testable property
fooAndWitness :: Stuff -> Writer String Stuff
fooAndWitness = fooM >> tell "foo"
-- testable property forall s. (foo s, "foo") = runWriter $ fooAndWitness s
barAndWitness :: Stuff -> Writer String Stuff
barAndWitness = barM >> tell "bar"
-- similar testable property
applyOpen :: Pointed p => (Stuff -> p Stuff) -> (Stuff -> p Stuff) -> Stuff -> Char -> p Stuff
applyOpen onF _ x 'f' = onF x
applyOpen _ onB x 'b' = onB x
applyOpen _ _ x _ = point x
-- semi-testable property (must fix p):
-- forall f b s c. let a = applyOn f b s c in a `elem` [f s, b s, point s]
-- In particular, if we choose p carefully we can be, at least stochastically,
-- sure that either f, b, or neither were called by having p = Const [Int], and running several tests
-- where two random numbers are chosen, `f _ = Const $ [rand1]`, and `b _ = Const $ [rand2]`
-- and verifying we get one of those numbers, which could not have been known when applyOpen was written.
applyM = applyOpen fooM barM
-- similar testable property, although but be loose the "rigged" tests for variable f/b, so
-- some of our correctness may have to follow from the definition.
apply = (runIdentity .) . applyM
-- similar testable property and caveat
Pointed
является классом типа, который подходит между Functor и Applicative и обеспечивает point
с той же семантикой, что и pure
или же return
, Из параметричности следует только закон: (. point) . fmap = (point .)