C #: Как вы пишете тестовые случаи для конструкторов и перегрузок конструкторов? - PullRequest
0 голосов
/ 09 ноября 2009

Скажем, у вас есть эта оболочка класса:

public class Number
{
    private int value;

    public Number()
        : this(0) {}

    public Number(int initialValue)
        : this(initialValue, 0, 100) {}

    public Number(int initialValue, int minimumValue, int maximumValue)
    {
        if (minimumValue > maximumValue)
            throw new ArgumentException("Minimum cannot be greater than maximum", "minimumValue");

        MinValue = minimumValue;
        MaxValue = maximumValue;
        Value = initialValue;
    }

    public int MinValue { get; private set; }
    public int MaxValue { get; private set; }

    public int Value
    {
        get { return value; }
        set
        {
            if (value < MinValue)
                value = MinValue;
            if (value > MaxValue)
                value = MaxValue;

            this.value = value;
        }
    }
}

Будете ли вы писать тесты для этого класса, и если да, то как бы вы их написали?

Я особенно думаю о конструкторах. Например, у вас был бы один тест, который создал Number, используя конструктор по умолчанию и проверив, что значение равно 0, minvalue равно 0 и maxvalue равно 100? Или это будет за спецификацией? Или это действительно не так, поскольку другие могут зависеть от того, что значения по умолчанию не изменились случайно? Не могли бы вы написать тест для каждого конструктора или просто тест по умолчанию, поскольку вы знаете, что он связывает все остальные.

Ответы [ 6 ]

3 голосов
/ 09 ноября 2009

Я полностью перешел от классического подхода к TDD к более современному и логичному BDD (Behavior Driven Design). В случае вашего класса Number я написал бы следующие спецификации BDD (обратите внимание, что приведенный ниже синтаксис сделан с SubSpec , который основан на xUnit.NET ):

public void Parameterless_constructor_initializes_all_defaults_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with no parameters".Do(() => { number = new Number(); });

    // Observations
    "the Value property should contain the default value 0".Assert(() => Assert.Equal(0, number.value));
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue));
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue));
}

public void Single_parameter_constructor_initializes_all_defaults_and_initial_value_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with the initial value".Do(() => { number = new Number(10); });

    // Observations
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value));
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue));
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue));
}

public void Full_constructor_initializes_all_values_properly()
{
    // State
    Number number = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with the initial, min, and max values".Do(() => { number = new Number(10, 1, 50); });

    // Observations
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value));
    "the MinValue property should be 1".Assert(() => Assert.Equal(1, number.MinValue));
    "the MaxValue property should be 50".Assert(() => Assert.Equal(50, number.MaxValue));
}

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

public void Full_constructor_throws_proper_exception_when_minvalue_greater_than_maxvalue()
{
    // State
    Number number = null;
    Exception expectedEx = null;

    // Context
    "Given a null context".Context(() => {});

    // Concern
    "when creating a new Number with inverted min and max values".Do(
        () => 
        { 
            try { number = new Number(10, 50, 1); }
            catch (Exception ex) { expectedEx = ex }
        }
     );

    // Observations
    "an exception should be thrown".Assert(() => Assert.NotNull(expectedEx));
    "the exception should be an ArgumentException".Assert(() => Assert.IsType<ArgumentException>(expectedEx));
}

Приведенные выше характеристики должны дать вам 100% тестовое покрытие. Они также создают очень хороший, понятный человеку логический отчет при выполнении с xunit.net и выводят отчет по умолчанию.

1 голос
/ 09 ноября 2009

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

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

0 голосов
/ 16 июня 2014

Все тесты для конструкторов похожи, потому что вещи, которые делают конструкторы, похожи по определению. Поэтому я написал простую библиотеку тестирования, которая помогает писать декларативные тесты для конструкторов: Как легко тестировать логику проверки в конструкторах в C #

Вот пример, в котором я пробую семь тестовых примеров на конструкторе одного класса:

[TestMethod]
public void Constructor_FullTest()
{

    IDrawingContext context = new Mock<IDrawingContext>().Object; 

    ConstructorTests<Frame>
        .For(typeof(int), typeof(int), typeof(IDrawingContext))
        .Fail(new object[] { -3, 5, context }, typeof(ArgumentException), "Negative  length")
        .Fail(new object[] { 0, 5, context }, typeof(ArgumentException), "Zero length")
        .Fail(new object[] { 5, -3, context }, typeof(ArgumentException), "Negative width")
        .Fail(new object[] { 5, 0, context }, typeof(ArgumentException), "Zero width")
        .Fail(new object[] { 5, 5, null }, typeof(ArgumentNullException), "Null drawing context")
        .Succeed(new object[] { 1, 1, context }, "Small positive length and width")
        .Succeed(new object[] { 3, 4, context }, "Larger positive length and width")
        .Assert();

}
0 голосов
/ 09 ноября 2009

ОК, я действительно отвечаю на вопрос прямо под списком кодов (а не в заголовке) ...

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

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

0 голосов
/ 09 ноября 2009

Я бы написал модульный тест для каждого конструктора, проверяя, правильно ли установлены минимальное и максимальное значения. Я хотел бы сделать это, чтобы убедиться, что если я позже изменю код одного из конструкторов, мои тесты сообщат мне, что и где изменилось.
Я бы также извлек минимальные и максимальные значения по умолчанию в константу, возможно, чтобы тест выглядел как Assert.AreEqual (DefaultMinimum, myNumber.MinValue).
Я хотел бы проверить, что недопустимый минимум / максимум выдает исключение.
И я бы переименовал этот класс "BoundedNumber" или что-то вроде этого:)

0 голосов
/ 09 ноября 2009

Используйте монахиню.

Создать тест, который создает объект для каждого конструктора. Используйте Assert.AreEqual, чтобы убедиться, что все объекты равны (вы должны переопределить Equals для таких классов, как этот). Чтобы быть более уверенным, отрицательное утверждение Assert.AreSame утверждение.

Затем проверьте каждое свойство на правильность значения.

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

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