2014-01-14 8 views
7

Ich arbeite durch Kent Becks TDD mit Beispiel als eine akademische Übung, aber mit MSpec, um die Tests zu schreiben. Wenn ich bearbeiteten Beispielen folge, möchte ich eine Wendung einführen, damit ich den Text nicht einfach kopieren kann. Ich finde, dass ich dazu tendiere, auf Probleme zu stoßen, die ich lösen muss, und am Ende viel mehr lerne. Ich glaube, das ist eine dieser Gelegenheiten.Warum wird meine Equals-Methode nicht aufgerufen?

Ich bin ein Stück weit durch Kents 'Geld' Beispiel. Hier ist die Klassenstruktur ich habe: enter image description here

Ich habe die folgenden zwei Test Kontexten:

[Subject(typeof(Money), "Equality")] 
public class when_comparing_different_classes_for_equality 
{ 
    Because of =() => FiveFrancs = new Franc(5, "CHF"); 
    It should_equal_money_with_currency_set_to_francs =() => FiveFrancs.Equals(new Money(5, "CHF")).ShouldBeTrue(); 
    static Franc FiveFrancs; 
} 

[Subject(typeof(Franc), "multiplication")] 
public class when_multiplying_a_franc_amount_by_an_integer 
{ 
    Because of =() => FiveFrancs = new Franc(5, null); 
    It should_be_ten_francs_when_multiplied_by_2 =() => FiveFrancs.Times(2).ShouldEqual(Money.Franc(10)); 
    It should_be_fifteen_francs_when_multiplied_by_3 =() => FiveFrancs.Times(3).ShouldEqual(Money.Franc(15)); 
    static Franc FiveFrancs; 
} 

The Times() Methode gibt ein neues Objekt vom Typ Geld das Ergebnis enthält, das heißt, die Objekte sind unveränderlich. Der erste obige Kontext wird übergeben, was darauf hindeutet, dass Equals wie erwartet funktioniert, d. h. es ignoriert die Objekttypen, solange sie beide von Money geerbt werden und vergleicht nur, dass Betrag und Währungsfelder gleich sind. Der zweite Kontext schlägt bei einer ähnlichen Ausgabe fehl:

Machine.Specifications.SpecificationException 
    Expected: TDDByExample.Money.Specifications.Franc:[15] 
    But was: TDDByExample.Money.Specifications.Money:[15] 
    at TDDByExample.Money.Specifications.when_multiplying_a_franc_amount_by_an_integer.<.ctor>b__2() in MoneySpecs.cs: line 29 

Gleichheit ist definiert als Menge (Wert) und Währung, die gleich sind; Der tatsächliche Typ des Objekts wird ignoriert. Das beabsichtigte Ergebnis ist also, dass es egal ist, ob ich die Gleichheit mit Money- oder Franc-Objekten teste, solange die Betrags- und Währungsfelder gleich sind. Die Dinge funktionieren jedoch nicht wie geplant. Beim Debuggen werden meine Equals() -Methoden nicht einmal aufgerufen. Da ist eindeutig etwas, was ich hier nicht verstehe. Ich bin mir sicher, dass die Lösung blendend offensichtlich wird, wenn ich es weiß, aber ich kann es nicht sehen. Kann mir jemand einen Vorschlag machen, was ich tun muss, damit das funktioniert?

Hier ist die Implementierung von Equals():

public bool Equals(Money other) 
{ 
    return amount == other.amount && currency == other.currency; 
} 

public override bool Equals(object obj) 
{ 
    if (ReferenceEquals(null, obj)) 
     return false; 
    if (ReferenceEquals(this, obj)) 
     return true; 
    return Equals(obj as Money); 
} 
+0

Nach viel Überlegung glaube ich, dass dies ein MSpec Problem sein kann. MSpec vergleicht die Objekttypen und vergleicht den Vergleich nicht, weil die Typen nicht übereinstimmen. Nur wenn die Typen gleich sind, verwendet MSpec dann einen wertebasierten Vergleich. Das ist der Grund, warum meine Equals-Methode nie aufgerufen wird. Ich glaube jedoch, dass Liskov sagt, dass ich in der Lage sein sollte, ein Geld mit einem Franc zu vergleichen, wenn meine Definition von Gleichheit dies zulässt.Daher habe ich ein Problem mit dem MSpec-Projekt geöffnet. https://github.com/machine/machine.specifications/issues/200 –

