2010-08-19 2 views
22

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

Предположим, я хочу проверить, что все типы, которые переопределяют Equals(), делают это правильно. Поскольку Equals() определяется как виртуальный в System.Object, широкий диапазон типов может изменить это поведение. Каждый тип, который делает это, должен будет иметь тесты, чтобы убедиться, что новое поведение следует за неявными ожиданиями вызывающего абонента этого метода. В частности, для Equals(), если вы переопределите этот метод, новая реализация должна убедиться, что два одинаковых объекта также имеют одинаковые хэш-коды, как определено System.Object.GetHashCode().

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

Чтобы избежать всех TestMethods, необходимые для проверки такого типа я вместо того, чтобы определить базовый тест класс, который выглядит, как показано ниже, и есть те тестовые классы все унаследуют же тест поведения люкс повторно типа:

/// <summary> 
/// Test fixture base class for testing types that overrides Object.Equals() 
/// </summary> 
/// <typeparam name="T">The production type under test</typeparam> 
public abstract class EqualsFixtureBase<T> 
{ 
    #region Equals tests 

    protected static void CompareInstances(T inst1, T inst2, bool expectedEquals) 
    { 
     Assert.AreEqual(expectedEquals, inst1.Equals((T)inst2)); 
     Assert.AreEqual(expectedEquals, inst1.Equals((object)inst2)); 
     if (expectedEquals) 
     { 
      // equal instances MUST have identical hash codes 
      // this is a part of the .NET Equals contract 
      Assert.AreEqual(inst1.GetHashCode(), inst2.GetHashCode()); 
     } 
     else 
     { 
      if (inst2 != null) 
      { 
       Assert.AreNotEqual(inst1.GetHashCode(), inst2.GetHashCode()); 
      } 
     } 
    } 

    /// <summary> 
    /// Creates version 1 instance of the type under test, not 'Equal' to instance 2. 
    /// </summary> 
    /// <returns>An instance created with properties 1.</returns> 
    protected abstract T CreateInstance1(); 

    /// <summary> 
    /// Creates version 2 instance of the type under test, not 'Equal' to instance 1. 
    /// </summary> 
    /// <returns>An instance created with properties 2.</returns> 
    protected abstract T CreateInstance2(); 

    /// <summary> 
    /// Creates an instance equal to the version 1 instance, but not the identical 
    /// same object. 
    /// </summary> 
    /// <returns>An instance created with properties equal to instance 1.</returns> 
    protected abstract T CreateInstanceThatEqualsInstance1(); 

    [TestMethod] 
    public void Equals_NullOrDefaultValueTypeInstance() 
    { 
     T instance = CreateInstance1(); 
     CompareInstances(instance, default(T), false); 
    } 

    [TestMethod] 
    public void Equals_InstanceOfAnotherType() 
    { 
     T instance = CreateInstance1(); 
     Assert.IsFalse(instance.Equals(new object())); 
    } 

    [TestMethod] 
    public void Equals_SameInstance() 
    { 
     T slot1 = CreateInstance1(); 
     CompareInstances(slot1, slot1, true); 
    } 

    [TestMethod] 
    public void Equals_EqualInstances() 
    { 
     T slot1 = CreateInstance1(); 
     T slot2 = CreateInstanceThatEqualsInstance1(); 
     CompareInstances(slot1, slot2, true); 
     CompareInstances(slot2, slot1, true); 
    } 

    [TestMethod] 
    public void Equals_NonEqualInstances() 
    { 
     T slot1 = CreateInstance1(); 
     T slot2 = CreateInstance2(); 
     CompareInstances(slot1, slot2, false); 
     CompareInstances(slot2, slot1, false); 
    } 

    #endregion Equals tests 
} 

Я могу повторно использовать эти TestMethods для каждого типа, переопределяя Equals(). Например, это будет определение тестового класса для тестирования, что тип System.String правильно реализует Equals().

[TestClass] 
public class ExampleOfAnEqualsTestFixture : EqualsFixtureBase<string> 
{ 
    [TestMethod] 
    public void Foo() 
    { 
     Assert.IsTrue(true); 
    } 

    protected override string CreateInstance1() 
    { 
     return "FirstString"; 
    } 

    protected override string CreateInstance2() 
    { 
     return "SecondString"; 
    } 

    protected override string CreateInstanceThatEqualsInstance1() 
    { 
     return "FirstString"; 
    } 
} 

Это также может быть продлено дальше. Например, для типов, которые перегружают операторы == и! =, Может быть определен второй абстрактный тестовый базовый класс (то есть EqualsOperatorsFixtureBase<T> : EqualsFixtureBase<T>), который проверяет, что реализация этих операторов не только правильна, но также согласуется с расширенными определениями Equals() и GetHashCode().

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

