Здравствуйте, djandy_spb, Вы писали:
_>Реальный код выложить не могу, но например сначала класс вот такой:
_>_>public class Statistics {
_>
_>не хочется его тестить типа генерируя разные стримы а потом сравнивая результат с заготовленными результатами.
Вообще то нужно полностью покрыть входы и выходы.
В твоем случае нужно разорвать зависимости от StreamReader и ResultFormatter
_>В этом примере отдельно можно оттестить StreamReader, ResultFormatter, можно логику обработки этих data1-data3 вынести в отдельный метод:
_>_>}
_>
_>Теперь все замечательно тестируется кроме метода do — как убедиться что он правильно вызвал doInternal?
Никак, он не учавствует в юниттестировании, только в интеграционном

Но я бы не вводил новый класс без нужды.
см. ниже как бы я орагнизовал этот класс для тестирования. Пример у тебя не самый удачный, т.к. не ясно, зачем тестировать StreamReader, а логику ты явно накидал прямо в браузере.
Итого, код с предположением, что StreamReader это какой то твой класс а не системный, что нужно изолироваться от ResultFormatter и тд
Очевидно, если StreamReader это системный, а ResultFormatter уже изолирован, то сгодится и твой первоначальный код
Как создавать такой класс для тестирования ? Очевидно — делать мок для Statistics, например Moq и всех его депенденсов или же наколбасить мок вручную, как показано ниже
// класс для тестирования. Создается или руками или через Moq
public class StatisticsMoq : Statistics {
public Func<Stream, StreamReader> FuncReaderFrom {get; set;}
public Func<ResultFormatter> FuncFormatter {get; set;}
public Func<ResultFormatter, Func<Data,bool, string> > FuncGetFormat {get; set;}
protected override StreamReader ReaderFrom(Stream stream)
{
return FuncReaderFrom();
}
protected override ResultFormatter Formatter()
{
return FuncFormatter();
}
protected override Func<Data,bool, string> getFormat(ResultFormatter formatter)
{
return FuncGetFormat(formatter);
}
}
public class Statistics {
public String do(Stream stream) {
// логика полностью тестируема со всеми потрохами
reader = ReaderFrom(stream);
formatter = Formatter();
var dataSequence = FetchFrom(reader);
return FormatSequence(dataSequence, getFormat(formatter) );
}
protected virtual Func<Data,bool, string> getFormat(ResultFormatter formatter)
{
// при тестировании можно(если нужно) что бы распознавать бехевиор FormatSequence
return (data, valid, stream) => formatter.format(valid.ToValidEnum();
}
protected virtual StreamReader ReaderFrom(Stream stream)
{
// этот метод можно(если нужно) переопределить для тестирования и там возвращать мок-ридер, что бы изолировать тесты от стрима
return new StreamReader(stream);
}
protected virtual ResultFormatter Formatter()
{
// этот метод можно(если нужно) переопределить для тестирования и там возвращать мок-форматтер,
// например если формат слишком сложный что бы колбасить его в тестах
return new ResultFormatter();
}
public static IEnumerable<Data> FetchFrom(StreamReader reader)
{ // идея в том, что извлечение из стрима отделяется от остальной логики. метод тестируемый - зависимость только от ридера
yield return reader.ReadData();
yield return reader.ReadData();
yield return reader.ReadData();
}
public static FormatSequence(IEnumerable<Data> seq, Func<Data,bool,string> format)
{
// основная логика полностью тестируема - депенденсы только от Data
var data1 = seq.ElementAt(1);
var data2 = seq.ElementAt(2);
var data3 = seq.ElementAt(3);
if (data1.valid())
return format(data1, true);
if (data2.valid())
return format(data1, false) + format(data2,true);
if (data3.valid())
return format(data1, false) + format(data2, false) + format(data3,true);
что вернуть если valid ни разу не встретилось ?
}
}
public enum ValidEnum
{
VALID,
INVALID
}
public static Helper
{
public static ValidEnum ToValidEnum(this bool isValid)
{
return isValid ? VALID : INVALID;
}
}