Re: Тест метода класса Java, вызывающего другие методы
От: avpavlov  
Дата: 10.04.11 09:48
Оценка: -1
Прочитал тут всё что понаписали и вспомнил нашу компанию и внедрение юнит-тестов.

Примерно так эволюция и происходила

— тестируем в лоб
— понимаем, что долго, надо по частям, при помощи моков
— в дальнейшем, постепенно, при правках и при рефакторинге понимаем, что все тесты из предыдущего подхода — шлак
— возвращаемся к первому варианту

После 5 лет писания юнит-тестов пришло понимание, что важно не переступить грань между "тестируем контракт публичного метода" и "тестируем фактическую имплементацию".

Тесты полученные вторым способом на самом деле становятся бесполезны сразу после написания, хотя в момент написания они архиполезны: позволяют критически взглянуть на код, правильно подойти к проектированию, быстро проверить все ветки алгоритма.

Использование моков гвоздями прибивает тесты к внутренней имплементации и простейшие изменения в этой имплементации (не затрагивающие контракт!) требуют переписывания тестов.

Стремление съэкономить пойдя по 2му пути на самом деле потом обернётся постоянными мелкими правками и невозможностью опираться на эти тесты во время рефакторинга.

Вообщем, коллеги, тестируйте контракт, не имплементацию, и будет вам счастье!
Тест метода класса Java, вызывающего другие методы
От: djandy_spb  
Дата: 16.02.11 12:12
Оценка:
Есть класс, у которого есть один основной метод:
1) берет на входе некий объект для доступа к разнообразной информации, стрим например
2) совершает над ним операции по вытаскиванию информации
3) обрабатывает информацию
4) выдает результат обработки.

Тестировать в лоб основной метод очень сложно — миллион вариантов начальных данных и набор финальных результатов,

Допустим действия 2,3 и 4 вынесены в отдельные промежуточные методы вспомогательных подклассов, т.о. их по отдельности можно оттестировать и заменить моками.

Тогда основной метод можно переделать:
* он создает все вспомогательные классы и вызывает вызывает другой метод, в который передает эти экземпляры.
* этот другой метод уже будет в правильном порядке дергать методы подклассов.

Подклассы тестируются отдельно, метод в который подклассы передаются — тестируется с помощью моков этих подклассов, все замечательно.

А теперь вопрос — как протестировать самый главный метод, который создает подклассы?

Вижу два варианта:
1) в конструктор всего класса передавать фабрики подклассов
2) фабрики создавать в виде моков, которые будут также выдавать моки подклассов.
3) проверять что главный метод вызвал методы фабрик
4) кодировать моки подклассов так, чтобы на них получался эталонный результат, а потом с ним сравнивать.

Но тут в пункте 4) огромный недостаток: он неявно тестирует практически весь класс целиком — это опять по сути исходная проблема.

Второй вариант — извращенский:
1)-3) такие же
4) Создать класс-наследник тестируемого класса
5) Оверрайдить второй метод (который изнутри вызывается тестируемым главным методом)
6) вызвать главный метод и убедиться, что он создал правильные подклассы и передал в наш оверрайденый метод.

В этом случае все методы отделены друг от друга и не надо строить всю цепочку от начальных тестовых данных до правильного финального результата.

Но это все-таки извращение какое-то, да и не всегда так получится.
Что можно в консерватории подправить?
Re: Тест метода класса Java, вызывающего другие методы
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 16.02.11 14:09
Оценка:
Здравствуйте, djandy_spb, Вы писали:

_>Подклассы тестируются отдельно, метод в который подклассы передаются — тестируется с помощью моков этих подклассов, все замечательно.


_>А теперь вопрос — как протестировать самый главный метод, который создает подклассы?


_>Но это все-таки извращение какое-то, да и не всегда так получится.

_>Что можно в консерватории подправить?

Покажи пример лучше нормальный. Все зависит от того кода, что в этом основном методе. Нужно определиться с конечным дизайном.

Дальнейшие действия по созданию тестопригодного кода и рефакторинг вобщем то сложности не представляют.

Я бы не менял код гигантского класса без заготовленых заранее тестов. Поначалу нужно худо-бедно обрезать лишние зависимости и написать тесты которые будут детектить конкретные ситуации, которые ты хочешь модифицировать.
Re[2]: Тест метода класса Java, вызывающего другие методы
От: djandy_spb  
Дата: 16.02.11 15:49
Оценка:
I>Покажи пример лучше нормальный. Все зависит от того кода, что в этом основном методе. Нужно определиться с конечным дизайном.

