验证一组对象已正确映射 [英] Verifying a set of objects have been mapped correctly

查看:119
本文介绍了验证一组对象已正确映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一套干净的方法来管理测试专用 F#单元测试中的相等" . 90%的时间,取消引用来表示我的resultexpected之间的关系.

I'm looking for a clean set of ways to manage Test Specific Equality in F# unit tests. 90% of the time, the standard Structural Equality fits the bill and I can leverage it with unquote to express the relation between my result and my expected.

TL; DR我找不到一种干净的方法来为一个或两个属性提供自定义的Equality函数,该值的90%可以很好地由Structured Equal进行服务,F#是否有办法匹配任意记录为其一个或两个字段使用自定义的平等"功能?"

TL;DR "I can't find a clean way to having a custom Equality function for one or two properties in a value which 90% of is well served by Structural Equality, does F# have a way to match an arbitrary record with custom Equality for just one or two of its fields?"

在验证将数据类型与其他数据类型进行1:1映射的函数时,在某些情况下,我经常会从两边提取匹配的元组,并比较输入和输出集.例如,我有一个运算符:-

When verifying a function that performs a 1:1 mapping of a datatype to another, I'll often extract matching tuples from both sides of in some cases and compare the input and output sets. For example, I have an operator:-

let (====) x y = (x |> Set.ofSeq) = (y |> Set.ofSeq)

我可以这样做:

let inputs = ["KeyA",DateTime.Today; "KeyB",DateTime.Today.AddDays(1); "KeyC",DateTime.Today.AddDays(2)]

let trivialFun (a:string,b) = a.ToLower(),b
let expected = inputs |> Seq.map trivialFun

let result = inputs |> MyMagicMapper

test <@ expected ==== actual @>

这使我能够Assert将我的每个输入都映射到一个输出,而没有任何多余的输出.

This enables me to Assert that each of my inputs has been mapped to an output, without any superfluous outputs.

问题是当我想对一个或两个字段进行自定义比较时.

The problem is when I want to have a custom comparison for one or two of the fields.

例如,如果我的DateTime被SUT传递到一个稍微有损的序列化层,则我需要一个测试特定的容错DateTime比较.或者,也许我想对string字段进行不区分大小写的验证

For example, if my DateTime is being passed through a slightly lossy serialization layer by the SUT, I need a test-specific tolerant DateTime comparison. Or maybe I want to do a case-insensitive verification for a string field

通常,我会使用Mark Seemann的 SemanticComparison 库的Likeness<Source,Destination>定义了特定于测试的相等性,但遇到了一些障碍:

Normally, I'd use Mark Seemann's SemanticComparison library's Likeness<Source,Destination> to define a Test Specific equality, but I've run into some roadblocks:

  • 元组:F#在Tuple上隐藏.ItemX,所以我无法通过.With强类型字段名称Expression<T>
  • 定义属性
  • 记录类型:TTBOMK按F#是sealed,没有选择退出,因此SemanticComparison无法代理它们以覆盖Object.Equals
  • tuples: F# hides .ItemX on Tuple so I can't define the property via a .With strongly typed field name Expression<T>
  • record types: TTBOMK these are sealed by F# with no opt-out so SemanticComparison can't proxy them to override Object.Equals

我能想到的就是创建一个通用的类似代理类型可以包含在元组或记录中.

All I can think of is to create a generic Resemblance proxy type that I can include in a tuple or record.

或者也许使用模式匹配(有没有一种方法可以使用它来生成IEqualityComparer,然后使用该方法进行集合比较?)

Or maybe using pattern matching (Is there a way I can use that to generate an IEqualityComparer and then do a set comparison using that?)

