Смущенный об этом модульном тесте! - PullRequest
1 голос
/ 07 ноября 2010

Итак, в принципе, у меня есть абстрактный класс с уникальным инкрементным идентификатором - Primitive. Когда создается экземпляр Primitive (или, точнее, наследник Primitive), идентификатор увеличивается - до точки переполнения идентификатора, и в этот момент я добавляю сообщение об исключении и перебрасываю.

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

  • Неразумно создавать 2 миллиарда объектов для этого! Однако я не вижу другого пути.
  • Не знаю, правильно ли я использую насмешки? (Я использую Moq.)

Вот мой тест (xUnit):

[Fact(DisplayName = "Test Primitive count limit")]
public void TestPrimitiveCountLimit()
{
    Assert.Throws(typeof(OverflowException), delegate()
    {
        for (; ; )
        {
            var mock = new Mock<Primitive>();
        }
    });
}

и

public abstract class Primitive
{
    internal int Id { get; private set; }
    private static int? _previousId;

    protected Primitive()
    {
        try
        {
            _previousId = Id = checked (++_previousId) ?? 0;
        }
        catch (OverflowException ex)
        {
            throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
        }
    }
}

Я предполагаю, что я делаю это неправильно - так как мне проверить это правильно?

Ответы [ 4 ]

5 голосов
/ 07 ноября 2010

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

Однако есть способ использовать макеты, и это устранит проблему с экземплярами на 2 млрд. Если вы отделите генерацию идентификатора от Primitive класс и использовать генератор, вы можете издеваться над генератором.Пример:

Я изменил Primitive, чтобы использовать предоставленный генератор.В этом случае для него задана статическая переменная, и существуют более эффективные способы, но в качестве примера:

public abstract class Primitive
{
    internal static IPrimitiveIDGenerator Generator;

    protected Primitive()
    {
        Id = Generator.GetNext();
    }

    internal int Id { get; private set; }
}

public interface IPrimitiveIDGenerator
{
    int GetNext();
}

public class PrimitiveIDGenerator : IPrimitiveIDGenerator
{
    private int? _previousId;

    public int GetNext()
    {
        try
        {
            _previousId = checked(++_previousId) ?? 0;

            return _previousId.Value;
        }
        catch (OverflowException ex)
        {
            throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
        }
    }
}

Тогда ваш тестовый пример станет:

[Fact(DisplayName = "Test Primitive count limit")]
public void TestPrimitiveCountLimit()
{
    Assert.Throws(typeof(OverflowException), delegate()
    {
        var generator = new PrimitiveIDGenerator();

        for (; ; )
        {
            generator.GetNext();
        }
    });
}

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

Теперь, когда вы, например, хотите проверить, что создание нового примитива фактически запрашивает идентификатор, вы можете попробовать следующее:

public void Does_primitive_ask_for_an_ID()
{
    var generator = new Mock<IPrimitiveIDGenerator>();

    // Set the expectations on the mock so that it checks that
    // GetNext is called. How depends on what mock framework you're using.

    Primitive.Generator = generator;

    new ChildOfPrimitive();
}

Теперь вы разделили различные проблемы и можете проверить их по отдельности.

1 голос
/ 07 ноября 2010

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

Если вы создали поле _ previousId вашего Primitive класса internal (и, конечно, включили соответствующий атрибут InternalsVisibleTo), тогда ваш тест может быть таким же простым, как этот, с помощью Typemock Изолятор инструмент:

[Fact(DisplayName = "Test Primitive count limit"), Isolated]
public void TestPrimitiveCountLimit()
{
    Primitive._previousId = int.MaxValue;

    Assert.Throws<OverflowException>(() => 
        Isolate.Fake.Instance<Primitive>(Members.CallOriginal, ConstructorWillBe.Called));
}

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

Thomas

1 голос
/ 07 ноября 2010

У вас возникли две проблемы:

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

Я думаю, что решения довольно просты, даже если вам придется немного переосмыслить структуру вашего объекта.

ДляПервая проблема, решение так же просто, как добавление подделки, которая наследует Primitive, но не добавляет функциональности в ваш тестовый проект.Затем вы можете создать экземпляр своего поддельного класса, и вы все равно будете тестировать функциональность Primitive.

public class Fake : Primitive { }

// and in your test...
Assert.Throws(typeof(OverflowException), delegate() { var f = new Fake(int.MaxValue); });

Для второй проблемы я бы добавил конструктор, который принимает int дляпредыдущий идентификатор, и используйте конструктор цепочки, чтобы «не нужно» в вашем реальном коде.(Но как иначе узнать предыдущий идентификатор? Разве вы не можете установить его в int.MaxValue-1 при настройке теста?) Думайте об этом как о введении зависимости, но вы не вводите ничего сложного;Вы просто вводите простой int.Это может быть что-то вроде этого:

public abstract class Primitive
{
internal int Id { get; private set; }
private static int? _previousId;

protected Primitive() : Primitive([some way you get your previous id now...])
protected Primitive(int previousId)
{
    _previousId = previousId;
    try
    {
        _previousId = Id = checked (++_previousId) ?? 0;
    }
    catch (OverflowException ex)
    {
        throw new OverflowException("Cannot instantiate more than (int.MaxValue) unique primitives.", ex);
    }
}
1 голос
/ 07 ноября 2010

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

Id 'на самом деле добавляет еще один конструктор, который принимает значение стратификации для счетчика идентификаторов, так что вы можете фактически начинать близко к int.MaxValue и, следовательно, не нужно устанавливать столько объектов.

Также, только прочитав источник, я могу сказать, что ваш объект не пройдет тест. ; -)

...