Overview
Part of the motivation for #1 and #2 was to make it easier to write tests against IStrategy
implementations, and particularly to move test data closer to the tests, rather than being forced to use TheoryData
. Previously I had (erroneously) thought I could only pass one array to a test method by using params
, not realising an array could be passed to the ctor of an attribute. The plan was to allow string
s to be used for the hint
and line
parameters which could be parsed into Hint
and Line
types respectively. This worked but led to output on failing tests similar to:
Expected newTrivialStrategy().Execute(hint,line) to be equal to {CellState { Description = Filled, Boolean = True, Character = 1 }, CellState { Description = Filled, Boolean = True, Character = 1 }, CellState { Description = Filled, Boolean = True, Character = 1 }, CellState { Description = Filled, Boolean = True, Character = 1 }, CellState { Description = Filled, Boolean = True, Character = 1 }}, but {CellState { Description = Undetermined, Boolean = , Character = . }, CellState { Description = Undetermined, Boolean = , Character = . }, CellState { Description = Undetermined, Boolean = , Character = . }, CellState { Description = Undetermined, Boolean = , Character = . }, CellState { Description = Undetermined, Boolean = , Character = . }} differs at index 0.
Clearly, this is horrendous to read and to try to work out what the actual issue is π’
Cause
The problem is caused by Line
now implementing IEnumerable<CellState>
. This means the Should
extension method returns a GenericCollectionAssertions
and compares the Line
as an enumerable, checking each element in the enumerable. If it finds a CellState
that doesn't match it then outputs the enumerable. The default behaviour here is to use the ToString
method on each element in the collection. In our case that is a record struct
so we get the default implementation of a comma delimited list of the public properties!
Potential Solutions
Override ToString
on CellState
This dramatically improves the format of the output, and gives us something similar to:
Expected newTrivialStrategy().Execute(hint,line) to be equal to {1, 1, 1, 1, 1}, but {., ., ., ., .} differs at index 0.
This is much more readable. It has the advantages of being low impact in terms of the number of places we would need to make changes. It also gives us the advantage of seeing the first index where there is a difference. This also keeps the ToString
implementation in line with the implicit conversion to string.
On the downside, this changes the ToString
implementation from what people might expect for a record, so is arguably a violation of the principle of least surprise. Repeated strings of .,
might also be a little tricky to read.
Convert Line
to string
and compare that
This dramatically improves the format of the output, and gives us something similar to:
* Expected ((string)actual) to be "11111", but "....." differs near "..." (index 0).
This also has the advantage of being able to see the index for where the first differences appear, and having much shorter output. Assuming we supply the input data as a string we see comparison against similar values. This makes sense from an implementation point of view. Finally, we also keep the expected implementation of ToString
for a record in place.
This has the downside of requiring more changes, but not excessively so.
On the down
Manually enumerate CellState
s and compare individually
This requires more work so I haven't got sample output for this option. However, because we could use an AssertionScope
we would be able to see the index of all cells that did not match. In addition I would be able to use the because
and becauseArgs
params of Be
to be something along the lines of should have been {0} but was {1}
to give a more descriptive explanation.
This would require each test changing, and in a very similar way. I could introduce some form of helper that would be consumed by all test cases, but the MO so far with this repo has been to experiment with having as much code as possible in the test itself, and to try to avoid things such as shared set-up etc
This would also end up comparing CellStates
; it is just that we're doing it by hand rather than letting Fluent Assertions do it for us. That means we'll still pick up the default implementation ToString
for CellState
which could be clumsy in the test output.