我也愿意使用其他功能来验证完整的映射(即不滥用F#Set

I'm also open to using some other function to verify the full mapping (i.e. not abusing F# Set or involving too much third party code. i.e. something to make this pass:

let sut (a:string,b:DateTime) = a.ToLower(),b + TimeSpan.FromTicks(1L)

let inputs = ["KeyA",DateTime.Today; "KeyB",DateTime.Today.AddDays(1.0); "KeyC",DateTime.Today.AddDays(2.0)]

let toResemblance (a,b) = TODO generate Resemblance which will case insensitively compare fst and tolerantly compare snd
let expected = inputs |> List.map toResemblance

let result = inputs |> List.map sut

test <@ expected = result @>

推荐答案

首先,感谢大家的投入.我基本上没有意识到 SemanticComparer<'T> 和它无疑为在该空间中构建通用设施提供了很好的构建基块. 尼科斯的帖子提供了精美的食物在该地区也可以考虑. 填充也存在,我不应该感到惊讶-@ptrelford确实为所有内容提供了一个库( FSharpValue 点也很有价值)!

Firstly, thanks to all for the inputs. I was largely unaware of SemanticComparer<'T> and it definitely provides a good set of building blocks for building generalized facilities in this space. Nikos' post gives excellent food for thought in the area too. I shouldn't have been surprised Fil exists too - @ptrelford really does have a lib for everything (the FSharpValue point is also v valuable)!

幸运的是,我们已经得出了结论.不幸的是,这不是一个包罗万象的工具或技术,而是更好的是可以在给定上下文中根据需要使用的一组技术.

We've thankfully arrived at a conclusion to this. Unfortunately it's not a single all-encompassing tool or technique, but even better, a set of techniques that can be used as necessary in a given context.

首先,确保映射完整的问题实际上是一个正交的问题.问题涉及到====运算符:-

Firstly, the issue of ensuring a mapping is complete is really an orthogonal concern. The question refers to an ==== operator:-

let (====) x y = (x |> Set.ofSeq) = (y |> Set.ofSeq)

这绝对是最好的默认方法-依靠结构平等.需要注意的一件事是,依赖于F#持久集,它要求您的类型支持: comparison(而不是仅支持: equality).

This is definitely the best default approach - lean on Structural Equality. One thing to note is that, being reliant on F# persistent sets, it requires your type to support : comparison (as opposed to just : equality).

在经过验证的结构相等"路径上进行集合比较时,一种有用的技术是将HashSet<T>与自定义IEqualityComparer结合使用:-

When doing set comparisons off the proven Structural Equality path, a useful technique is to use HashSet<T> with a custom IEqualityComparer:-

[<AutoOpen>]
module UnorderedSeqComparisons = 
    let seqSetEquals ec x y = 
        HashSet<_>( x, ec).SetEquals( y)

    let (==|==) x y equals =
        let funEqualityComparer = {
            new IEqualityComparer<_> with
                member this.GetHashCode(obj) = 0 
                member this.Equals(x,y) = 
                    equals x y }
        seqSetEquals funEqualityComparer x y 

==|==equals参数是'a -> 'a -> bool,允许出于比较目的使用模式匹配来解构args.如果输入端或结果端自然已经是元组,则此方法效果很好.示例:

the equals parameter of ==|== is 'a -> 'a -> bool which allows one to use pattern matching to destructure args for the purposes of comparison. This works well if either the input or the result side are naturally already tuples. Example:

sut.Store( inputs)
let results = sut.Read() 

let expecteds = seq { for x in inputs -> x.Name,x.ValidUntil } 

test <@ expecteds ==|== results 
    <| fun (xN,xD) (yN,yD) -> 
        xF=yF 
        && xD |> equalsWithinASecond <| yD @>

虽然SemanticComparer<'T>可以完成工作,但是当您拥有模式匹配的功能时,根本就不值得为元组打扰.例如使用SemanticComparer<'T>,以上测试可以表示为:

While SemanticComparer<'T> can do a job, it's simply not worth bothering for tuples with when you have the power of pattern matching. e.g. Using SemanticComparer<'T>, the above test can be expressed as:

test <@ expecteds ==~== results 
    <| [ funNamedMemberComparer "Item2" equalsWithinASecond ] @>

使用助手:

[<AutoOpen>]
module MemberComparerHelpers = 
    let funNamedMemberComparer<'T> name equals = {                
        new IMemberComparer with 
            member this.IsSatisfiedBy(request: PropertyInfo) = 
                request.PropertyType = typedefof<'T> 
                && request.Name = name
            member this.IsSatisfiedBy(request: FieldInfo) = 
                request.FieldType = typedefof<'T> 
                && request.Name = name
            member this.GetHashCode(obj) = 0
            member this.Equals(x, y) = 
                equals (x :?> 'T) (y :?> 'T) }
    let valueObjectMemberComparer() = { 
        new IMemberComparer with 
            member this.IsSatisfiedBy(request: PropertyInfo) = true
            member this.IsSatisfiedBy(request: FieldInfo) = true
            member this.GetHashCode(obj) = hash obj
            member this.Equals(x, y) = 
                x.Equals( y) }
    let (==~==) x y mcs = 
        let ec = SemanticComparer<'T>( seq { 
            yield valueObjectMemberComparer()
            yield! mcs } )
        seqSetEquals ec x y

通过阅读对于类型或记录,==|==技术可以起作用(除非严重地丢失了Likeness<'T>验证字段的覆盖范围,否则).但是,简洁可以使其成为某些测试的有价值的工具:-

For types or records, the ==|== technique can work (except critically you lose Likeness<'T>s verifying coverage of fields). However the succinctness can make it a valuable tool for certain sorts of tests :-

sut.Save( inputs)

let expected = inputs |> Seq.map (fun x -> Mapped( base + x.ttl, x.Name))

let likeExpected x = expected ==|== x <| (fun x y -> x.Name = y.Name && x.ValidUntil = y.ValidUntil)

verify <@ repo.Store( is( likeExpected)) @> once

这篇关于验证一组对象已正确映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