юнит тест. как написать?
От: rsdntchkru  
Дата: 24.03.10 15:53
Оценка:
Не имею опыта с юнит-тестами. Разбираюсь чисто теоретически.
Вот возник вопрос:
Например
Есть некая ф-ция. На входе текст, ф-ция пишeт в файл текст построчно. Файл имеет ограничения по размеру. Как только тeкста больше чем надо, ф-ця урeзает начало (n-ное кол-во строк).

Вот собственно псевдокод:
const int MAX_LINE_SIZE = 100;
const int MIN_DATA_SIZE = MAX_LINE_SIZE*10;
const int MAX_DATA_SIZE = MAX_LINE_SIZE*100;
const int CUT_DATA_LINES = 30;

void DataWriter::WriteLine(const String& aLine)
{
    //cut the input text if bigger than max allowed for one line
    String dataLine = aLine.Left(MAX_LINE_SIZE);
    
    File file;
    file.Open(DATA_FILENAME, frw);
    int size = file.Size();
    
    //if final data size will be bigger than max allowed, 
    //then cut several lines from begining of the file
    if(size + dataLine.Length() > MAX_DATA_SIZE)
    {
        String fileData = file.ReadAll();
        for(int i = 0; i < CUT_DATA_LINES; i++)
        {
            int newLinePos = fileData.Find("\n");
            if(newLinePos <= 0 || newLinePos >= (fileData.Length() - MIN_DATA_SIZE))
                break;
            fileData = fileData.Right(fileData.Length() - newLinePos - 1);
        }
        file.Write(fileData);
    }
    //append new line to the end of the file
    file.Seek(END_OF_FILE);
    file.Write(dataLine);
    file.Write("\n";);
    file.Close();
}


В код вкралась ошибка: в строке "if(newLinePos <= 0 || newLinePos >= (fileData.Length() — MIN_DATA_SIZE))" должно быть "if(newLinePos < 0..."
Иначе если файл начинается с новой линии, то будет расти бесконечно.
Как должен бы быть написан юнит-тест, чтобы можно было выявить такую ошибку и подобные ей такого плана?
Re: юнит тест. как написать?
От: fmiracle  
Дата: 24.03.10 18:23
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

R>В код вкралась ошибка: в строке "if(newLinePos <= 0 || newLinePos >= (fileData.Length() — MIN_DATA_SIZE))" должно быть "if(newLinePos < 0..."

R>Иначе если файл начинается с новой линии, то будет расти бесконечно.
R>Как должен бы быть написан юнит-тест, чтобы можно было выявить такую ошибку и подобные ей такого плана?

Я не буду говорить детально про твой пример, т.к. не вполне трезвый.
Но расскажу про общее правило написания тестов, потому что помню его даже в этом состоянии
(правда ничего нового тут нет, напиано не раз и более умными людьми, чем я. Но ссылок у меня под рукой нет).

Набор юнит-тестов (а пишется всегда не один тест а именно набор, под одному тесту на каждый отдельный случай) на каждую функцию должен покрывать всегда:

1. "Нормальные условия" — в твоем случае это что-то типа записали в файл, пока он еще не настолько большой, что надо что-то особенное делать. И сюдаже попадает (отдельным случаем, что делать, если в файл пишется больше чем его нормальый размер и надо урезать строки)

2. "Граничные условия" — что произойдет, если в файл запишать строку так, что суммарный размер сравняется с максимально-допустимым. Если на 1 байт больше или на 1 байт меньше — что будет? А если в непустой файл записывается строка, которая сама ровно как допустимый размер файла? А если в пустой?
Что будет если записать допустимые, но маловероятные строки, обладающие к тому же каким-то специальными значениями? Например, строка, состоящая из одних переводов строк. Или наоборот — если файл состоит из одних переводов строк.

3. "Совершенно ненормальные условия" — это выход за границы условий. Что будет, если написать в файл NULL или пустую строку. Что будет, если записать в файл неадекватно большую строку. Или если допустимый размер файла меньше, чем строка которую в него пишут. Что будет, если файл недоступен для записи. Возможно, тут должны выбрасываться исключения — ну так все равно надо проверить, что они выбрасываются, и именно те, которые ожидаются.


