Этот вопрос касается общей техники модульного тестирования с потенциально очень полезным широким спектром применимых сценариев. Но это легче понять на примере, чтобы лучше проиллюстрировать мой вопрос.
Допустим, я хочу проверить, что все типы, которые переопределяют 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
), который проверяет, что реализация этих операторов не только верны, но и согласуются с расширенными определениями Equals ()
и GetHashCode ()
.
Я могу сделать это с помощью NUnit, но при использовании MsTest возникают проблемы.
a) Visual Studio 2010 обнаруживает только тестовый метод Foo ()
, но не унаследованные тестовые методы, поэтому он не может их запустить. Кажется, что загрузчик тестов Visual Studio не проходит иерархию наследования тестового класса.
б) Когда я регистрирую эти типы в TFS, TFS находит абстрактный тип EqualsFixtureBase и считает, что это тестовый класс, который нужно запустить. Но поскольку он не может быть создан, он не может его запустить и помечает тесты этого типа как неубедительные, что не позволяет выполнить тестовый прогон и, следовательно, сборку (!).
Есть ли способ обойти это, или это ограничение MsTest и Visual Studio?
Если да, исправляет ли это в дорожной карте для VS / TFS ??
Это было бы очень полезно, особенно при тестировании производственных типов, которые реализуют интерфейс или являются частью иерархии наследования, где определенные элементы имеют семантические свойства или инварианты типа контракта - если это имеет смысл.
По сути, отсутствие поддержки этого не позволяет мне провести рефакторинг моего тестового кода, чтобы удалить дублирование.
Спасибо
РЕДАКТИРОВАТЬ: Я нашел эту ссылку на один из блогов MSDN, в ней говорится следующее
«В Whidbey поддержка наследования тестовых классов отсутствовала. В Nunit это полностью поддерживается. Это будет исправлено в косатках »
. Это было написано более трех лет назад. Почему этого еще не добавили? Я не понимаю, для этого есть законные причины, и, на мой взгляд, это будет незначительное изменение. Или я просто не прыгаю по правильному пути?