Как функциональные программисты тестируют функции, которые возвращают единицу?
Как функциональные программисты тестируют функции, которые возвращают единицу?
В моем случае, я считаю, что мне нужно выполнить тестирование интерфейса для этой функции:
let logToFile (filePath:string) (formatf : 'data -> string) data =
use file = new System.IO.StreamWriter(filePath)
file.WriteLine(formatf data)
data
Каков рекомендуемый подход, когда я тестирую функцию с помощью ввода-вывода?
В ООП я считаю, что тест-шпион можно использовать.
Обрабатывает ли шаблон Test Spy функциональное программирование?
Мой клиент выглядит примерно так:
[<Test>]
let ''log purchase''() =
[OneDollarBill] |> select Pepsi
|> logToFile "myFile.txt" (sprintf "%A")
|> should equal ??? // IDK
Мой домен следующий:
module Machine
type Deposit =
| Nickel
| Dime
| Quarter
| OneDollarBill
| FiveDollarBill
type Selection =
| Pepsi
| Coke
| Sprite
| MountainDew
type Attempt = {
Price:decimal
Need:decimal
}
type Transaction = {
Purchased:Selection
Price:decimal
Deposited:Deposit list
}
type RequestResult =
| Granted of Transaction
| Denied of Attempt
(* Functions *)
open System
let insert coin balance = coin::balance
let refund coins = coins
let priceOf = function
| Pepsi
| Coke
| Sprite
| MountainDew -> 1.00m
let valueOf = function
| Nickel -> 0.05m
| Dime -> 0.10m
| Quarter -> 0.25m
| OneDollarBill -> 1.00m
| FiveDollarBill -> 5.00m
let totalValue coins =
(0.00m, coins) ||> List.fold (fun acc coin -> acc + valueOf coin)
let logToFile (filePath:string) (formatf : 'data -> string) data =
let message = formatf data
use file = new System.IO.StreamWriter(filePath)
file.WriteLine(message)
data
let select item deposited =
if totalValue deposited >= priceOf item
then Granted { Purchased=item
Deposited=deposited
Price = priceOf item }
else Denied { Price=priceOf item;
Need=priceOf item - totalValue deposited }
Ответы
Ответ 1
Не вижу это как авторитетный ответ, потому что я не специалист по тестированию, но мой ответ на этот вопрос будет то, что, в идеальном мире, вы не можете и не должны проверить unit
-returning функцию.
В идеале вы бы структурировали свой код так, чтобы он состоял из некоторого ввода-вывода для чтения данных, преобразования, кодирующие всю логику и некоторые IO для сохранения данных:
read
|> someLogic
|> someMoreLogic
|> write
Идея состоит в том, что все ваши важные вещи находятся в someLogic
и someMoreLogic
и что read
и write
абсолютно тривиальны - они читают файл как строку или последовательность строк. Это достаточно тривиально, что вам не нужно его тестировать (теперь вы можете проверить фактическую запись файла, снова прочитав файл, но это, когда вы хотите протестировать файл IO, а не любую написанную вами логику).
Здесь вы должны использовать макет в OO, но поскольку у вас есть хорошая функциональная структура, вы теперь будете писать:
testData
|> someLogic
|> someMoreLogic
|> shouldEqual expectedResult
Теперь, на самом деле, мир не всегда так хорош, и что-то вроде spy
операции оказывается полезным - возможно, потому, что вы взаимодействуете с миром, который не является чисто функциональным.
У Фила Трелфорда есть хороший и очень простой рекордер, который позволяет записывать вызовы функции и проверять, что она была вызвана с ожидаемыми входами - и это то, что я нашел полезным несколько раз (и достаточно просто, чтобы вы на самом деле не нужны рамки).
Ответ 2
Очевидно, вы могли бы использовать макет, как и в императивном коде, если единица кода принимает свои зависимости в качестве параметра.
Но, для другого подхода, я нашел этот разговор действительно интересным Mocks & stubs от Ken Scambler. Насколько я помню, общий аргумент заключался в том, что вам следует избегать использования mocks, сохраняя все функции как можно более чистыми, делая их Data-in-data-out. На самых краях вашей программы у вас будут очень простые функции, которые выполняют важные побочные эффекты. Они настолько просты, что даже не требуют тестирования.
Предоставляемая вами функция достаточно проста, чтобы попасть в эту категорию. Тестирование его с помощью макета или подобного было бы просто связано с тем, что вызываются определенные методы, а не что побочный эффект. Такой тест не имеет смысла и не добавляет никакой ценности над самим кодом, при этом все еще добавляет нагрузку на обслуживание. Лучше проверить часть побочного эффекта с помощью теста интеграции или сквозного теста, который фактически смотрит на файл, который был написан.
Еще один хороший разговор на эту тему - Границы Гэри Бернхардта, в котором обсуждается концепция функционального ядра, императивной оболочки.