2009-03-05 7 views
15

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

public class Hole : INotifyPropertyChanged 
{ 
    #region Field Definitions 
    private double _AbsX; 
    private double _AbsY; 
    private double _CanvasX { get; set; } 
    private double _CanvasY { get; set; } 
    private bool _Visible; 
    private double _HoleDia = 20; 
    private HoleTypes _HoleType; 
    private int _HoleNumber; 
    private double _StrokeThickness = 1; 
    private Brush _StrokeColor = new SolidColorBrush(Colors.Black); 
    private HolePattern _ParentPattern; 
    #endregion 

    public enum HoleTypes { Drilled, Tapped, CounterBored, CounterSunk }; 
    public Ellipse HoleEntity = new Ellipse(); 
    public Ellipse HoleDecorator = new Ellipse(); 
    public TextBlock HoleLabel = new TextBlock(); 

    private static DoubleCollection HiddenLinePattern = 
       new DoubleCollection(new double[] { 5, 5 }); 

    public int HoleNumber 
    { 
     get 
     { 
      return _HoleNumber; 
     } 
     set 
     { 
      _HoleNumber = value; 
      HoleLabel.Text = value.ToString(); 
      NotifyPropertyChanged("HoleNumber"); 
     } 
    } 
    public double HoleLabelX { get; set; } 
    public double HoleLabelY { get; set; } 
    public string AbsXDisplay { get; set; } 
    public string AbsYDisplay { get; set; } 

    public event PropertyChangedEventHandler PropertyChanged; 
    //public event MouseEventHandler MouseActivity; 

    // Constructor 
    public Hole() 
    { 
     //_HoleDia = 20.0; 
     _Visible = true; 
     //this.ParentPattern = WhoIsTheParent; 
     HoleEntity.Tag = this; 
     HoleEntity.Width = _HoleDia; 
     HoleEntity.Height = _HoleDia; 

     HoleDecorator.Tag = this; 
     HoleDecorator.Width = 0; 
     HoleDecorator.Height = 0; 


     //HoleLabel.Text = x.ToString(); 
     HoleLabel.TextAlignment = TextAlignment.Center; 
     HoleLabel.Foreground = new SolidColorBrush(Colors.White); 
     HoleLabel.FontSize = 12; 

     this.StrokeThickness = _StrokeThickness; 
     this.StrokeColor = _StrokeColor; 
     //HoleEntity.Stroke = Brushes.Black; 
     //HoleDecorator.Stroke = HoleEntity.Stroke; 
     //HoleDecorator.StrokeThickness = HoleEntity.StrokeThickness; 
     //HiddenLinePattern=DoubleCollection(new double[]{5, 5}); 
    } 

