AutoFixture was originally build as a tool for Test-Driven Development (TDD), and TDD is all about feedback. In the spirit of GOOS, you should listen to your tests. If the tests are hard to write, you should consider your API design. AutoFixture tends to amplify that sort of feedback, and here's what it's telling me.
Make comparison easier
First, while not related to AutoFixture, the Quote
class just begs to be turned into a proper Value Object, so I'll override Equals
to make it easier to compare expected and actual instances:
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;
}
(Make sure to override GetHashCode
too.)
Copy and update
The above attempt at a test seems to imply that we're lacking a way to vary a single field while keeping the rest constant. Taking a cue from functional languages, we can introduce a way to do that on the Quote
class itself:
public Quote WithClose(decimal newClose)
{
return new Quote(
_tradingDate,
_open,
_high,
_low,
newClose,
_closeAdjusted,
_volume);
}
This sort of API tends to be very useful on Value Objects, to the point where I always add such methods to my Value Objects.
Let's do the same with Symbol
:
public Symbol WithHistoricalQuotes(IEnumerable<Quote> newHistoricalQuotes)
{
return new Symbol(_identifier, newHistoricalQuotes);
}
This makes it much easier to ask AutoFixture to deal with all the stuff you don't care about while explicitly stating only that which you care about.
Testing with AutoFixture
The original test can now be rewritten as:
[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);
}
This test only states those parts of the test case that you care about, while AutoFixture takes care of all the other values that don't have any impact on the test case. This makes the test more robust, as well as more readable.