Этот вопрос касается общего метода unit test с потенциально очень полезным широким спектром применимых сценариев. Но легче понять на примере, чтобы лучше проиллюстрировать мой вопрос.
Скажем, я хочу проверить, что все типы, которые переопределяют Equals()
, делают это правильно. Поскольку Equals()
определяется как виртуальный в System.Object
, широкий диапазон типов может изменить это поведение. Каждый тип, который делает это, должен будет иметь тесты, чтобы убедиться, что новое поведение следует за неявными ожиданиями вызывающего абонента этого метода. В частности, для Equals()
, если вы переопределите этот метод, новая реализация должна удостовериться, что два равных объекта также имеют одинаковые хэш-коды, как определено System.Object.GetHashCode()
.
Таким образом, чтобы обеспечить выполнение этого, потребуются несколько тестовых классов, и все они будут проверяться на одинаковое согласованность поведения во всех этих типах.
Чтобы избежать повторного ввода всех тестовых методов, необходимых для тестирования такого типа, я вместо этого определяю базовый тестовый класс, который выглядит как ниже, и эти классы тестов наследуют один и тот же набор тестов поведения:
/// <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: я нашел эту ссылку в одном из блогов MSDN, он говорит следующее
"В Whidbey поддержка наследования тестового класса отсутствовала. В Nunit он полностью поддерживается, и это будет исправлено в Orcas".
Это было написано более трех лет назад. Почему это еще не добавлено? Я не понимаю, есть законные причины, чтобы иметь это, и, на мой взгляд, это будет незначительное изменение. Или я просто не прыгаю в нужное кольцо?