+0

@Anthony Ich bevorzuge Whitesmiths Stil Einrückung (geschweifte Klammern eingerückt). Es macht mir nichts aus, wenn Sie meine Einrückung ändern, und ich bin froh, dass Ihre Änderungen bestehen bleiben, aber auf der anderen Seite scheint es etwas vermessen zu sein, die Vorlieben anderer Menschen zu übersteuern. Gibt es eine Richtlinie, die mir nicht bekannt ist? –

+0

Oh, tut mir leid, ich habe einfach nur angenommen, dass du einen schlechten Platz/Tab Mix hast oder so. Ich bekomme glänzende Augen von der allgemeinen schlechten Code-Block-Formatierung von SO. –

Antwort

2

Eine voll vollständige Umsetzung der Gleichstellung würde wie folgt aussehen. Sehen Sie, ob es hilft.

protected bool Equals(Money other) 
{ 
    // maybe you want this extra param to Equals? 
    // StringComparison.InvariantCulture 
    return amount == other.amount 
     && string.Equals(currency, other.currency); 
} 

public override bool Equals(object obj) 
{ 
    if (ReferenceEquals(null, obj)) return false; 
    if (ReferenceEquals(this, obj)) return true; 
    var other = obj as Money; 
    return other != null && Equals(other); 
} 

public override int GetHashCode() 
{ 
    unchecked 
    { 
     return (amount * 997)^currency.GetHashCode(); 
    } 
} 

public static bool operator ==(Money left, Money right) 
{ 
    return Equals(left, right); 
} 

public static bool operator !=(Money left, Money right) 
{ 
    return !Equals(left, right); 
} 
+0

Diese Art von Definition ist nur dann korrekt, wenn die Felder "Betrag" und "Währung" schreibgeschützt sind (oder garantiert nie geändert werden). Es ist NOTWENDIG, dass der von 'GetHashCode' zurückgegebene Wert für die Lebensdauer der Instanz unveränderbar ist. – Enigmativity

+0

@Enigmatismus: In der Tat, wie in der obigen Frage gesagt: "dh die Objekte sind unveränderlich" – spender

+0

Obwohl dies eine sehr gründliche und vollständige Umsetzung der Gleichheit ist, beantwortet es nicht die OP Frage, warum es nicht auf die aufgerufen wird erster Platz. – Xint0

0

Wie @Harrison kommentierte, ist das Problem der Ergebnistyp der Times Methode Ihrer Franc Klasse. Es erfüllt die Testspezifikation nicht, weil es ein Money Objekt zurückgibt, aber die Spezifikation erwartet eine Franc Instanz. Ändern Sie entweder die Spezifikation so, dass ein Objekt Money erforderlich ist, oder überschreiben Sie die Methode Times, um eine Franc Instanz zurückzugeben.

aktualisieren

Nach der Testspezifikation Aktualisierung geändert Sie die folgenden Zeilen:

It should_be_ten_francs_when_multiplied_by_2 =() => FiveFrancs.Times(2).ShouldEqual(Money.Franc(10)); 
It should_be_fifteen_francs_when_multiplied_by_3 =() => FiveFrancs.Times(3).ShouldEqual(Money.Franc(15)); 

Aber auf dem Attribut der Art des Themas noch ist:

[Subject(typeof(Franc), "multiplication")] 

So Ich denke, es erwartet immer noch eine Franc Instanz anstelle einer Money Instanz.

+0

Entschuldigung, ich habe diese Frage innerhalb der letzten 10 Minuten dreimal aktualisiert, ich hatte keine so schnelle Antwort erwartet! Bleibt Ihre Antwort auch nach meinen Updates bestehen? –

+0

@TimLong Nur mit meinen Beobachtungen aktualisiert, hoffe es hilft. – Xint0

+0

Das Attribut [Subject] dient nur der Dokumentation, es hat keine Auswirkungen auf den Code. Es mag merkwürdig erscheinen, aber das Beispiel ist ein Mid-Refactor und es bewegt sich in Richtung der Eliminierung der Franc- und Dollar-Klassen zugunsten einer einzigen Allzweck-Money-Klasse. Es handelt sich also um eine erfundene Situation. Ich könnte einfach auf die Jagd gehen und den fertigen Refaktorcode dort hineinlegen, aber ich wollte verstehen, wie das schief gelaufen ist. Ich verstehe immer noch nicht, warum es nicht funktioniert. –