Описывать такие условия надо всегда. Даже если кажется, что код совершенно не отличается от того, что запишешь ты строку ровно максимально допустимого размера за раз или более маленькую. Кажется так, а на практике — очень может оказаться, что в данном случае поведение будет чуть-чуть другое. Как понять заранее, что есть совершенно нормальный случай, а что граничный? Ну иногда это и логически видно (особенно на чисто счетных задачах), а иногда — как говорится "нутром чую" — приходит с практикой и случаи распознаются где-то в подсознании.

Причем самая первая польза от юнит-тестов, которую ты получишь, это именно то, что задумаешься об обработке граничных или ненормальных случаев. И, соответственно, сможешь их предусмотреть при реализации.
Ну и не надо расстраиваться, если не получилось предусмотреть все сразу — мы не боги. Нашлась неучтеная заранее проблема — дописал новый тест и дополнил набор. Заодно получил опыт на будущее.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re: юнит тест. как написать?
От: fmiracle  
Дата: 24.03.10 18:25
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

R>В код вкралась ошибка: в строке "if(newLinePos <= 0 || newLinePos >= (fileData.Length() — MIN_DATA_SIZE))" должно быть "if(newLinePos < 0..."

R>Иначе если файл начинается с новой линии, то будет расти бесконечно.
R>Как должен бы быть написан юнит-тест, чтобы можно было выявить такую ошибку и подобные ей такого плана?

Я не буду говорить детально про твой пример, т.к. не вполне трезвый.
Но расскажу про общее правило написания тестов, потому что помню его даже в этом состоянии
(правда ничего нового тут нет, напиано не раз и более умными людьми, чем я. Но ссылок у меня под рукой нет).

Набор юнит-тестов (а пишется всегда не один тест а именно набор, под одному тесту на каждый отдельный случай) на каждую функцию должен покрывать всегда:

1. "Нормальные условия" — в твоем случае это что-то типа записали в файл, пока он еще не настолько большой, что надо что-то особенное делать. И сюдаже попадает (отдельным случаем, что делать, если в файл пишется больше чем его нормальый размер и надо урезать строки)

2. "Граничные условия" — что произойдет, если в файл запишать строку так, что суммарный размер сравняется с максимально-допустимым. Если на 1 байт больше или на 1 байт меньше — что будет? А если в непустой файл записывается строка, которая сама ровно как допустимый размер файла? А если в пустой?
Что будет если записать допустимые, но маловероятные строки, обладающие к тому же каким-то специальными значениями? Например, строка, состоящая из одних переводов строк. Или наоборот — если файл состоит из одних переводов строк.

3. "Совершенно ненормальные условия" — это выход за границы условий. Что будет, если написать в файл NULL или пустую строку. Что будет, если записать в файл неадекватно большую строку. Или если допустимый размер файла меньше, чем строка которую в него пишут. Что будет, если файл недоступен для записи. Возможно, тут должны выбрасываться исключения — ну так все равно надо проверить, что они выбрасываются, и именно те, которые ожидаются.


Описывать такие условия надо всегда. Даже если кажется, что код совершенно не отличается от того, что запишешь ты строку ровно максимально допустимого размера за раз или более маленькую. Кажется так, а на практике — очень может оказаться, что в данном случае поведение будет чуть-чуть другое. Как понять заранее, что есть совершенно нормальный случай, а что граничный? Ну иногда это и логически видно (особенно на чисто счетных задачах), а иногда — как говорится "нутром чую" — приходит с практикой и случаи распознаются где-то в подсознании.

Причем самая первая польза от юнит-тестов, которую ты получишь, это именно то, что задумаешься об обработке граничных или ненормальных случаев. И, соответственно, сможешь их предусмотреть при реализации.
Ну и не надо расстраиваться, если не получилось предусмотреть все сразу — мы не боги. Нашлась неучтеная заранее проблема — дописал новый тест и дополнил набор. Заодно получил опыт на будущее.
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Re: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 24.03.10 23:49
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

R>В код вкралась ошибка: в строке "if(newLinePos <= 0 || newLinePos >= (fileData.Length() — MIN_DATA_SIZE))" должно быть "if(newLinePos < 0..."

R>Иначе если файл начинается с новой линии, то будет расти бесконечно.
R>Как должен бы быть написан юнит-тест, чтобы можно было выявить такую ошибку и подобные ей такого плана?

