Gibt es mit FluentAssertions eine Möglichkeit zu steuern, wie die Werte eines Wörterbuchs auf Gleichheit verglichen werden?

Ich habe eine Klasse, von der eine Eigenschaft ein Wörterbuch ist (string / double). Ich möchte zwei Instanzen der Klasse (erwartet und tatsächlich) vergleichen und für die Wörterbuchmitglieder angeben, wie "Gleichheit" bestimmt wird.

Angenommen, ich habe eine Klasse wie gezeigt:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var t1 = new Thing();
        t1.Name = "Bob";
        t1.Values.Add("Hello", 100.111);
        t1.Values.Add("There", 100.112);
        t1.Values.Add("World", 100.113);

        var t2 = new Thing();
        t2.Name = "Bob";
        t2.Values.Add("Hello", 100.111);
        t2.Values.Add("There", 100.112);
        t2.Values.Add("World", 100.1133);

        t1.Should().BeEquivalentTo(t2);
    }
}

public class Thing
{
    public string Name { get; set; }

    public Dictionary<string, double> Values { get; set; } = new Dictionary<string, double>();
}

Ich möchte angeben können, wie beispielsweise der Eintrag "Welt" im Wörterbuch verglichen wird. In der Realität kann es sein, dass die Werte sehr groß sind oder über 10 Dezimalstellen gleich sind (aber nicht danach), aber ich denke, ich muss möglicherweise etwas wie "gleich, wenn weniger als 1% Unterschied" sagen.

Ich mag die Art und Weise, wie FluentAssertions mir das Mitglied sagt und warum sie nicht gleich sind, und habe eine benutzerdefinierte IAssertionRule (mit Options Lambda) ausprobiert, aber das schien nur die Klasseneigenschaften zu vergleichen, nicht die Mitglieder des Wörterbuchs.

Ich besitze die zu vergleichenden Klassen nicht, kann also die "Equal" -Methode nicht überschreiben und finde keine Möglichkeit, einen benutzerdefinierten Vergleicher (IEquatable) anzugeben - aber ich vermute, ich würde die fließenden Details darüber verlieren, warum sie nicht gleich sind .

Wenn es möglich wäre, aber dass jede Methode auch für die Doubles gelten würde, die Eigenschaften der Klasse sind (im Gegensatz zu Werten im Wörterbuch), wäre das in Ordnung.

Vielen Dank.

1
Black Light 24 Juni 2018 im 01:16

3 Antworten

Beste Antwort

Nach der Antwort von Nkosi ist dies ein Beispiel für ein BeApproximate, das ich verwende (um die Verwendung von BeApproximately mit einem decimal? zu ermöglichen):

    [CustomAssertion]
    public static void BeApproximately(this NullableNumericAssertions<decimal> value, decimal? expected, decimal precision, string because = "",
        params object[] becauseArgs)
    {
        if (expected == null)
            value.BeNull(because);
        else
        {
            if (!Execute.Assertion.ForCondition(value.Subject != null).BecauseOf(because)
                .FailWith($"Expected {{context:subject}} to be '{expected}' {{reason}} but found null"))
                return;

            Decimal num = Math.Abs(expected.Value - (Decimal) value.Subject);

            Execute.Assertion.ForCondition(num <= precision).BecauseOf(because, becauseArgs).FailWith("Expected {context:value} to approximate {1} +/- {2}{reason}, but {0} differed by {3}.", (object) value.Subject, (object) expected.Value, (object) precision, (object) num);
        }
    }
1
Michal Ciechan 25 Juni 2018 im 10:38

BeApproximately kann verwendet werden, um die Doubles innerhalb eines akzeptablen Genauigkeitsbereichs zu vergleichen. Die Verwendung zusammen mit der Konfiguration der Genauigkeit für alle Doppel sollte das gewünschte Verhalten erfüllen.

t1.Should().BeEquivalentTo(t2, options => options
    .Using<double>(ctx => 
        ctx.Subject.Should().BeApproximately(ctx.Expectation, ctx.Expectation * 0.01D))
    .WhenTypeIs<double>()
);

Referenz Objektdiagrammvergleich: Äquivalenzvergleichsverhalten

3
Nkosi 25 Juni 2018 im 12:15

Nach Michals ausgezeichnetem Beitrag habe ich begonnen, mit Folgendem zu arbeiten, was vielversprechend ist:

    [CustomAssertion]
    public static void BeWithinPercentageOf(this NumericAssertions<double> value, double expected, double tolerance, string because = "", params object[] becauseArgs)
    {
        if (!Execute.Assertion.ForCondition(value.Subject != null)
            .BecauseOf(because)
            .FailWith($"Expected {{context:subject}} to be '{expected}' {{reason}} but found null"))
            return;

        var actual = (double)value.Subject;
        var diff = Math.Abs(expected - actual);

        if (diff > double.Epsilon)
        {
            var percent = Math.Round(100 / (expected / diff), 2);

            Execute.Assertion.ForCondition(percent <= tolerance)
                .BecauseOf(because, becauseArgs)
                .FailWith("Expected {context:value} to be {1} (±{2}%){reason}, but {0} differed by {3}%.", actual, expected, tolerance, percent);
        }
    }
0
Black Light 28 Juni 2018 im 12:19