Как эффективно протестировать анализатор плоских файлов фиксированной длины с помощью MSpec? - PullRequest
5 голосов
/ 16 августа 2011

У меня есть подпись этого метода: List<ITMData> Parse(string[] lines)

ITMData имеет 35 свойств.

Как бы вы эффективно протестировали бы такой парсер?

Вопросы:

  • Должен ли я загрузить весь файл (могу ли я использовать System.IO)?
  • Должен ли я поместить строку из файла в строковую константу?
  • Должен ли я проверить одну или несколько строк
  • Должен ли я проверять каждое свойство ITMData или я должен тестировать весь объект?
  • А как насчет названия моего теста?

РЕДАКТИРОВАТЬ

Я изменил подпись метода на ITMData Parse(string line).

Тестовый код:

[Subject(typeof(ITMFileParser))]
public class When_parsing_from_index_59_to_79
{
    private const string Line = ".........";
    private static ITMFileParser _parser;
    private static ITMData _data;

    private Establish context = () => { _parser = new ITMFileParser(); };

    private Because of = () => { _data = _parser.Parse(Line); };

    private It should_get_fldName = () => _data.FldName.ShouldBeEqualIgnoringCase("HUMMELDUMM");
}

РЕДАКТИРОВАТЬ 2

Я все еще не уверен, стоит ли проверять только одно свойство на класс . По моему мнению, это позволяет мне дать больше информации для спецификации, а именно, что, когда я анализирую одну строку из индекса 59 в индекс 79, я получаю fldName. Если я проверю все свойства в пределах одного класса, я потеряю эту информацию . Я завышаю свои тесты?

Мои тесты теперь выглядят так:

[Subject(typeof(ITMFileParser))]
public class When_parsing_single_line_from_ITM_file
{
    const string Line = ""

    static ITMFileParser _parser;
    static ITMData _data;

    Establish context = () => { _parser = new ITMFileParser(); };

    private Because of = () => { _data = _parser.Parse(Line); };

    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    It should_get_fld??? = () => _data.Fld???.ShouldEqual(???);
    ...

}

Ответы [ 4 ]

4 голосов
/ 19 августа 2011

Должен ли я загрузить весь файл (могу ли я использовать System.IO)?

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

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

Должен ли я поместить строку из файла в строковую константу?

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

Должен ли я проверить одну или несколько строк?

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

Почти всегда стоит протестировать вырожденный случай, который в этом случае будет пустым массивом строк (ноль строк). И, вероятно, стоит иметь хотя бы один тест, содержащий более одной строки, просто чтобы вы могли убедиться, что в вашей итерации нет глупых ошибок.

Однако, если ваш вывод один на один с вашим вводом, то у вас действительно есть другой метод, желающий выйти - у вас должен быть метод ParseSingleLine. Тогда ваш Parse будет не более чем итерацией строк и вызовом ParseSingleLine. Вам все еще нужно несколько тестов для Parse, но большая часть ваших тестов будет сосредоточена на ParseSingleLine.

2 голосов
/ 31 августа 2011

Вот что я обычно делал бы, если бы столкнулся с такой проблемой:

