2015-10-28 5 views
2

Я пишу тесты для объекта, который принимает вход, объединяет некоторые функции, запускает вход через сложенную функцию и возвращает результат.Точность модульного тестирования функционального состава

Вот очень упрощенный набор объектов и функций, зеркала моего дизайна:

type Result = 
| Success of string 

let internal add5 x = x + 5 

let internal mapResult number = 
    Success (number.ToString()) 

type public InteropGuy internal (add, map) = 
    member this.Add5AndMap number = 
     number |> (add >> map) 

type InteropGuyFactory() = 
    member this.CreateInteropGuy() = 
     new InteropGuy(add5, mapResult) 

Класс предназначен для использования в C# Interop, которая объясняет структуру, но эта проблему все еще можно применить к любой функции который содержит параметры функции.

У меня возникли проблемы с поиском элегантного способа сохранить детали реализации внутренних функций от ползучести до условий тестирования при тестировании функции компоновки или, другими словами, изолировать одно звено в цепочке, а не проверять выход, когда вход полностью проложен. Если я просто проверю выход, тогда тесты для каждой функции будут зависеть от правильной работы нисходящих функций, и если один из них в конце цепочки перестанет работать, все тесты потерпят неудачу. Лучшее, что я смог сделать, - это заглушить функцию, чтобы вернуть определенное значение, а затем отключить ее нисходящую функцию, сохранить входной сигнал нисходящей функции, а затем утверждать, что сохраненное значение равно выходу оштукатуренной функции:

[<TestClass>] 
type InteropGuyTests() = 

    [<TestMethod>] 
    member this.``Add5AndMap passes add5 result into map function``() = 

     let add5 _ = 13 

     let tempResult = ref 0 
     let mapResult result = 
      tempResult := result 
      Success "unused result" 

     let guy = new InteropGuy(add5, mapResult) 

     guy.Add5AndMap 8 |> ignore 

     Assert.AreEqual(13, !tempResult) 

Есть ли лучший способ сделать это или это вообще как проверить композицию в изоляции? Дизайнерские комментарии также оценили.

+0

Как вы можете использовать «InteropGuy» из вашего модульного теста, если он «внутренний»? –

+0

@MarkSeemann атрибут InternalsVisibleTo. Я стараюсь, чтобы моя сборка публично раскрывала 'FSharpFunc', так как она будет потребляться C#. – moarboilerplate

+0

Не делайте этого http://blog.ploeh.dk/2015/09/22/unit-testing-internals По существу, делая это, вы говорите, что все внутренние коды также являются «публичными», по крайней мере, когда речь идет об обслуживании. –

ответ

5

Первый вопрос, который мы должны задать, когда сталкиваемся с чем-то вроде этого: Почему мы хотим протестировать этот фрагмент кода?

Когда потенциальный системный тест (SUT) является буквально единственным заявлением, то какое значение добавляет тест?

AFAICT, есть только два способа проверить один вкладыш.

  • триангуляции
  • Дублирование реализации

Оба варианта возможны, но приходит с недостатками, поэтому я думаю, стоит задать вопрос, если такой метод/функция должна быть проверена на всех.

По-прежнему предполагается, что вы хотите проверить функцию, например. для предотвращения регрессий вы можете использовать любой из этих вариантов.

триангуляции

С триангуляции, вы просто выбросить достаточно примеров значений в SUT, чтобы продемонстрировать, что он работает как черный ящик, он должен быть:

open Xunit 
open Swensen.Unquote 

[<Theory>] 
[<InlineData(0, "5")>] 
[<InlineData(1, "6")>] 
[<InlineData(42, "47")>] 
[<InlineData(1337, "1342")>] 
let ``Add5AndMap returns expected result`` (number : int, expected : string) = 
    let actual = InteropGuyFactory().CreateInteropGuy().Add5AndMap number 
    Success expected =! actual 

Преимущество этого примера в том, что рассматривает SUT как черный ящик, но недостатком является то, что он не показывает, что SUT является результатом какой-либо конкретной композиции.

Дублирование реализации

Вы можете использовать основанные на свойствах тестирования, чтобы продемонстрировать (или, по крайней мере, сделать весьма вероятно), что SUT состоит из искомых функций, но она требует дублирования реализации.

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

open FsCheck.Xunit 
open Swensen.Unquote 

[<Property>] 
let ``Add5AndMap returns composed result`` (number : int) = 
    let actual = InteropGuyFactory().CreateInteropGuy().Add5AndMap number 

    let expected = number |> add5 |> mapResult 
    expected =! actual 

ли это когда-нибудь интересно дублировать реализацию в тесте?

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

+0

Предположим, что я должен сделать конструктор общедоступным и ввести функции в качестве параметров конструктора. Это было бы аналогично предоставлению функций в качестве функциональных параметров в F # -онном коде. В моем реальном приложении функции вводятся через конструктор, а параметры метода - это экземпляры .NET и примитивы, которые я закрываю, используя функции, предоставляемые ctor, а затем составляют более крупную оценку. Даже если мой дизайн может быть немного подозрительным, он отражает пример использования функций, которые выполняют другие функции и составляют их, что, по моему мнению, вызывает те же проблемы. Мысли? – moarboilerplate

+0

@moarboilerplate Это звучит достаточно разумно для меня. Если вы правильно поняли, вы можете увидеть что-то подобное в моем курсе по методу «Тестовое развитие с F #» (http://bit.ly/1Hhru7l) Pluralsight. –

+0

Да - я прошел курс :) У меня есть трудности с использованием TDD, когда функции передаются как параметры, а затем объединены и сведены к другой функции, особенно. когда мне нужно отложить оценку и создать закрытие вместо вызова функций. Когда проводка сложнее, чем 'bind a >> map b', я знаю, что тестирование требуется, но я борюсь с возможностью выделения функций, не прибегая к созданию метода заглушки, имеет некоторый побочный эффект. Например, я обнаружил ошибку, в которой я создал функцию из списка функций, используя 'reduce' вместо' reduceBack', и у меня должны были быть тесты для этого. – moarboilerplate