Реальный код выложить не могу, но например сначала класс вот такой:

public class Statistics {
    public String do(Stream stream) {
        reader = new StreamReader();
        formatter = new ResultFormatter();

        Data data1 = reader.readData(stream);
        Data data2 = reader.readData(stream);
        Data data3 = reader.readData(stream);
        
        if (data1.valid()) {
            return formatter.format(VALID, data1);
        } else if (data2.valid()) {
            return formatter.format(INVALID, data1) + formatter.format(VALID, data2);
        } else if (data3.valid()) {
            return formatter.format(INVALID, data1) + formatter.format(INVALID, data2) + formatter.format(VALID, data3);
        }
    }
}


не хочется его тестить типа генерируя разные стримы а потом сравнивая результат с заготовленными результатами.

В этом примере отдельно можно оттестить StreamReader, ResultFormatter, можно логику обработки этих data1-data3 вынести в отдельный метод:

public class Statistics {
    public String do(Stream stream) {
        return doInternal(stream, new StreamReader(), new ResultFormatter(), new ResultStringBuilder());
    }

    public String doInternal(Stream stream, StreamReader reader, ResultFormatter formatter, ResultStringBuilder resultBuilder) {
        Data data1 = reader.readData(stream);
        Data data2 = reader.readData(stream);
        Data data3 = reader.readData(stream);
        
        return resultBuilder.getResult(data1, data2, data3);
    }
    
    public static class ResultStringBuilder {
        public String getResult(Data data1, Data data2, Data data3) {
            if (data1.valid()) {
                return formatter.format(VALID, data1);
            } else if (data2.valid()) {
                return formatter.format(INVALID, data1) + formatter.format(VALID, data2);
            } else if (data3.valid()) {
                return formatter.format(INVALID, data1) + formatter.format(INVALID, data2) + formatter.format(VALID, data3);
            }
        }
    }
}


Теперь все замечательно тестируется кроме метода do — как убедиться что он правильно вызвал doInternal?
Re[3]: Тест метода класса Java, вызывающего другие методы
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 17.02.11 09:11
Оценка:
Здравствуйте, 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;
   }
}
Re[4]: Тест метода класса Java, вызывающего другие методы
От: Ikemefula Беларусь http://blogs.rsdn.org/ikemefula
Дата: 18.02.11 12:23
Оценка:
Как то поздно заметил, что речь про java
Re[3]: Тест метода класса Java, вызывающего другие методы
От: -VaS- Россия vaskir.blogspot.com
Дата: 19.03.11 20:55
Оценка:
public interface StreamReader
{
    Data readData(Stream stream);
}

public interface ResultStringBuilder
{
    String getResult(Data data1, Data data2, Data data3);
}

public interface ResultFormatter
{
    public String format(int i, Data data);
}

public class DefaultResultStringBuilder implements ResultStringBuilder
{
    private ResultFormatter _formatter;

    public DefaultResultStringBuilder(ResultFormatter formatter)
    {
        _formatter = formatter;
    }

    public String getResult(Data data1, Data data2, Data data3)
    {
        if (data1.valid())
        {
            return _formatter.format(1, data1);
        }
        else if (data2.valid())
        {
            return _formatter.format(2, data1) + _formatter.format(1, data2);
        }
        else if (data3.valid())
        {
            return _formatter.format(3, data1) + _formatter.format(2, data2) + _formatter.format(2, data3);
        }

        return null;
    }
}

public class Statistics
{
    private StreamReader _reader;
    private ResultStringBuilder _resultStringBuilder;

    public Statistics(StreamReader reader, ResultStringBuilder resultStringBuilder)
    {
        _reader = reader;
        _resultStringBuilder = resultStringBuilder;
    }

    public String doIt(Stream stream)
    {
        Data data1 = _reader.readData(stream);
        Data data2 = _reader.readData(stream);
        Data data3 = _reader.readData(stream);
        return _resultStringBuilder.getResult(data1, data2, data3);
    }
}


Тестируем Statistics:

// create mocks and setup expectations
Statistics statistics = new Statistics(readerMock, resultBuilderMock);
statistics.doIt(testStream);
// verify here that the mocks have been called with proper parameters


Тестируем билдер:
// create mock and setup expectations
DefaultResultStringBuilder builder = new DefaultResultStringBuilder(formatterMock);
String result = builder.getResult(data1, data2, data3);
// test the result value and verify that thу mock has been called properly


Как создавать моки см. здесь
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.