За один короткий отказ от ответственности: думаю, я бы больше отказался от "тестирования интеграции" или "тестирования парсера в целом""маршрут, а не тестирование отдельных линий.В прошлом я не раз сталкивался с ситуацией, когда в мои тесты просачивалось множество деталей реализации, и я часто менял тесты, когда менял детали реализации.Типичный случай завышения, я думаю; - /

  1. Я бы не включил загрузку файлов в парсер.Как предложил @mquander, я бы предпочел вместо этого использовать TextReader или IEnumerable в качестве входного параметра.Это приведет к более быстрым тестам, так как вы сможете указать ввод парсера в памяти и не должны касаться файловой системы.
  2. Я не большой поклонник тестовых данных, поэтому в большинстве случаев я использую встроенные ресурсы и ResourceManager для загрузки тестовых данных непосредственно из сборки спецификации через assembly.GetManifestResource ().У меня обычно есть несколько методов расширения в моем решении для оптимизации чтения ресурсов (что-то вроде TextReader TextResource.Load ("NAME_OF_SOME_RESOURCE")).
  3. Относительно MSpec: я использую один класс на файл для разбора,Для каждого свойства, которое проверяется в разобранном результате, у меня есть отдельное (It) утверждение.Обычно это один лайнер, поэтому дополнительное количество кода не так уж велико.С точки зрения документации и диагностики, imho это огромный плюс, так как, когда свойство не анализируется правильно, вы можете непосредственно увидеть, какое утверждение не удалось, без необходимости заглядывать в источник или искать номера строк.Он также появляется в вашем файле результатов MSpec.Кроме того, вы не скрываете другие ошибочные утверждения (ситуация, когда вы исправляете одно утверждение только для того, чтобы спецификация провалилась на следующей строке со следующим утверждением).Это, конечно, заставляет вас больше думать о формулировке, которую вы используете в своих спецификациях, но для меня это также огромный плюс, так как я сторонник идеи, что язык формирует мышление.Другими словами, если вы не понимаете, как фракировать название вашего утверждения, вероятно, есть что-то подозрительное либо в вашей спецификации, либо в вашей реализации.
  4. Относительно подписи вашего метода для синтаксического анализатора: я бы не возвращал конкретный тип, такой как List или массив, и я бы также предложил не возвращать изменяемый тип List .То, что вы в основном говорите здесь: «Эй, вы можете разобраться с результатами анализа после того, как я закончу», что в большинстве случаев, вероятно, то, что вы не хотите.Я бы предложил вместо этого возвратить IEnumerable (или ICollection , если вам ДЕЙСТВИТЕЛЬНО нужно изменить его впоследствии)
1 голос
/ 16 августа 2011

Обычно я стараюсь учитывать общие сценарии успеха и неудачи, а также крайние случаи.Требования также полезны для настройки соответствующих вариантов использования.Рассмотрим Pex для перечисления различных сценариев.

0 голосов
/ 24 августа 2011

Относительно ваших новых вопросов:

Должен ли я проверять каждое свойство ITMData или я должен тестировать весь объект?

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

А как насчет именования моего теста?

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

public void Check_All_Properties_Parsed_Correctly(){.....}

public void Exception_Thrown_If_Lines_Is_Null(){.....}

public void Exception_Thrown_If_Lines_Is_Wrong_Length(){.....}

Итак, другими словами, тестирование на точное поведение, которое вы считаете «правильным» для синтаксического анализатора.Как только это будет сделано, вы почувствуете себя намного легче при внесении изменений в код синтаксического анализатора, потому что у вас будет всеобъемлющий набор тестов, чтобы убедиться, что вы ничего не сломали.Не забывайте регулярно проверять и обновлять тесты при внесении изменений!Существует довольно хорошее руководство по модульному тестированию и разработке через тестирование на MSDN .

В общем, я думаю, что вы можете найти ответы на большинство ваших вопросов, немного погуглив.Есть также несколько превосходных книг по разработке через тестирование, в которых рассказывается не только о как TDD, но и о почему .Если вы относительно независимы от языка программирования, я бы порекомендовал Кента Бека "Разработка через тестирование на примере , иначе что-то вроде Разработка через тестирование в Microsoft .NET .Это должно очень быстро привести вас на правильный путь.

РЕДАКТИРОВАТЬ:

Я завышаю свои тесты?

На мой взгляд, да.В частности, я не согласен с вашей следующей строкой:

Если я проверю все свойства в одном классе, я потеряю эту информацию.

Каким образом вы теряете информацию?Допустим, есть 2 способа выполнить этот тест, кроме создания нового класса для каждого теста:

  1. Иметь разные методы для каждого свойства.Ваши методы тестирования могут называться CheckPropertyX, CheckPropertyY и т. Д. Когда вы запустите свои тесты, вы увидите, какие именно поля были пройдены, а какие - нет.Это явно соответствует вашим требованиям, хотя я бы сказал, что это все еще излишне.Я бы выбрал вариант 2:
  2. У меня есть несколько разных методов, каждый из которых проверяет один конкретный аспект.Это то, что я изначально рекомендовал, и я думаю, что вы имеете в виду.Когда один из тестов не пройден, вы получите информацию только о первой неудачной вещи для каждого метода, но если вы правильно закодировали свой Assert, вы будете знать точно , какое свойство неверно.Рассмотрим следующий код:

Assert.AreEqual("test1", myObject.PropertyX, "Property X was incorrectly parsed"); Assert.AreEqual("test2", myObject.PropertyY, "Property Y was incorrectly parsed");

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

...