Установить источник тестового примера, используя TextFixtureAttribute - PullRequest
0 голосов
/ 28 октября 2019

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

class TestTemplate<X, Y>
{
    public virtual void Reflexivity(X x)
    {
        bool isEqual = x.Equals(x);
        Assert.IsTrue(isEqual);
    }

    public virtual void Symmetry(X x, Y y)
    {
        bool xy = x.Equals(y);
        bool yx = y.Equals(x);
        Assert.AreEqual(xy, yx);
    }

    public virtual void Transitivity(X x, Y y, Y z)
    {
        bool xy = x.Equals(y);
        bool yz = y.Equals(z);
        bool xz = x.Equals(z);
        Assert.IsTrue(!(xy && yz) || xz);
    }

    public virtual void NullTest(X x)
    {
        bool isEqual = x.Equals(null);
        Assert.IsFalse(isEqual);
    }

    public virtual void EqualTest(X x, Y equivalent)
    {
        bool isEqual = x.Equals(equivalent);
        Assert.IsTrue(isEqual);
    }

    public virtual void InqualTest(X x, Y nonequivalent)
    {
        bool isEqual = x.Equals(nonequivalent);
        Assert.IsFalse(isEqual);
    }
}

Теперь я ищу лучший способ использования шаблона.

1 Производное тестовое приспособление

Возможно наследовать шаблон и предоставлять набор тестовых случаевиспользование TestCaseSourceAttribute.

[TestFixture]
class ConcreteTest : TestTemplate<MyClass, MyClass>
{
    [TestCaseSource(typeof(ConcreteTestSource), "ReflexivitySet")]
    public override void Reflexivity(MyClass x) => base.Reflexivity(x);

    [TestCaseSource(typeof(ConcreteTestSource), "EqualitySet")]
    [TestCaseSource(typeof(ConcreteTestSource), "InequalitySet")]
    public override void Symmetry(MyClass x, MyClass y) => base.Symmetry(x, y);

    [TestCaseSource(typeof(ConcreteTestSource), "TransitivitySet")]
    public override void Transitivity(MyClass x, MyClass y, MyClass z) => base.Transitivity(x, y, z);

    [TestCaseSource(typeof(ConcreteTestSource), "ReflexivitySet")]
    public override void NullTest(MyClass x) => base.NullTest(x);

    [TestCaseSource(typeof(ConcreteTestSource), "EqualitySet")]
    public override void EqualTest(MyClass x, MyClass equivalent) => base.EqualTest(x, equivalent);

    [TestCaseSource(typeof(ConcreteTestSource), "InqualitySet")]
    public override void InqualTest(MyClass x, MyClass nonequivalent) => base.InqualTest(x, nonequivalent);
}

Работает, но требует определения отдельного класса тестовых приборов для каждой реализации Equals.

2 Использование одного класса приборов

Другой способ - применить TestCaseSourceAttribute непосредственно к методам базовых приборов.

[TestFixture]
class EqualityTests
{
    [TestCaseSource(typeof(Test1Source), "ReflexivitySet")]
    [TestCaseSource(typeof(Test2Source), "ReflexivitySet")]
    [TestCaseSource(typeof(Test3Source), "ReflexivitySet")]
    public virtual void Reflexivity(object x)
    {
        bool isEqual = x.Equals(x);
        Assert.IsTrue(isEqual);
    }

    // Symmetry, Transitivity, EqualTest, InqualTest and NullTest methods definition here
}

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

3 Попытка задать источник тестовых данных с помощью конструктора фикстур

На следующем шаге я попытался определить источник тестовых данных, передав тип источникаконструктору прибора через аргументы TestFixtureAttribute. Идея состояла в том, чтобы сохранить тип, используя поле или свойство фикстуры, а затем использовать его в sourceType аргументе TestCaseSourceAttribute.

