Что вы делаете для тестирования методов, которые создают сложные графы объектов? - PullRequest
1 голос
/ 18 сентября 2009

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

Например, у меня есть класс со свойствами, которые описывают состояние элемента управления, и метод, который генерирует объект WPF PathGeometry, состоящий из нескольких сегментов. Реализация выглядит примерно так:

internal PathGeometry CreateOuterGeometry()
{
    double arcRadius = OuterCoordinates.Radius;
    double sweepAngle = OuterCoordinates.SweepAngle;
    ArcSegment outerArc = new ArcSegment(...);

    LineSegment arcEndToCenter = new LineSegment(...);

    PathFigure fig = new PathFigure();
    // configure figure and add segments...

    PathGeometry outerGeometry = new PathGeometry();
    outerGeometry.Figures.Add(fig);
    return outerGeometry;
}

У меня есть несколько других подобных методов, которые учитывают несколько сотен блоков непокрытого кода, дополнительное покрытие 25%. Первоначально я планировал проверить эти методы, но отверг это мнение. Я все еще новичок в модульном тестировании, и единственный способ, которым я мог придумать, чтобы проверить код, - это несколько методов, подобных этому:

void CreateOuterGeometry_AngleIsSmall_ArcSegmentIsCorrect()
{
    ClassUnderTest classUnderTest = new ClassUnderTest();
    // configure the class under test...
    ArcSegment expectedArc = // generate expected Arc...

    PathGeometry geometry = classUnderTest.CreateOuterGeometry()
    ArcSegment arc = geometry.Figures.Segments[0];

    Assert.AreEqual(expectedArc, arc)
}

Сам тест выглядит хорошо; Я написал бы один для каждого ожидаемого сегмента. Но у меня были некоторые проблемы:

  • Нужны ли мне тесты для проверки "Является ли первый сегмент ArcSegment?" Теоретически тест проверяет это, но не должен ли каждый тест проверять только одну вещь? Это звучит как две вещи.
  • В контроле есть как минимум шесть случаев для расчета и четыре крайних случая; это означает, что для каждого метода мне нужно как минимум десять тестов.
  • Во время разработки я изменил, как различные геометрии генерировались несколько раз. Это заставило бы меня переписать все тесты.

Первая проблема заставила меня задуматься, потому что казалось, что это может увеличить количество тестов. Я подумал, что мне, возможно, придется проверить такие вещи, как "Были ли сегменты х?" и «Является ли сегмент n правильным типом?», но теперь, когда я подумал больше, я вижу, что в методе нет логики ветвления, поэтому мне нужно выполнить эти тесты только один раз. Вторая проблема сделала меня более уверенным, что с тестом будет много усилий. Это кажется неизбежным. Третья проблема состоит из первых двух. Каждый раз, когда я менял способ вычисления геометрии, мне приходилось редактировать примерно 40 тестов, чтобы они соответствовали новой логике. Это также включает добавление или удаление тестов, если сегменты были добавлены или удалены.

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

Ответы [ 6 ]

2 голосов
/ 18 сентября 2009

Использовать инъекцию зависимостей и макеты.

Создайте интерфейсы для ArcSegmentFactory, LineSegmentFactory и т. Д. И передайте фиктивную фабрику вашему классу. Таким образом, вы изолируете логику, характерную для этого объекта (это должно упростить тестирование), и не будут зависеть от логики других ваших объектов.

О том, что проверять: вы должны проверить, что важно . У вас, вероятно, есть график времени, в который вы хотите, чтобы что-то было сделано, и вы, вероятно, не сможете протестировать все до единого. Приоритетные данные, которые необходимо протестировать , и тестирование в порядке приоритета (учитывая, сколько времени потребуется для тестирования). Кроме того, когда вы уже сделали несколько тестов, гораздо проще создавать новые тесты для других вещей, и я не вижу проблемы в создании нескольких тестов для одного класса ...

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

0 голосов
/ 18 сентября 2009

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

Вместо человеческих глаз вы могли бы создать набор ожидаемых изображений (снимок проверенных «хороших» изображений) и создать модульные тесты, которые используют операции рисования для создания того же набора изображений и сравнения результата с сравнение изображений. Это позволило бы вам автоматизировать тестирование графических операций, что, как я понимаю, ваша проблема.

0 голосов
/ 18 сентября 2009

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

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

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

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

0 голосов
/ 18 сентября 2009

В нашем проекте мы используем JUnit для выполнения тестов, которые, строго говоря, не являются юнит-тестами. Например, мы находим, что полезно подключить пустую базу данных и сравнить автоматическую схему, сгенерированную Hibernate (инструмент Object-Relational Mapping) с реальной схемой для нашей тестовой базы данных; это помогает нам выявить множество проблем с неправильным отображением базы данных. Но в целом ... вы должны тестировать только один метод в одном классе в данном методе тестирования. Это не означает, что вы не можете сделать несколько утверждений против него, чтобы исследовать различные свойства объекта.

0 голосов
/ 18 сентября 2009

Вы можете попробовать написать инструмент генерации элементов управления, который генерирует случайные контрольные графики, и протестировать их. Это может привести к некоторым точкам данных, о которых вы могли и не подумать.

0 голосов
/ 18 сентября 2009

Учебным способом для этого было бы перенести всю бизнес-логику в библиотеки или контроллеры, которые вызываются методом из 1 строки в графическом интерфейсе. Таким образом, вы можете модульно протестировать контроллер или библиотеку, не имея дело с GUI.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...