Вы таки вначале разберитесь с теорией. Юнит-тест — это тест одного мелкого фрагмента кода (обычно, одной функции), в условиях минимальной зависимости от внешних факторов (в идеале, функция не имеет побочных эффектов — проверяется, что выходные данные соответствуют тому, что она должна выдавать при заданных входных). Я намеренно не пытаюсь лезть в книги, чтобы не пугать деталями, тем более что чёткой границы может и не быть (тут с год назад был пример, как функцию, которая имеет скрытый входной аргумент в виде текущей даты, протестировать в условиях другой даты — вроде это и юнит-тест, а вроде и не чистый).

В данном случае Вы, например, могли бы выделить наиболее логически громоздкую и пригодную ко внесению ошибок часть в отдельную функцию. Но тут слишком мало кода, чтобы это имело какой-то реальный смысл.

Другой вариант — функциональный тест. В этом случае уже заведомо допускается, что тщательно готовится окружение, а тестирование проверяет работу не отдельной функции, а целого компонента. Вот в этом случае возможно было бы подсунуть, например, на вход файл из сотни пустых строк — и заметить неожиданный эффект — отсутствие сокращения. Но, как и ко всем тестам, тут применим общий метод: проверяется то, что заподозрили. Если Вы при написании кода не заподозрили возможность проблемы с файлом с \n вначале — скорее всего и не напишете соответствующие проверки в тесте.

Чтобы можно было что-то тестировать в приведённом примере, можно было бы разделить операции открытия файла — их уносить во внешнюю функцию — и добавления в уже открытый файл. В тестах можно подкладывать вместо реального файла объект, который себя ведёт точно так же, но вместо записи на диск сохраняет в память результат или его ключевые признаки (и позволяет их затем потом получать). Для C++ можно произвести потомка от ostream с нужными свойствами (для такого метода как в этом коде может сгодиться даже ostringstream, но я бы так грубо не обращался в реальных ситуациях).
The God is real, unless declared integer.
Re[2]: юнит тест. как написать?
От: rsdntchkru  
Дата: 25.03.10 11:48
Оценка:
Здравствуйте, netch80, Вы писали:

>Но, как и ко всем тестам, тут применим общий метод: проверяется то, что заподозрили. Если Вы при написании кода не заподозрили возможность проблемы с файлом с \n вначале — скорее всего и не напишете соответствующие проверки в тесте.


это то и смущает, заподозрил, ошибки бы не было .
Т.е. в данном случае тест-код будет полезен только, если в этот код когда-либо в будущем залезет другой разработчик и будет править именно что-то вокруг этого "иф". Если же он будет раширять функционал и тоже что-нибудь "не заподозрит", то тeст-код (также им дописанный для раширeнного ф-ционала) опять будет бесполезен . Так?
Re: юнит тест. как написать?
От: Mystic Украина http://mystic2000.newmail.ru
Дата: 25.03.10 12:08
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

R>Иначе если файл начинается с новой линии, то будет расти бесконечно.

R>Как должен бы быть написан юнит-тест, чтобы можно было выявить такую ошибку и подобные ей такого плана?

Я бы скорее всего написал тест, который бы записывал в файл случайные строки (в том числе и пустые). А в конце проверил бы размер файла и последние N строк. Тест скорее функциональный. Граничный размер файла выбрал бы небольшим, чтобы переполнение встречалось очень часто. Строки бы вначале чередовались (например, вначале очень длинные, я потом одни пустые до переполнения).
Re[2]: юнит тест. как написать?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 25.03.10 17:06
Оценка: 4 (1) +2 -1
Здравствуйте, Mystic, Вы писали:

M>Я бы скорее всего написал тест, который бы записывал в файл случайные строки (в том числе и пустые)


Любые случайные алгоритмы в тестах — зло.
... << RSDN@Home 1.2.0 alpha 4 rev. 1466 on Windows 7 6.1.7600.0>>
AVK Blog
Re[3]: юнит тест. как написать?
От: Кэр  
Дата: 26.03.10 02:44
Оценка: +1
Здравствуйте, AndrewVK, Вы писали:

M>>Я бы скорее всего написал тест, который бы записывал в файл случайные строки (в том числе и пустые)


