Примеры практики Тестирование кода С#

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

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 для проверки содержимого информационных параметров.

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

Ответ 2

Unit Test Пример:

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Итак, например, в вашем коде вы можете добавить следующий тест:

hole.Visible = false;

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

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

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

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

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

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

Как предполагали другие, ищите рамки тестирования.

Ответ 7

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

Ответ 8

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

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

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

Только 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 всегда полезно попробовать.

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

Ответ 9

Тесты помогут, если вам нужно внести изменения.

В соответствии с Перьями (Feathers, Эффективно работает с устаревшим кодом, стр. 3) есть четыре причины изменений:

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

Когда есть необходимость в изменении, вы хотите быть уверены, что ничего не сломаете. Точнее: вы не хотите нарушать какое-либо поведение (Hunt, Thomas, Прагматическое тестирование модуля в С# с помощью NUnit, стр. 31).

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

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

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

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

Я бы порекомендовал вам Эффективно работать с устаревшим кодом для существующей основы кода (как тот фрагмент кода, который вы указали выше). Для легкого начала запуска в модульное тестирование попробуйте Прагматическое тестирование модуля на С# с NUnit. Настоящий ящик для глаз для меня был xUnit Test Patterns: Рефакторинг тестового кода.

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

Ответ 10

один пример,

для

public HoleTypes HoleType

проверить/проверить значение null в наборе

Ответ 11

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

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

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

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

Что я тестирую?

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

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

Ответ 12

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

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

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

Ответ 13

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

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

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

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

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

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