a) Visual Studio 2010 обнаруживает только метод тестирования Foo(), а не унаследованные методы тестирования, поэтому он не может их запускать. Кажется, что тестовый загрузчик Visual Studio не выполняет иерархию наследования тестового класса.

b) Когда я проверяю эти типы в TFS, TFS находит абстрактный тип EqualsFixtureBase и думает, что это тестовый класс для запуска. Но поскольку он не может быть создан, он не может его запустить и называет тесты такого типа неубедительными, что не позволяет выполнить тестовый прогон и, следовательно, построить (!).

Есть ли способ обойти это, или это ограничение MsTest и Visual Studio?

Если это так, исправление этого в дорожной карте для VS/TFS ??

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

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

Благодаря

EDIT: Я нашел this link в одном из блогов MSDN, он говорит следующее

«В Whidbey, поддержка тест наследования классов не хватает в NUnit, она полностью поддерживает. Это будет исправлено в Оркасе ».

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

+1

@ Jack & Rory: Период щедрости заканчивается, пока этот вопрос остается в значительной степени неразрешенным, поэтому я не стану отмечать этот вопрос, как ответил. Тем не менее вы хорошо поработали, и отчасти помогли, поэтому я проголосовал за каждый из ваших ответов в одном слоте. Если я правильно понимаю правила, это означает, что вы разделите очки бонусов. – Mahol25

ответ

21

Использование VS 2010 Я не вижу такого же поведения, как вы. Когда я скопировал 2 классов в тестовом проект и скомпилировал я получил результат:

UTA004: Illegal use of attribute...The TestMethodAttribute can be 
defined only inside a class marked with the TestClass attribute 

Я отмеченную EqualsFixutureBase:

[TestClass] 
public abstract class EqualsFixtureBase<T> 
{ 
... 
} 

Теперь компилируется без предупреждения и, когда я выбираю запустить тесты для ExampleOfAnEqualsTestFixture работает Foo и все 5 унаследованных тестов равных. Также, когда я копирую ExampleOfAnEqualsTestFixture и использую его для int и запускает тесты для решения. Я вижу, что все 5 унаследованных тестов выполняются (и передаются) для примера строкового класса и примера класса int.

Вы делаете что-то в дополнение к вашему примеру, которое может вызвать проблемы?

+2

Я не получаю результат, который вы делаете, никаких предупреждений компилятора. Im с использованием VS2010 Ultimate, версия 10.0.30319.1. Когда я добавляю TestClassAttribute к абстрактному базовому классу, TFS находит унаследованные методы тестирования в порядке, палец вверх! Но локальный тест-тест VS не работает. Возможно, вы работаете локально с помощью resharper? Это тоже работает для меня, но не поможет мед, когда я хочу увидеть покрытие тестового кода локально, так как для этого мне нужен VS-тест для VS. :/ – Mahol25

+0

Я не использовал Test Runner, я просто запускал тесты либо через IDE, либо с помощью MSTest из командной строки. –

+1

На самом деле, все типы находятся в одном проекте – Mahol25

6

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

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

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

+2

Вот пример блога TestClassExtensionAttribute: http://blogs.msdn.com/b/vstsqualitytools/archive/2009/09/04/extending-the-visual-studio-unit-test-type-part-1.aspx – ErnieL

0

Это работает, если вы поместили сборку базового класса в ту же папку, что и производная? Возможно, поэтому положить их в одни и те же сборочные работы; другая сборка не разрешима в той точке, в которой они этого хотят. Я не уверен, как еще установить правильные пути проб, которые вам могут понадобиться, в то время, когда они необходимы. Параметры .testsettings могут выражать такие вещи, как appbase и probes для appdomain бегуна, возможно, те, которые были настроены правильно, помогут привязать его к сборке базового класса, если он отличается от сборной единицы теста, основанной на корне.

+1

Все находится в одной и той же сборке. Но почему это имеет значение, если они не там? CLR по-прежнему должен загружать все типы перед выполнением чего-либо в любом случае, и к тому времени доступны все метаданные соответствующего типа – Mahol25

10

TestClassAttribute позволяет использовать методы в абстрактной базе. IgnoreAttribute исключает базовый класс из списка тестов. Без метода атрибутов IgnoreAttribute в базе выполняются как для базового класса, так и для подклассов, помеченных TestClassAttribute.

[TestClass][Ignore] 
public abstract class EqualsFixtureBase<T> 
{ 
.... 
Смежные вопросы