单元测试功能组合的准确性

时间:2015-10-28 16:14:12

标签: unit-testing f# function-composition

我正在为一个接受输入的对象编写测试,将一些函数组合在一起,通过组合函数运行输入,并返回结果。

这是一组极为简化的对象和函数,反映了我的设计:

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)

有没有更好的方法来做到这一点,或者这通常是如何单独测试成分?设计评论也很感激。

1 个答案:

答案 0 :(得分:5)

遇到类似问题时我们应该问的第一个问题是:为什么我们要测试这段代码?

当潜在的被测系统(SUT)实际上是单一语句时,测试会添加哪个值?

AFAICT,只有两种测试单线的方法。

  • 三角
  • 重复实施

两者都有可能,但也有缺点,所以我认为值得问一下这种方法/功能是否应该进行测试。

仍然,假设您要测试该功能,例如为防止回归,您可以使用以下任一选项。

三角

通过三角测量,您只需在SUT上输入足够的示例值,以证明它可以作为它应该是的黑盒子:

person = context.Persons.Where(x => x.personId == 555)
                        .Select(xxx => new
                        {
                            personName = xxx.personName,
                            personLastName = xxx.personLastName,
                            PersonImages = xxx.PersonsImages
                        })
                        .ToArray() // data now local
                        .Select(xxx => new Persons
                        {
                            personName = xxx.personName,
                            personLastName = xxx.personLastName,
                            PersonImages = xxx.PersonsImages
                        })
                        .FirstOrDefault();

这个例子的优点是它将SUT视为一个黑盒子,但缺点是它没有证明SUT是任何特定组合的结果。

重复实施

您可以使用基于属性的测试来演示(或者至少很可能)SUT由所需的函数组成,但它需要重复实现。

由于假设函数是引用透明的,因此您只需在合成和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

在测试中复制实现是否有意义?

通常,它不是,但如果测试的目的是为了防止回归,那么作为一种复式簿记可能是值得的。