AVK>Любые случайные алгоритмы в тестах — зло.


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

Но при этом — главный критерий рандомного теста — воспроизводимость! Поэтому все рандомизаторы в тесте инициируются от одного сида. Если сид был передан в параметрах теста — то используется конкретное значение. Иначе генерится рандомное и сразу пишется в логи, чтобы было понятно, какие параметры запуска.
Re[3]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 08:07
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

>>Но, как и ко всем тестам, тут применим общий метод: проверяется то, что заподозрили. Если Вы при написании кода не заподозрили возможность проблемы с файлом с \n вначале — скорее всего и не напишете соответствующие проверки в тесте.


R>это то и смущает, заподозрил, ошибки бы не было :-) .

R>Т.е. в данном случае тест-код будет полезен только, если в этот код когда-либо в будущем залезет другой разработчик и будет править именно что-то вокруг этого "иф". Если же он будет раширять функционал и тоже что-нибудь "не заподозрит", то тeст-код (также им дописанный для раширeнного ф-ционала) опять будет бесполезен :???: . Так?

В общем да: в этом и проблема тестов — они проверяют только те случаи, которые пришли в голову их авторам, или те, которые можно генерировать случайным образом, параметризуя входные данные (но при этом заведомо зная метод контроля результата и имея возможность применить этот метод).

Если делается тотальный контроль, зная метод реализации — то это уже не тестирование, а верификация.

Умение разработчика состоит в том числе и в определении, какие могут быть проблемы и какие писать тесты.
The God is real, unless declared integer.
Re[3]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 08:13
Оценка: -1
Здравствуйте, AndrewVK, Вы писали:

M>>Я бы скорее всего написал тест, который бы записывал в файл случайные строки (в том числе и пустые)


AVK>Любые случайные алгоритмы в тестах — зло.


Что такое "случайные алгоритмы"? Это данные случайные, а алгоритмы скорее стохастические;) потому что никто не выбирает алгоритм наобум.

С утверждением про "зло" не могу согласиться. Такие тесты — отдельный подкласс, допустимый и необходимый в некоторых случаях. Они не заменяют остальные виды тестов (на краевые случаи, регрессионные на уже проверенные входные данные...), а дополняют их. Для них должна быть соответствующим образом заточена тестовая инфраструктура (в первую очередь, в случае невыполнения должны быть известны входные данные). Но если у кого-то эта инфраструктура недостаточна, это не повод ругать сами тесты.
The God is real, unless declared integer.
Re[4]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 08:15
Оценка:
Здравствуйте, Кэр, Вы писали:

Кэр>Ну как-то слишком критично. Когда случайный алгоритм — это не основное средство тестирования, то получается порой весьма хорошо. Также неплох тест, где сама структура теста дает детерменированный результат, а рандомные только некоторые детали.


Кстати, последнее особо полезно для контрольных тестов функционирования работающей системы (да, это уже никак не юнит;))

Кэр>Но при этом — главный критерий рандомного теста — воспроизводимость! Поэтому все рандомизаторы в тесте инициируются от одного сида. Если сид был передан в параметрах теста — то используется конкретное значение. Иначе генерится рандомное и сразу пишется в логи, чтобы было понятно, какие параметры запуска.


Это всё необязательно, если для провалившегося случая полностью известны входные данные и (если нужно) входное состояние.
The God is real, unless declared integer.
Re[4]: юнит тест. как написать?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 26.03.10 09:36
Оценка:
Здравствуйте, netch80, Вы писали:

N>Для них должна быть соответствующим образом заточена тестовая инфраструктура


При чем тут инфраструктура? Если тестовые данные случайны, то и результат выполнения теста тоже будет случайным.
... << RSDN@Home 1.2.0 alpha 4 rev. 1466 on Windows 7 6.1.7600.0>>
AVK Blog
Re[5]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 09:50
Оценка: +2
Здравствуйте, AndrewVK, Вы писали:

AVK>Здравствуйте, netch80, Вы писали:

N>>Для них должна быть соответствующим образом заточена тестовая инфраструктура
AVK>При чем тут инфраструктура? Если тестовые данные случайны, то и результат выполнения теста тоже будет случайным.

Ты невнимательно читаешь. Случайность результата несущественна, если мы в состоянии проверить иным путём, насколько этот результат адекватен.

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

