2013-07-24 36 views
5

这是我的目标:Autofixture生成自定义列表

public class Symbol 
{ 
    private readonly string _identifier; 
    private readonly IList<Quote> _historicalQuotes; 

    public Symbol(string identifier, IEnumerable<Quote> historicalQuotes = null) 
    { 
     _identifier = identifier; 
     _historicalQuotes = historicalQuotes; 
    } 
} 

public class Quote 
{ 
    private readonly DateTime _tradingDate; 
    private readonly decimal _open; 
    private readonly decimal _high; 
    private readonly decimal _low; 
    private readonly decimal _close; 
    private readonly decimal _closeAdjusted; 
    private readonly long _volume; 

    public Quote(
     DateTime tradingDate, 
     decimal open, 
     decimal high, 
     decimal low, 
     decimal close, 
     decimal closeAdjusted, 
     long volume) 
    { 
     _tradingDate = tradingDate; 
     _open = open; 
     _high = high; 
     _low = low; 
     _close = close; 
     _closeAdjusted = closeAdjusted; 
     _volume = volume; 
    } 
} 

我需要填充的报价列表符号的一个实例。

在我的测试中,我想验证我可以返回收盘价低于或高于特定值的所有报价。这是我的测试:

[Fact] 
public void PriceUnder50() 
{ 
    var msftIdentifier = "MSFT"; 
    var quotes = new List<Quote> 
    { 
     new Quote(DateTime.Parse("01-01-2009"), 0, 0, 0, 49, 0, 0), 
     new Quote(DateTime.Parse("01-02-2009"), 0, 0, 0, 51, 0, 0), 
     new Quote(DateTime.Parse("01-03-2009"), 0, 0, 0, 50, 0, 0), 
     new Quote(DateTime.Parse("01-04-2009"), 0, 0, 0, 10, 0, 0) 
    }; 

    _symbol = new Symbol(msftIdentifier, quotes); 


    var indicator = new UnderPriceIndicator(50); 
    var actual = indicator.Apply(_symbol); 

    Assert.Equal(2, actual.Count); 
    Assert.True(actual.Any(a => a.Date == DateTime.Parse("01-01-2009"))); 
    Assert.True(actual.Any(a => a.Date == DateTime.Parse("01-04-2009"))); 
    Assert.True(actual.Any(a => a.Price == 49)); 
    Assert.True(actual.Any(a => a.Price == 10)); 
} 

好的。

现在,我想要使用Autofixture,我是一个真正的初学者。我已经在网上阅读了关于这个工具的几乎所有东西(作者的博客,codeplex FAQ,github源代码)。我了解自动混合的基本特征,但现在我想在我的真实项目中使用自动混合。这是我到目前为止所尝试的。

var msftIdentifier = "MSFT"; 

var quotes = new List<Quote>(); 
var random = new Random(); 
fixture.AddManyTo(
    quotes, 
    () => fixture.Build<Quote>().With(a => a.Close, random.Next(1,49)).Create()); 
quotes.Add(fixture.Build<Quote>().With(a => a.Close, 49).Create()); 

_symbol = new Symbol(msftIdentifier, quotes); 

// I would just assert than 49 is in the list 
Assert.True(_symbol.HistoricalQuotes.Contains(new Quote... blabla 49)); 

理想情况下,我宁愿直接创建符号的灯具,但我不知道如何自定义我的列表引号。我不确定我的测试是否是通用的,因为在另一个测试中,我需要检查一个特定的值是否在上面,所以我要复制“夹具代码”并手动添加一个报价= 51.

所以我的问题是:

1 - 难道我应该如何使用autofixture的方式?

2 - 是否有可能改善我在我的例子中使用自动混合的方式?

回答

10

AutoFixture最初是作为测试驱动开发(TDD)的工具构建的,而TDD全部是关于反馈。本着GOOS的精神,您应该倾听您的测试。如果测试很难写,你应该考虑你的API设计。 AutoFixture倾向于放大这种反馈,这是它告诉我的。

制作比较容易

首先,虽然没有涉及到AutoFixture的Quote类只是回避要变成一个适当的值对象,所以我会重写Equals以使其更容易比较预期和实际情况:

public override bool Equals(object obj) 
{ 
    var other = obj as Quote; 
    if (other == null) 
     return base.Equals(obj); 

    return _tradingDate == other._tradingDate 
     && _open == other._open 
     && _high == other._high 
     && _low == other._low 
     && _close == other._close 
     && _closeAdjusted == other._closeAdjusted 
     && _volume == other._volume; 
} 

(确保覆盖GetHashCode了。)

复制和更新

在测试上面的尝试似乎暗示我们缺乏一种方式来改变单场同时保持恒定的休息。函数式语言得到暗示,我们可以引入一个办法做到这一点的Quote类本身:

public Quote WithClose(decimal newClose) 
{ 
    return new Quote(
     _tradingDate, 
     _open, 
     _high, 
     _low, 
     newClose, 
     _closeAdjusted, 
     _volume); 
} 

这类API的往往是价值的对象是非常有用的,到这种地步,我总是添加这样的方法我的价值对象。

让我们做同样的Symbol

public Symbol WithHistoricalQuotes(IEnumerable<Quote> newHistoricalQuotes) 
{ 
    return new Symbol(_identifier, newHistoricalQuotes); 
} 

这使得它更容易问AutoFixture应对所有的东西你不关心同时明确只有你在乎说明。

测试与AutoFixture

原来的测试现在可以改写为:

[Fact] 
public void PriceUnder50() 
{ 
    var fixture = new Fixture(); 
    var quotes = new[] 
    { 
     fixture.Create<Quote>().WithClose(49), 
     fixture.Create<Quote>().WithClose(51), 
     fixture.Create<Quote>().WithClose(50), 
     fixture.Create<Quote>().WithClose(10), 
    }; 
    var symbol = fixture.Create<Symbol>().WithHistoricalQuotes(quotes); 
    var indicator = fixture.Create<UnderPriceIndicator>().WithLimit(50); 

    var actual = indicator.Apply(symbol); 

    var expected = new[] { quotes[0], quotes[3] }; 
    Assert.Equal(expected, actual); 
} 

这个测试只是说你关心,而AutoFixture通吃的护理测试用例的那些部分其他值对测试用例没有任何影响。这使得测试更健壮,而且更具可读性。

+1

+1 @Gui和其他人马克太客气了,但他的[PluralSight Advanced Unit Testing](http://pluralsight.com/training/courses/TableOfContents?courseName=advanced-unit-testing)当然是chock充满了这种善良。值得付出,但更好,他们有一个免费试用,我不能想到一个更好的课程使用它(当然[外部在测试驱动开发](http://pluralsight.com/training/Courses/TableOfContents/外部在tdd)是接近第二) –

+4

@马克西曼感谢您的评论马克。我同意你的测试肯定更具可读性。我打算提出一些更多的问题,但是你的答案非常完整,每次我重新阅读你的答案时都会回答我的每一个问题。 Omg,你可能是单元测试的神。 – Gui

+0

如何为测试项目中定义的'WithClose','WithLimit' *添加扩展方法,而不是将它们添加为实例方法? (现在在C#6中,他们甚至不需要处于静态类) – 2016-02-07 21:21:27