    public void NotifyPropertyChanged(String info) 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, 
         new PropertyChangedEventArgs(info)); 
     } 
    } 

    #region Properties 
    public HolePattern ParentPattern 
    { 
     get 
     { 
      return _ParentPattern; 
     } 
     set 
     { 
      _ParentPattern = value; 
     } 
    } 

    public bool Visible 
    { 
     get { return _Visible; } 
     set 
     { 
      _Visible = value; 
      HoleEntity.Visibility = value ? 
      Visibility.Visible : 
      Visibility.Collapsed; 
      HoleDecorator.Visibility = HoleEntity.Visibility; 
      SetCoordDisplayValues(); 
      NotifyPropertyChanged("Visible"); 
     } 
    } 

    public double AbsX 
    { 
     get { return _AbsX; } 
     set 
     { 
      _AbsX = value; 
      SetCoordDisplayValues(); 
      NotifyPropertyChanged("AbsX"); 
     } 
    } 

    public double AbsY 
    { 
     get { return _AbsY; } 
     set 
     { 
      _AbsY = value; 
      SetCoordDisplayValues(); 
      NotifyPropertyChanged("AbsY"); 
     } 
    } 

    private void SetCoordDisplayValues() 
    { 
     AbsXDisplay = HoleEntity.Visibility == 
     Visibility.Visible ? String.Format("{0:f4}", _AbsX) : ""; 
     AbsYDisplay = HoleEntity.Visibility == 
     Visibility.Visible ? String.Format("{0:f4}", _AbsY) : ""; 
     NotifyPropertyChanged("AbsXDisplay"); 
     NotifyPropertyChanged("AbsYDisplay"); 
    } 

    public double CanvasX 
    { 
     get { return _CanvasX; } 
     set 
     { 
      if (value == _CanvasX) { return; } 
      _CanvasX = value; 
      UpdateEntities(); 
      NotifyPropertyChanged("CanvasX"); 
     } 
    } 

    public double CanvasY 
    { 
     get { return _CanvasY; } 
     set 
     { 
      if (value == _CanvasY) { return; } 
      _CanvasY = value; 
      UpdateEntities(); 
      NotifyPropertyChanged("CanvasY"); 
     } 
    } 

    public HoleTypes HoleType 
    { 
     get { return _HoleType; } 
     set 
     { 
      if (value != _HoleType) 
      { 
       _HoleType = value; 
       UpdateHoleType(); 
       NotifyPropertyChanged("HoleType"); 
      } 
     } 
    } 

    public double HoleDia 
    { 
     get { return _HoleDia; } 
     set 
     { 
      if (value != _HoleDia) 
      { 
       _HoleDia = value; 
       HoleEntity.Width = value; 
       HoleEntity.Height = value; 
       UpdateHoleType(); 
       NotifyPropertyChanged("HoleDia"); 
      } 
     } 
    } 

    public double StrokeThickness 
    { 
     get { return _StrokeThickness; } 
     //Setting this StrokeThickness will also set Decorator 
     set 
     { 
      _StrokeThickness = value; 
      this.HoleEntity.StrokeThickness = value; 
      this.HoleDecorator.StrokeThickness = value; 
      NotifyPropertyChanged("StrokeThickness"); 
     } 
    } 

    public Brush StrokeColor 
    { 
     get { return _StrokeColor; } 
     //Setting this StrokeThickness will also set Decorator 
     set 
     { 
      _StrokeColor = value; 
      this.HoleEntity.Stroke = value; 
      this.HoleDecorator.Stroke = value; 
      NotifyPropertyChanged("StrokeColor"); 
     } 
    } 

    #endregion 

    #region Methods 

    private void UpdateEntities() 
    { 
     //-- Update Margins for graph positioning 
     HoleEntity.Margin = new Thickness 
     (CanvasX - HoleDia/2, CanvasY - HoleDia/2, 0, 0); 
     HoleDecorator.Margin = new Thickness 
     (CanvasX - HoleDecorator.Width/2, 
     CanvasY - HoleDecorator.Width/2, 0, 0); 
     HoleLabel.Margin = new Thickness 
     ((CanvasX * 1.0) - HoleLabel.FontSize * .3, 
     (CanvasY * 1.0) - HoleLabel.FontSize * .6, 0, 0); 
    } 

    private void UpdateHoleType() 
    { 
     switch (this.HoleType) 
     { 
      case HoleTypes.Drilled: //Drilled only 
       HoleDecorator.Visibility = Visibility.Collapsed; 
       break; 
      case HoleTypes.Tapped: // Drilled & Tapped 
       HoleDecorator.Visibility = (this.Visible == true) ? 
       Visibility.Visible : Visibility.Collapsed; 
       HoleDecorator.Width = HoleEntity.Width * 1.2; 
       HoleDecorator.Height = HoleDecorator.Width; 
       HoleDecorator.StrokeDashArray = 
       LinePatterns.HiddenLinePattern(1); 
       break; 
      case HoleTypes.CounterBored: // Drilled & CounterBored 
       HoleDecorator.Visibility = (this.Visible == true) ? 
       Visibility.Visible : Visibility.Collapsed; 
       HoleDecorator.Width = HoleEntity.Width * 1.5; 
       HoleDecorator.Height = HoleDecorator.Width; 
       HoleDecorator.StrokeDashArray = null; 
       break; 
      case HoleTypes.CounterSunk: // Drilled & CounterSunk 
       HoleDecorator.Visibility = (this.Visible == true) ? 
       Visibility.Visible : Visibility.Collapsed; 
       HoleDecorator.Width = HoleEntity.Width * 1.8; 
       HoleDecorator.Height = HoleDecorator.Width; 
       HoleDecorator.StrokeDashArray = null; 
       break; 
     } 
     UpdateEntities(); 
    } 

    #endregion 

} 

ответ

1

Вот пример. Имейте в виде, ваш пример кода не хватает определений для ряда зависимостей:

[TestFixture()] 
public class TestHole 
{ 

    private Hole _unitUnderTest; 

    [SetUp()] 
    public void SetUp() 
    { 
     _unitUnderTest = new Hole(); 
    } 

    [TearDown()] 
    public void TearDown() 
    { 
     _unitUnderTest = null; 
    } 

    [Test] 
    public void TestConstructorHole() 
    { 
     Hole testHole = new Hole(); 
     Assert.IsNotNull(testHole, "Constructor of type, Hole failed to create instance."); 
    } 

    [Test] 
    public void TestNotifyPropertyChanged() 
    { 
     string info = null; 
     _unitUnderTest.NotifyPropertyChanged(info); 
    } 
} 

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

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

