unit-testing - studio - unit test project c#
¿La definición de TestMethod en las clases base de prueba no es compatible con MsTest? (4)
¿Funciona si coloca el ensamblaje de la clase base en la misma carpeta que la derivada? Tal vez sea por eso que ponerlos en el mismo montaje funciona; El otro ensamblaje no se puede resolver en el punto en que lo desean. No estoy seguro de cómo establecer las rutas de sondeo correctas que pueda necesitar en el momento en que se necesitan. Los .testsettings pueden expresar cosas como una base de datos y sondeos para el dominio de aplicación del corredor, quizás los configurados correctamente lo ayuden a unirse al conjunto de clase base si es diferente del conjunto de prueba de unidad derivado de la raíz.
Esta pregunta se refiere a una técnica de prueba unitaria general con una amplia gama de escenarios aplicables potencialmente muy útil. Pero es más fácil de entender con un ejemplo para ilustrar mejor mi pregunta.
Digamos que quiero probar que todos los tipos que anulan Equals()
lo hacen correctamente. Dado que Equals()
se define como virtual en System.Object
, una amplia gama de tipos puede cambiar ese comportamiento. Cada tipo que lo haga tendrá que tener pruebas para asegurarse de que el nuevo comportamiento cumpla con las expectativas implícitas de quien llama a ese método. Específicamente para Equals()
, si reemplaza ese método, la nueva implementación debe asegurarse de que dos objetos iguales también tengan códigos hash iguales, como lo define System.Object.GetHashCode()
.
Por lo tanto, para hacer cumplir esto, se necesitarán múltiples clases de prueba y todas ellas probarán la misma consistencia de comportamiento en todos estos tipos.
Para evitar tener que volver a escribir todos los TestMethods necesarios para probar un tipo de este tipo, en su lugar, defina una clase de prueba base como la que se muestra a continuación, y haga que todas esas clases de prueba hereden el mismo conjunto de pruebas de comportamiento:
/// <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
}
Luego puedo reutilizar estos TestMethods para cada tipo que reemplaza a Equals (). Por ejemplo, esta sería la definición de clase de prueba para probar que el tipo System.String
implementa Equals()
correctamente.
[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";
}
}
Esto también puede extenderse más allá. Por ejemplo, para los tipos que sobrecargan los operadores == y! =, Se puede definir una segunda clase base de prueba abstracta (es decir, EqualsOperatorsFixtureBase<T> : EqualsFixtureBase<T>
) que comprueba que la implementación de esos operadores no solo es correcta, sino que también consistente con las definiciones extendidas de Equals()
y GetHashCode()
.
Puedo hacer esto usando NUnit, pero cuando uso MsTest tengo problemas.
a) Visual Studio 2010 solo descubre el método de prueba Foo()
, no los métodos de prueba heredados, por lo que no puede ejecutarlos. Parece que el cargador de prueba de Visual Studio no recorre la jerarquía de herencia de la clase de prueba.
b) Cuando verifico estos tipos en TFS, TFS encuentra el tipo EqualsFixtureBase abstracto y cree que es una clase de prueba para ejecutarse. Pero como no se puede crear, no se puede ejecutar y etiqueta las pruebas de ese tipo como no concluyentes, lo que falla la ejecución de la prueba y, por lo tanto, la compilación (!).
¿Hay alguna forma de evitar esto o es una limitación de MsTest y Visual Studio?
Si es así, ¿está solucionando esto en la hoja de ruta para VS / TFS?
Esto sería muy útil, especialmente cuando se prueban tipos de producción que implementan una interfaz, o son parte de una jerarquía de herencia, donde ciertos miembros tienen propiedades o invariantes semánticas de "tipo de contrato", si eso tiene sentido.
Básicamente, no tener soporte para esto me impide refactorizar mi código de prueba para eliminar la duplicación.
Gracias
EDITAR: Encontré este enlace a uno de los blogs de MSDN, dice lo siguiente
"En Whidbey, faltaba el soporte para la herencia de clase de prueba. En Nunit, es totalmente compatible. Esto se corregirá en orcas".
Eso fue escrito hace más de tres años. ¿Por qué no se ha agregado aún? No lo entiendo, hay razones legítimas para tener esto y en mi opinión sería un cambio menor. ¿O simplemente no estoy saltando los aros correctos aquí?
El TestClassAttribute permite poner métodos en la base abstracta. IgnoreAttribute excluye la clase base de la lista de pruebas. Sin el atributo IgnoreAttribute métodos dentro de la base se ejecutan tanto para la clase base como en las subclases marcadas con el TestClassAttribute
[TestClass][Ignore]
public abstract class EqualsFixtureBase<T>
{
....
Fuera de la caja, parece que la herencia de prueba unitaria solo funciona si la clase de prueba base está en el mismo conjunto que las clases derivadas. Para mí, esto generalmente derrota el propósito de tener la clase base. Yo también me pregunto por qué no hay más publicaciones sobre esto en los blogs y si me falta algo.
Es posible que pueda solucionar el problema vinculando la clase base a cada proyecto en el que desee usarlo. Tal vez marcarlo como interno para que las copias múltiples no interfieran entre sí.
También está el TestClassExtensionAttribute
que puede extender para enganchar en el motor de ejecución de prueba. Intenté usarlo para reflexionar sobre las clases de prueba y cargar las pruebas de la clase base, pero muchas de las clases no están documentadas, y no pude hacer que funcionara.
Usando VS 2010 no veo el mismo comportamiento que tú. Cuando copié sus 2 clases en un proyecto de prueba y lo compilé obtuve el resultado:
UTA004: Illegal use of attribute...The TestMethodAttribute can be
defined only inside a class marked with the TestClass attribute
Así que marqué EqualsFixutureBase :
[TestClass]
public abstract class EqualsFixtureBase<T>
{
...
}
Ahora se compila sin avisar y cuando selecciono las pruebas de ejecución para ExampleOfAnEqualsTestFixture ejecuta Foo y las 5 pruebas de igual heredadas. También cuando copio el ExampleOfAnEqualsTestFixture y lo uso para int y ejecuto las pruebas para la solución, veo que las 5 pruebas heredadas se ejecutan (y pasan) para la clase de cadena de ejemplo y la clase de int de ejemplo.
¿Está haciendo algo además de su ejemplo que podría estar causando su problema?