модульное тестирование свойств c #

#c# #.net #tdd #xunit

#c# #.net #tdd #xunit

Вопрос:

Я работаю с классом, который имеет множество свойств. Например;

 public class Bib
{        
    public int PartQty { get; set; }
}
  

Теперь к модульному тестированию; Я выполнил тест xUnit примерно так

     [Fact]
    public void CanGetAndSetPartQuantity()
    {
        const int expected = 3;

        var target = new Bib() {PartQty = expected};

        Assert.Equal(expected, target.PartQty);
    }
  

здесь я ненавижу то, как я жестко кодирую expected = 3. Каков хороший способ протестировать это свойство для accessor и mutator?

Комментарии:

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

2. Вы ожидаете, что .NET Framework перестанет работать? Я бы не рекомендовал писать тесты для автоматических свойств.

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

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

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

Ответ №1:

Поскольку это свойство не имеет никакого поведения, кроме как быть средством получения / установки целого числа, вы, по сути, просто проверяете, что компилятор работал. Это практически не добавляет ценности вашему тесту. Рассмотрите возможность его полного удаления. Это избавило бы вас от этой странной ситуации. 🙂

Если у вас действительно есть какое-то поведение, которое вы пытаетесь зафиксировать (например, разрешенные граничные условия), вам нужно протестировать только их, и ничего больше. Обычно у вас будут константы для этих граничных условий, доступные как часть объекта. Рассмотрите возможность использования этих констант, / — некоторое соответствующее приращение.

Комментарии:

1. Я не могу с этим согласиться. Если мы говорим о TDD, нет гарантии, что доступное для записи свойство работает. Аргумент о «проверке только того, что компилятор работает» подразумевает, что тест знает что-то о внутренней реализации SUT. Этого не должно быть.

Ответ №2:

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

 [Fact]
public void CanGetAndSetPartQuantity()
{
    const int expected = new Random().Next();

    var target = new Bib() {PartQty = expected};

    Assert.Equal(expected, target.PartQty);
}
  

Это гарантирует, что выходные данные правильно представляют входные данные, независимо от того, что это за входные данные.

Ответ №3:

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

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

  • Отрицательные числа для свойств, которые проверяются >= 0
  • Другие ошибки проверки
  • Крайние граничные случаи, такие как Int.MaxValue, которые иногда могут вызывать переполнения и неожиданное поведение в установщике
  • Произвольное значение, которое должно пройти проверку (никаких реальных указаний о том, как выбрать значение здесь, если вы знаете, что это в вашем «хорошем» случае.)

Ответ №4:

Это должно помочь…

 [Fact]     
public void CanGetAndSetPartQuantity()     
{
    bool fail = false;
    int expected = 0;

    while (!fail amp;amp; expected < int.MaxValue)
    {
        var target = new Bib() {PartQty = expected};          
        fail = expected != target.PartQty;
        expected  ;
    }

    Assert.IsTrue(!fail);
} 
  

Комментарии:

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

2. Я отредактировал его так, чтобы тест завершался после того, как были протестированы все положительные целые числа. Это строгий тест.

3. У вас, ребята, фантастическое чувство юмора, вы закатываете глаза

4. 1 потому что я думаю, что вы пытаетесь быть смешным, и это так, но вы должны прояснить это, чтобы не получить так много голосов «против».

5. Вам следует изменить 5-ю строку на int expected = int.MinValue; . И проверьте на наличие значений, меньших минимального значения и больших максимального значения. И дроби.

Ответ №5:

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

В вашем случае я бы использовал имя типа

 const int SomeRandomValidPartQuantity=3;
  

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

Комментарии:

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

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

Ответ №6:

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

Вариант использования информирует тест, который информирует код. Я сильно сомневаюсь, что ваш вариант использования таков: «пользователь моего API может присвоить вызываемому свойству PartQty любое целое число и всегда получать обратно заданное им целое число». Если бы это был реальный вариант использования, вы бы написали модульный тест, который проверяет int.MaxValue и int.MinValue . Однако это редко реальные значения.

Реальный пример использования может выглядеть следующим образом: «пользователь моего API вводит Bib , вводит IFlugleBinder , устанавливает PartQty значение 4, а затем вызывает Execute метод. Это вызывает Bind метод в IFlugleBinder экземпляре 4 раза.» Если бы это был вариант использования, ваш тест выглядел бы совсем по-другому.

Честно говоря, похоже, что Bib это просто какой-то DTO. По моему опыту, большинство DTO — это просто артефакт некоторого варианта использования более высокого уровня. Если DTO возвращается как некоторый результат вызова функции, предоставляемой вашим API, тогда вы действительно должны возвращать интерфейс, а сам класс DTO должен быть закрытым, и в этом случае нет необходимости тестировать его явно (просто проверьте свойства фактического результата, который вы получаете от вызова метода). Аналогично, если это внутренний DTO, который никогда не раскрывается, не делайте его общедоступным. Если ваш пользователь должен предоставить некоторый набор значений, то ваш API должен принимать интерфейс. Позвольте пользователю определить свой собственный класс, который реализует интерфейс, или предоставить неизменяемый класс, например:

 public class Bib : IBib
{
    public Bib(int partQty)
    {
        PartQty = partQty;
    }
    public int PartQty { get; private set; }
}
  

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

Ответ №7:

Хотя я также считаю, что это подпадает под категорию «Тестировать, пока не надоест», если вы действительно считаете, что это стоит протестировать, approval tests предлагает очень простой способ тестирования. Прилагается простой тест для проверки свойств.

 [TestMethod]
[UseReporter(typeof(DiffReporter))]
public void TestMethod1()
{
    var fred = new Person{
            Age = 35,
        FirstName = "fred",
        LastName = "Flintstone",
        Hair = Color.Black
           };
    Approvals.Verify(fred.WritePropertiesToString());
}
  

В результате будет создан файл следующего содержания:

 Person
{
    Age: 35
    FirstName: fred
    LastName: Flintstone
    Hair: Color [Black]
}
  

Просто переименуйте этот файл в .approved и все готово.

Обратите внимание на использование метода расширения: .WritePropertiesToString()

Здесь есть видео об основах тестов утверждения для

MSTest:http://www.youtube.com/watch?v=bg8GOmlwqYY

Nunit:http://www.youtube.com/watch?v=aO_fyZBxaFk

Xunit:http://www.youtube.com/watch?v=8wPx0O4gFzc

Ответ №8:

также вы можете использовать атрибут autofixture autodata следующим образом:

         [Theory]
        [AutoData]
        public void CanGetAndSetPartQuantity(int expected)
        {
            var target = new Bib() {PartQty = expected};

            Assert.Equal(expected, target.PartQty);
        }