Reputation: 436
Over time I've improved my unit testing by refactoring functions so they are pure functions meaning a set of inputs always produces the same output without depending on any sort of state and without causing side effects.
Occasionally I encounter scenarios for which I'm not sure how to write good unit tests such as when a function (or object) must depend on previous values. For purposes of demonstration I include this excerpt of code for an averaging filter here but really my question is quite general.
One of the reasons I created the method CAvgFilter::Clear was so that I could always insure the filter responds in a desirable way while it is being primed. While being primed CAvgFilter::Push outputs track the inputs quite closely but once the m_samples list is full, deviations to the inputs to CAvgFilter::Push are less pronounced due to the filtering.
With objects like these I usually want a way to call a method with several series of inputs while checking the outputs and ideally my unit test has as few dependencies on data ordering as possible (ideally none).
When creating test vectors I often log data from the function output, verify it manually, and then use that for future tests (to insure the class doesn't break if I refactor or modify it later). Anyway, it occurred to me that I can't exactly just use any series of inputs/outputs in the middle of some range. I have to start with the filter clear (or add some functionality to otherwise initialize it for test purposes). Can anyone offer some suggestions on how this sort of thing is tackled in unit tests? Do you always start your test in the initialized state or do you offer the unit test some way to set the initial condition?
CAvgFilter::CAvgFilter(int length)
{
m_maxLength = length;
}
//pass the new value and receive the moving average
long CAvgFilter::Push(long newValue)
{
m_samples.push_back(newValue);
if ((int) m_samples.size() > m_maxLength)
{
m_samples.pop_front();
}
long sum = 0;
for (FILTER_CONTAINER_IT it = m_samples.begin(); it != m_samples.end(); ++it)
{
sum += *it;
}
return (sum / (long) m_samples.size()); //cast size() as long to preserve sign of the average value
}
void CAvgFilter::Clear(void)
{
m_samples.clear();
}
Upvotes: 2
Views: 536
Reputation: 5939
Your situation is (on a general level) quite common. For example, the behaviour of state machines depends on the history of inputs. There are many ways of dealing with this.
There are, as always, pros and cons for the different approaches.
Upvotes: 3