2014-01-14 3 views
7

Я работаю по TDD Кент Бек по примеру, как академическое упражнение, но используя MSpec для написания тестов. Когда я следую приведенным примерам, мне нравится вводить твист, чтобы я не мог просто скопировать текст из строки, я нахожу, что у меня возникают проблемы, которые я должен решить, и в результате получаю гораздо больше знаний. Я считаю, что это один из тех случаев.Почему мой метод Equals не вызван?

Я частично переживаю пример «денег» Кента. Вот структура класса у меня есть: enter image description here

У меня есть следующие два теста контексты:

[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; 
} 

Метод Times() возвращает новый объект типа денег, содержащий результат, то есть объекты неизменны. первый вышеперечисленный контекст, предполагающий, что Equals работает как ожидалось, т. е. игнорирует типы объектов, если они оба унаследованы от Money, и только сравнивает, что сумма и валютные поля равны. Второй контекст выходит из строя с аналогичным результатом:

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 

Равенство определяется как сумма (стоимость) и валюта одинакова; предполагается, что фактический тип объекта игнорируется, поэтому предполагаемый результат заключается в том, что не имеет значения, проверяю ли я равенство с объектами «Деньги» или «Франк», если поля суммы и валюты совпадают. Однако все работает не так, как планировалось. При отладке мои методы Equals() даже не вызываются. Очевидно, что-то я не понимаю здесь. Я уверен, что решение будет ослепительно очевидным, когда я это знаю, но я не вижу его для поиска. Может ли кто-нибудь предложить предложение о том, что мне нужно сделать, чтобы сделать эту работу?

Вот реализация 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

После большого рассмотрения, я считаю, что это может быть проблема MSpec. MSpec сравнивает типы объектов и отказывает сравнение, основанное на том, что типы не совпадают. Только если типы равны, MSpec затем продолжает использовать сравнение, основанное на значении. Вот почему мой метод Equals никогда не вызывается. Однако, я думаю, Лисков говорит, что я должен иметь возможность сравнивать Деньги с франком, если это позволяет мое определение равенства.Поэтому я открыл проблему с проектом MSpec. https://github.com/machine/machine.specifications/issues/200 –

+0

@ Энтони Я предпочитаю отступы стиля Whitesmiths (скобки с отступом). Я не особо возражаю против того, что вы меняете мой отступ, и я счастлив, что ваши правки стоят, но, с другой стороны, кажется немного самонадеянным переоценивать предпочтения других людей. Есть ли руководство, о котором я не знаю? –

+0

Ой, извините, я просто предполагал, что у вас плохой пробел/табуляция или что-то в этом роде. Я получаю все глянцевые глаза от общего форматирования кода неправильного кода SO. –

ответ

2

Полностью завершена реализация равенства будет выглядеть следующим образом. Посмотрите, помогает ли это.

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

Такое определение ТОЛЬКО правильно, если поля 'amount' и' currency' доступны только для чтения (или гарантированно никогда не изменяются). ТРЕБУЕТСЯ, что значение, возвращаемое из 'GetHashCode', является неизменным для времени жизни экземпляра. – Enigmativity

+0

@ Энигматичность: Действительно, как указано в вышеприведенном вопросе: «то есть объекты неизменяемы» – spender

+0

Хотя это очень тщательная и полная реализация равенства, она не отвечает на вопрос ОП, почему он не вызывается на первое место. – Xint0

0

Как отметил @Harrison, проблема является типом результата Times метода вашего Franc класса. Он не выполняет спецификацию теста, поскольку возвращает объект Money, но спецификация ожидает экземпляр Franc. Либо измените спецификацию, чтобы потребовать объект Money, либо переопределите метод Times, чтобы вернуть экземпляр Franc.

Update

После обновления тестовой спецификации, вы изменили линию:

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)); 

Но, по атрибуту типа предмета до сих пор является:

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

Так Я думаю, что он все еще ожидает экземпляр Franc вместо экземпляра Money.

+0

Извинения, я обновил этот вопрос 3 раза за последние 10 минут, я не ожидал такого быстрого ответа! Ваш ответ по-прежнему сохраняется после моих обновлений? –

+0

@TimLong Только что обновил мои наблюдения, надеюсь, что это поможет. – Xint0

+0

Атрибут [Тема] предназначен только для документации, он не оказывает никакого влияния на код. Это может показаться странным, но примером является mid-refactor, и он движется к устранению классов франка и доллара в пользу единого класса Money. Поэтому это что-то вроде надуманной ситуации. Я мог просто прервать погоню и поставить готовый реорганизованный код там, но я хотел понять, как это пошло не так. Я все еще не понимаю, почему это не работает. –

Смежные вопросы