haf / expecto

A smooth testing lib for F#. APIs made for humans! Strong testing methodologies for everyone!
Apache License 2.0
668 stars 96 forks source link

add Expect.almostEqual - floatClose for records and unions #294

Open olivercoad opened 5 years ago

olivercoad commented 5 years ago

I use Expect.floatClose in my tests, but often need to compare records such as

type TextData = {
  TextString: string
  Position: Vector
  Rotation: float
  Size: float
}

where each float is compared using floatClose, as well as the floats in the Vector.

I propose adding a new test such as almostEqual : accuracy:Accuracy -> actual'a -> expected:'a -> message:string -> unit which would recursively check for equality of each member, with the accuracy being used on floats*, otherwise F#'s normal structural equality for other primitives or complex types.

This would use reflection similar to in #280. If possible it would also be great to support unions and tuples.

*Would there be any good way for types to control how they are compared with accuracy (preferably without a dependency on anything)? For example a Vector that has a minus operator and abs member could be compared based on Euclidean distance.

haf commented 5 years ago

Wouldn't it be more correct and easier to reason about types of measurements? You could provide equality comparers for euclidian distance, similarity, groupedness, or whatever you consider is almost equal. Then we can build a library of these comparers. In your case, for values with overridden operators, you'd select the abs(a - b) comparer, for example.

AnthonyLloyd commented 5 years ago

If we were to do this in full i.e. recursive then it should form the basis of showing where exactly types differ generally. Applying different measures would be part of it too.

olivercoad commented 5 years ago

@haf Equality comparers would make a lot of sense, since it moves control over what is considered almost equal to the tests (where it probably should be) rather than the types. However because comparers are not covariant, it is difficult to provide them in a type-safe way (thinking Map<Type, IEqualityComparer<object>>). How do you suggest it would work?

@AnthonyLloyd That is a good idea. Perhaps, depending on the implementation, it could even feed back into #280 to recursively find the point of difference with Expect.equal also. And #276 on unions/tuples.

What do you mean by "Applying different measures"?

I think I have come up with what I think is a relatively elegant way of supporting almost-equality comparers and showing where objects differ, which is also composable. It will have to wait till the morning though.