自动混合生成自定义列表

Joh*_*ohn 5 refactoring 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我需要一个填充了一个Quote列表的Symbol实例.

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

[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));
}
Run Code Online (Sandbox Code Playgroud)

好.

现在,我想使用Autofixture,我是一个真正的初学者.我已经在互联网上阅读了关于这个工具的所有内容(作者的博客,codeplex FAQ,github源代码).我理解autofixture的基本功能,但现在我想在我的真实项目中使用自动混合.这是我到目前为止所尝试的内容.

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));
Run Code Online (Sandbox Code Playgroud)

理想情况下,我更喜欢直接创建Symbol的夹具,但我不知道如何自定义我的引号列表.而且我不确定我的测试是通用的,因为在另一个测试中,我需要检查特定值是否高于而不是,所以我要复制"fixture代码"并手动添加引号= 51.

所以我的问题是:

1 - 这是我应该如何使用自动混合?

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

Mar*_*ann 11

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;
}
Run Code Online (Sandbox Code Playgroud)

(确保也要覆盖GetHashCode.)

复制和更新

上述测试尝试似乎意味着我们缺乏改变单个场的方法,同时保持其余的不变.从函数式语言中获取提示,我们可以介绍一种在Quote类本身上执行此操作的方法:

public Quote WithClose(decimal newClose)
{
    return new Quote(
        _tradingDate,
        _open,
        _high,
        _low,
        newClose,
        _closeAdjusted,
        _volume);
}
Run Code Online (Sandbox Code Playgroud)

这种API在Value Objects上非常有用,我总是将这些方法添加到Value Objects中.

让我们做同样的事情Symbol:

public Symbol WithHistoricalQuotes(IEnumerable<Quote> newHistoricalQuotes)
{
    return new Symbol(_identifier, newHistoricalQuotes);
}
Run Code Online (Sandbox Code Playgroud)

这使得更容易让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);
}
Run Code Online (Sandbox Code Playgroud)

此测试仅说明您关心的测试用例的那些部分,而AutoFixture负责处理对测试用例没有任何影响的所有其他值.这使得测试更加健壮,并且更具可读性.

  • @Mark Seemann感谢您的评论Mark.我同意你的测试肯定更具可读性.我打算再问一些问题,但你的答案是如此完整,每次重新阅读你的答案时,它都会不断回答我的每一个问题.哦,你可能是单元测试之神. (4认同)