2014-01-14 41 views
7

我正在使用Kent Beck的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; 
} 

时报()方法返回类型货币包含结果的新的对象,即,对象是不可改变的。上面的第一个上下文通过,表明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 

平等被定义为金额(价值)和货币相同;该对象的实际类型应该被忽略,所以预期的结果是,只要数量和货币字段相同,我是否在测试与Money或Franc对象的平等性就不重要。但是,事情并没有按计划进行。在调试时,我的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方法永远不会被调用。不过,我认为Liskov说,如果我对平等的定义允许,我应该可以将货币与法郎进行比较。因此,我在MSpec项目中提出了一个问题。 https://github.com/machine/machine.specifications/issues/200 –

+0

@Anthony我更喜欢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

@Enigmativity:事实上,正如上面的问题所述:“即对象是不可变的” – spender

+0

虽然这是一个非常彻底和完整的平等执行,它不回答OP问题,为什么它不被调用第一名。 – Xint0

0

由于@Harrison评论,问题是你的Franc类的Times方法的结果类型。它未通过测试规范,因为它返回一个Money对象,但规范期望一个Franc实例。将规范更改为需要Money对象或覆盖Times方法以返回Franc实例。

更新

更新测试规范后,更改了线路:

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

道歉,我在最近10分钟内更新了这个问题3次,我没想到会有这么快的反应!我的更新后,您的答案是否仍然成立? –

+0

@TimLong刚刚更新了我的观察,希望它有帮助。 – Xint0

+0

[Subject]属性仅用于文档,对代码没有任何影响。这看起来可能很奇怪,但例子是中期重构,它正在逐步消除法郎和美元类,转而支持单一的通用Money类。因此,它是一种人为的情况。我可以简单地切入追逐,并将完成的重构代码放在那里,但我想了解这是怎么出错的。我仍然没有看到它为什么不起作用。 –

相关问题