Другой пример — мы прямо сейчас боремся с клинами I/O шедулера. Стресс-тесты такого не дают, а вот обычный писатель лога, который закрывает выходной файл через минуту после первой записи — его успешно заклинивает за 20 минут. (То, что это не юнит-тест, тут ни при чём)
The God is real, unless declared integer.
Re[6]: юнит тест. как написать?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 26.03.10 10:06
Оценка:
Здравствуйте, netch80, Вы писали:

N>Ты невнимательно читаешь. Случайность результата несущественна, если мы в состоянии проверить иным путём, насколько этот результат адекватен.


Это ты невнимательно читаешь:

Я бы скорее всего написал тест, который бы записывал в файл случайные строки

... << RSDN@Home 1.2.0 alpha 4 rev. 1466 on Windows 7 6.1.7600.0>>
AVK Blog
Re[7]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 10:13
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Здравствуйте, netch80, Вы писали:


N>>Ты невнимательно читаешь. Случайность результата несущественна, если мы в состоянии проверить иным путём, насколько этот результат адекватен.


AVK>Это ты невнимательно читаешь:

AVK>

AVK>Я бы скорее всего написал тест, который бы записывал в файл случайные строки


Это не противоречит моему утверждению, скорее наоборот. Мы можем 1) проверить соответствие записанного отосланному, 2) проверить отсутствие зависа на этой процедуре.
The God is real, unless declared integer.
Re[5]: юнит тест. как написать?
От: achmed Удмуртия https://www.linkedin.com/in/nail-achmedzhanov-9907188/
Дата: 26.03.10 10:42
Оценка: +1
Здравствуйте, AndrewVK, Вы писали:

AVK>Здравствуйте, netch80, Вы писали:


N>>Для них должна быть соответствующим образом заточена тестовая инфраструктура


AVK>При чем тут инфраструктура? Если тестовые данные случайны, то и результат выполнения теста тоже будет случайным.


Тестировать на случайных значениях имеет смысл различные инварианты, такой подход используется в QuickCheck.
PS незаконченный перевод введения здесь.
Re[8]: юнит тест. как написать?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 26.03.10 15:01
Оценка:
Здравствуйте, netch80, Вы писали:

N>Это не противоречит моему утверждению, скорее наоборот.


Если вспомить пример топикстартера. то очень даже противоречит. Нет, я понимаю, что можно придумать тест, который при случайных данных не будет давать случайный результат, но это исключение из правила. И цепляться к таким моментам — это только если развести флейм охота на пустом месте.
... << RSDN@Home 1.2.0 alpha 4 rev. 1466 on Windows 7 6.1.7600.0>>
AVK Blog
Re[6]: юнит тест. как написать?
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 26.03.10 15:01
Оценка:
Здравствуйте, achmed, Вы писали:

A>Тестировать на случайных значениях имеет смысл различные инварианты, такой подход используется в QuickCheck.


Это совсем другая ситуация.
... << RSDN@Home 1.2.0 alpha 4 rev. 1466 on Windows 7 6.1.7600.0>>
AVK Blog
Re[9]: юнит тест. как написать?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 26.03.10 16:53
Оценка:
Здравствуйте, AndrewVK, Вы писали:

N>>Это не противоречит моему утверждению, скорее наоборот.


AVK>Если вспомить пример топикстартера. то очень даже противоречит. Нет, я понимаю, что можно придумать тест, который при случайных данных не будет давать случайный результат, но это исключение из правила.


Если вспомнить пример топикстартера, то тест "сгенерировали известное содержание на вход, вычислили требуемое действие и сравнили содержание выходных файлов с предполагаемым" будет работать при любых случайных данных.

AVK> И цепляться к таким моментам — это только если развести флейм охота на пустом месте.


Ну вот кое-кто и разводит.
The God is real, unless declared integer.
Re: юнит тест. как написать?
От: ZevS Россия  
Дата: 29.03.10 12:26
Оценка:
Здравствуйте, rsdntchkru, Вы писали:

На вскидку:

— разделить код проверки условя и обрубания файла в отдельные функции,
— выделить операции с файлом в абстракцию (интерфейс), который передавать в фукнцию,
— в тестах передавать мок файла, и по нему проверять работу функции обрубания.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.