4

Вы не можете проверить этот код надлежащим образом, если не указано иное. «Тестирование» обычно означает, что программное обеспечение работает так, как было разработано.

EDIT: Это действительно не ответ «cop out». Раньше я работал в качестве тестера, и я могу сказать, что почти все тестовые примеры, которые я написал, были получены прямо из спецификации программного обеспечения.

+0

Ударьте одно место, на котором я работал, где тесты были созданы разработчиком в соответствии с идеей разработчика спецификации. Я всегда чувствовал, что обманываю себя, делая свои собственные тесты. –

+0

Создание собственных тестов не обманывает. Они говорят, что вы поняли спецификацию и смогли разбить ее на компоненты, которые полностью соответствуют потребностям программного обеспечения. – flq

0

один пример,

для

общественных HoleTypes HoleType

тест/проверить нуль в наборе

6

Unit Test Пример:

  • Убедитесь, что PropertyChanged события запускается с правильными аргументами событий. Используйте отражение в тесте для повторения всех свойств, задающих значения и проверяющих событие.

Обычно это выполняется с помощью испытательной основы, такой как NUnit.

(Kind смешные причины вы заметите, что ParentPattern свойство не срабатывает событие.)

+0

Тестирование, использующее отражение, не так хорошо. Для меня они намекают на некоторые проблемы с дизайном. Кроме того, такие тесты могут сильно нарушаться и неясны. –

+0

Я не специалист по тестированию, но я все еще не могу найти хороший способ автоматизировать с помощью модульных тестов, запускается ли событие. –

+0

Reflection позволяет забыть об этом тесте после его кодирования. В этом примере мы тестировали, что событие запускается, когда свойство изменяется на этом объекте. Подумал ли новый разработчик вернуться к тесту, когда будет добавлено новое свойство? Является ли тест еще зеленым, если он не тестирует все свойства? – Gord

2

Тестирование не только инженерно - это искусство. Что-то, что требует от вас чтения. Я не уверен, что мы сможем научить вас через этот единственный вопрос, что вы хотите/должны/должны/должны/должны знать. Чтобы начать, вот несколько вещей, которые вы можете проверить.

  • Unit (интерфейсы работы, как и ожидалось)
  • интеграции (компоненты ведут между собой)
  • Юзабилити (клиенты удовлетворены)
  • Функциональная (функция завершена)

Определить набор критерии для каждого (метрики) и начать тестирование.

+0

Это не искусство. Это может быть художественно, но не искусство. – OscarRyz

+0

Я смотрю на инженерство как на безжизненную форму искусства. – dirkgently

+0

Ну, это может быть причиной. В моем случае я думаю, что у искусства нет другой цели, кроме «воссоздания» человека. Если ваш код (или что-то еще) основной целью не является чем-то, то это не искусство (для меня). Например, тестирование первой цели - это проверка программного обеспечения. :) :) Мир. – OscarRyz

1