[TestFixture(typeof(Test1Source), TypeArgs = new Type[] { typeof(MyClass), typeof(MyClass) })]
[TestFixture(typeof(Test2Source), TypeArgs = new Type[] { typeof(ClassA), typeof(ClassA) })]
[TestFixture(typeof(Test3Source), TypeArgs = new Type[] { typeof(ClassA), typeof(ClassB) })]
class EqualityTests<X, Y>
{
    private Type _sourceType;

    public EqualityTests(Type sourceType)
    {
        _sourceType = sourceType;
    }

    [TestCaseSource(_sourceType, "SymmetrySet")]
    public virtual void Symmetry(X x, Y y)
    {
        bool xy = x.Equals(y);
        bool yx = y.Equals(x);
        Assert.AreEqual(xy, yx);
    }

    // Reflexivity, Transitivity, EqualTest, InqualTest and NullTest methods definition here
}

Конечно, код не работает, потому что атрибут sourceTypeиз TestCaseSourceAttribute требуется постоянное или typeof выражение.

4 Попытка использовать параметризованный источник тестового набора

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

[TestFixture(typeof(Test1Source), TypeArgs = new Type[] { typeof(MyClass), typeof(MyClass) })]
[TestFixture(typeof(Test2Source), TypeArgs = new Type[] { typeof(ClassA), typeof(ClassA) })]
[TestFixture(typeof(Test3Source), TypeArgs = new Type[] { typeof(ClassA), typeof(ClassB) })]
class EqualityTests<X, Y>
{
    private Type _sourceType;

    public EqualityTests(Type sourceType)
    {
        _sourceType = sourceType;
    }

    [TestCaseSource("GetSymmetryTestSet", new object[] { _sourceType })]
    public virtual void Symmetry(X x, Y y)
    {
        bool xy = x.Equals(y);
        bool yx = y.Equals(x);
        Assert.AreEqual(xy, yx);
    }

    public static IEnumerable<TestCaseData> GetSymmetryTestSet(Type sourceType)
    {
        // return Symmetry member value of the sourceType
    }

    // Reflexivity, Transitivity, EqualTest, InqualTest and NullTest methods definition here
}

Но решение также не работает по той же причине: невозможно использовать нестатический элемент прибораэкземпляр в methodParams аргументе TestCaseSourceAttribute.

Вопрос

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

1 Ответ

1 голос
/ 28 октября 2019

Я бы сделал все методы public static и использовал бы их явно.

class TestHelpers
{
    public static void AssertReflexivity<X>(X x)
    {
        bool isEqual = x.Equals(x);
        Assert.IsTrue("Add a good message here, otherwise test output may be hard to read");
    }

    public static AssertSymmetry<X,Y>(X x, Y y)
    {
        bool xy = x.Equals(y);
        bool yx = y.Equals(x);
        Assert.AreEqual(xy, yx, "Add a good message here, otherwise test output may be hard to read" );
    }
...
[Test]
public void Test1() {
   var x = new SomeType1()
   AssertReflexivity(x);
}

[Test]
public void Test2() {
   var x = new SomeType1();
   var y = new SomeType2();
   AssertReflexivity(x, y);
}

Несколько баллов

Если это примерно тот же тип

  1. Вы можете использовать один тип в своем классе
class TestHelpers
{
    public static void AssertReflexivity<T>(T x)
    {
        bool isEqual = x.Equals(x);
        Assert.IsTrue("Add a good message here, otherwise test output may be hard to read");
    }

    public static AssertSymmetry<T>(T x, T y)
Что делать, если вы сравниваете два объекта, для которых все их свойства имеют значение null? Являются ли они рефлексивными?

Если это его о различных типах

  • Он зависит от того, как Equals переопределен / реализован - эти тесты могут пройти / не пройти во многих различных сценарияхэто не имеет ничего общего с отношениями между типами, а только с настройкой данных.
  • Должны ли разные типы быть рефлексивными?
  • Что если вы сравните два объекта, у которых все свойства имеют нулевое значение? Они рефлексивные?
...