Вид в стороне, кажется, что большинство этого класса не нужно тестировать (кроме Gord's answer), если класс был написан другим способом. Например, вы смешиваете информацию о модели (дырочный образ и т. Д.) С информацией о представлении (толщиной). Кроме того, я думаю, что вам не хватает точки WPF и привязки/триггеров. UpdateHoleType() Должен быть выражен в файле .xaml как набор DataTriggers, а также с UpdateEntities() и большинством других свойств, которые у вас есть.

+0

+1 для указания на слияние информации о «модели» и «просмотре». (Я тоже думал об этом, но не в моде MVC.) – strager

2

В модульном тестировании вы просто проверяете свои «видимые» методы/свойства, а не частные.

Так, например, в своем коде вы можете добавить следующий тест: «Ну то будет очевидным»

hole.Visible = false; 

Debug.Assert("".Equals(hole.AbsXDisplay)); 
Debug.Assert("".Equals(hole.AbsYDisplay)); 

Вы могли бы подумать но через несколько недель вы можете забыть об этом. И если какая-то часть вашего кода зависит от значения AbsXDisplay (что является общедоступным атрибутом), и по какой-то причине после того, как вы установили свойство в false, оно больше не является «", а «пустым» или «NotSet», тест не удастся, и вы будете уведомлены об этом немедленно.

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

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

Это был простой пример того, что вы можете сделать, чтобы проверить свой код.

Надеюсь, это поможет вам дать вам представление о том, что такое тест.

Как и другие, предложите протестировать рамки.

+0

Существует ценность при тестировании частных методов. «Стеклянная коробка» - «черный ящик». Часто вы можете написать более узкое, четкое, более простое, более показательное тестирование, проверяя конфиденциальность, а не публика. Тесты стеклопакетов должны быть изменены, когда частные методы меняются, но это часть обычного рефакторинга. – Schwern

-1

Ну, история начинается с теории.

Это то, что я сделал.

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

Проведите несколько месяцев, чтобы легко усвоить все образцы.

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

После этой подготовки тестирование будет таким же простым, как и любая другая технология программирования.

+0

Все, кроме простейших рефакторингов REQUIRE! И только в том случае, если вы изучите шаблоны в качестве упражнений, они будут ограничены языками OO. Этот порядок ужасен. Тестирование, рефакторинг и модели следует изучать параллельно с акцентом на тестирование. OO находится в совершенно другом наборе. – Schwern

+0

@Schwern Вы абсолютно правы. Я говорил строго о том, как я научился тестировать. Проблема заключается в загрузке. С чего начать? Что узнать поначалу? Вы должны начать с чего-то, не так ли? –

4

Я расскажу вам о великой тайне тестирования.

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

if(1 + 1 == 2) { 
    print "ok - 1 plus 1 equals 2\n"; 
} 
else { 
    print "not ok\n"; 
} 

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

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

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

+1

Немного чересчур бессвязно, но все же хороший ответ. – strager

0

Мы должны проверить это, не так ли?

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

Итак, как протестировать его?

На уровне класса вам необходимо будет написать модульные тесты. Существует несколько структурных модулей тестирования. Я предпочитаю NUnit.

Для чего я тестирую?

Вы тестируете, что все ведет себя так, как вы ожидаете, что оно будет себя вести. Если вы даете метод X, то вы ожидаете возвращения Y. В ответе Gord's он предложил проверить, что ваше мероприятие действительно срабатывает. Это было бы хорошим испытанием.

Книга, Agile Principles, Patterns, and Practices in C# от дяди Боба, действительно помогла мне понять, что и как тестировать.

1

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

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

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

Только private void UpdateHoleType(){...} содержит любую логику, которая, по-видимому, является визуально ориентированной логикой, всегда сложнее всего тестировать. Написание тестов очень просто. Ниже приведен пример пробуренного отверстия.

[Test] 
public void testDrilledHole() 
{ 
    Hole hole = new Hole(); 
    hole.HoleType = HoleTypes.Drilled; 
    Assert.AreEqual(Visibility.Collapsed, hole.HoleDecorator.Visibility); 
} 

Если вы посмотрите на него, вы почти не считаете его стоящим. Тест тривиальный и очевидный. Атрибут [Test] объявляет метод тестом, а метод Assert.AreEquals() генерирует исключение, если предоставленные значения не равны. Фактическая конструкция может варьироваться в зависимости от используемой тестовой структуры, но все они одинаково просты.

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

Мощность модульных испытаний в сочетании со всеми этими тестами. Теперь, если вы что-то изменили в классе, есть ряд тестов, проверяющих, произошло ли изменение, которое нарушает одно из определений поведения, которое вы определили в своем тесте. Это позволяет работать с большим проектом, изменять и внедрять новые функции, в то время как тесты сохраняют уже закодированные функции.

1

Тестирование поможет, если вам необходимо внести изменения.

В соответствии с Feathers (Перья, Working Effectively with Legacy Code, стр. 3) существуют четыре причины изменений:

  • Добавление функции
  • исправление ошибки
  • совершенствования структуры
  • использование Оптимизация ресурсов

Когда есть необходимость в изменениях, вы хотите быть уверены, что не сломаете anythi нг. Точнее: вы не хотите нарушать какое-либо поведение (Хант, Томас, Pragmatic Unit Testing in C# with NUnit, стр. 31).

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

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

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

Как получить положительный опыт тестирования модулей? Будьте открыты для этого и учитесь.

Я бы порекомендовал вам Working Effectively with Legacy Code для существующего кода (как этот код кода, который вы указали выше). Для легкого запуска пуска в модульное тестирование попробуйте Pragmatic Unit Testing in C# with NUnit. Настоящим открывателем для меня было xUnit Test Patterns: Refactoring Test Code.

Удачи вам в путешествии!

0

В терминах Notify случае стрельбы вам, безусловно, должны гарантировать, работает ли ваш класс в соответствии со спецификацией, а именно, что:

  • Родитель никогда не срабатывает независимо от множества значений
  • StrokeColour и StrokeThickness всегда уволить событие, даже несмотря на то же значение устанавливается
  • CanvasX/Y, HoleType/Dia только огонь, когда значение отличается от предыдущего установлен

Тогда у ou хотите проверить пару побочных эффектов, которые влияют на ваши свойства. После этого вы могли подумать о рефакторинге вещи, потому что, dang, это не классный класс!

Смежные вопросы