.net - tipos - que son las pruebas unitarias de software
¿Cómo pruebas unitarias de métodos privados? (30)
Estoy construyendo una biblioteca de clase que tendrá algunos métodos públicos y privados. Quiero poder probar de forma unitaria los métodos privados (principalmente durante el desarrollo, pero también podría ser útil para futuras refactorizaciones).
¿Cuál es la forma correcta de hacer esto?
A veces, puede ser bueno probar declaraciones privadas. Fundamentalmente, un compilador solo tiene un método público: Compile (string outputFileName, params string [] sourceSFileNames). ¡Estoy seguro de que entiendes que sería difícil probar un método de este tipo sin probar cada declaración "oculta"!
Es por eso que hemos creado Visual T #: para facilitar las pruebas. Es un lenguaje de programación .NET gratuito (compatible con C # v2.0).
Hemos añadido el operador ''.-''. Simplemente se comporta como ''''. operador, excepto que también puede acceder a cualquier declaración oculta de sus pruebas sin cambiar nada en su proyecto probado.
Eche un vistazo a nuestro sitio web: download gratis .
Bueno, puedes probar el método privado de dos maneras.
puede crear una instancia de la clase
PrivateObject
, la sintaxis es la siguientePrivateObject obj= new PrivateObject(PrivateClass); //now with this obj you can call the private method of PrivateCalss. obj.PrivateMethod("Parameters");
Puedes usar la reflexión.
PrivateClass obj = new PrivateClass(); // Class containing private obj Type t = typeof(PrivateClass); var x = t.InvokeMember("PrivateFunc", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, obj, new object[] { 5 });
Creo que una pregunta más fundamental que se debe hacer es que ¿por qué estás tratando de probar el método privado en primer lugar? Ese es un olor a código que intenta probar el método privado a través de la interfaz pública de esa clase, mientras que ese método es privado por una razón, ya que es un detalle de implementación. Uno solo debe preocuparse por el comportamiento de la interfaz pública, no por cómo se implementa bajo las coberturas.
Si quiero probar el comportamiento del método privado, utilizando refactorizaciones comunes, puedo extraer su código en otra clase (tal vez con visibilidad a nivel de paquete, así que asegúrese de que no sea parte de una API pública). Entonces puedo probar su comportamiento de forma aislada.
El producto de la refactorización significa que el método privado es ahora una clase separada que se ha convertido en un colaborador de la clase original. Su comportamiento se habrá entendido bien a través de sus propias pruebas unitarias.
Luego puedo burlarme de su comportamiento cuando trato de probar la clase original para poder concentrarme en probar el comportamiento de la interfaz pública de esa clase en lugar de probar una explosión combinatoria de la interfaz pública y el comportamiento de todos sus métodos privados. .
Veo esto análogo a conducir un coche. Cuando manejo un automóvil, no manejo con el capó levantado, así puedo ver que el motor está funcionando. Confío en la interfaz que proporciona el automóvil, a saber, el contador de revoluciones y el velocímetro para saber que el motor está funcionando. Confío en el hecho de que el automóvil realmente se mueve cuando presiono el pedal del acelerador. Si quiero probar el motor puedo hacer comprobaciones de forma aislada. :RE
Por supuesto, probar métodos privados directamente puede ser un último recurso si tiene una aplicación heredada, pero preferiría que el código heredado sea refactorizado para permitir mejores pruebas. Michael Feathers ha escrito un gran libro sobre este tema. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
Declárelos internal
y luego use InternalsVisibleToAttribute
para permitir que el conjunto de prueba de la unidad los vea.
En CodeProject, hay un artículo que trata brevemente las ventajas y desventajas de probar métodos privados. Luego, proporciona un código de reflexión para acceder a los métodos privados (similar al código que Marcus proporciona anteriormente). El único problema que he encontrado con el ejemplo es que el código no tiene en cuenta los métodos sobrecargados.
Puede encontrar el artículo aquí:
En los casos raros que he querido probar funciones privadas, normalmente las he modificado para que estén protegidas, y he escrito una subclase con una función de envoltura pública.
La clase:
...
protected void APrivateFunction()
{
...
}
...
Subclase para la prueba:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
Hay 2 tipos de métodos privados. Métodos privados estáticos y métodos privados no estáticos (métodos de instancia). Los siguientes 2 artículos explican cómo realizar una prueba unitaria de métodos privados con ejemplos.
Los tipos privados, internos y privados lo son por alguna razón, y a menudo no quieres meterte con ellos directamente. Y si lo hace, lo más probable es que se rompa más tarde, porque no hay garantía de que los individuos que crearon esos ensamblajes mantendrán las implementaciones privadas / internas como tales.
Pero, a veces, al realizar algunos trucos / exploración de ensamblajes compilados o de terceros, yo mismo acabé queriendo inicializar una clase privada o una clase con un constructor privado o interno. O, a veces, cuando trato con bibliotecas heredadas precompiladas que no puedo cambiar, termino escribiendo algunas pruebas contra un método privado.
Así nació AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - es una clase de envoltorio rápido que facilitará el trabajo utilizando las características dinámicas de C # 4.0 y la reflexión.
Puedes crear tipos internos / privados como
//Note that the wrapper is dynamic
dynamic wrapper = AccessPrivateWrapper.FromType
(typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");
//Access the private members
wrapper.PrivateMethodInPrivateClass();
MS Test tiene una característica agradable incorporada que hace que los miembros privados y los métodos estén disponibles en el proyecto al crear un archivo llamado VSCodeGenAccessors
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class BaseAccessor
{
protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;
protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
{
m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
}
protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
:
this(null, type)
{
}
internal virtual object Target
{
get
{
return m_privateObject.Target;
}
}
public override string ToString()
{
return this.Target.ToString();
}
public override bool Equals(object obj)
{
if (typeof(BaseAccessor).IsInstanceOfType(obj))
{
obj = ((BaseAccessor)(obj)).Target;
}
return this.Target.Equals(obj);
}
public override int GetHashCode()
{
return this.Target.GetHashCode();
}
}
Con clases que derivan de BaseAccessor.
como
[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{
protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));
internal SomeClassAccessor(global::Namespace.Someclass target)
: base(target, m_privateType)
{
}
internal static string STATIC_STRING
{
get
{
string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
return ret;
}
set
{
m_privateType.SetStaticField("STATIC_STRING", value);
}
}
internal int memberVar {
get
{
int ret = ((int)(m_privateObject.GetField("memberVar")));
return ret;
}
set
{
m_privateObject.SetField("memberVar", value);
}
}
internal int PrivateMethodName(int paramName)
{
object[] args = new object[] {
paramName};
int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
typeof(int)}, args)));
return ret;
}
MbUnit tiene un bonito envoltorio para este llamado Reflector.
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
También puedes establecer y obtener valores de las propiedades.
dogReflector.GetProperty("Age");
Respecto a la "prueba privada" estoy de acuerdo en que ... en el mundo perfecto. No tiene sentido hacer pruebas unitarias privadas. Pero en el mundo real podría terminar queriendo escribir pruebas privadas en lugar de código de refactorización.
Me sorprende que nadie haya dicho esto todavía, pero una solución que he empleado es crear un método estático dentro de la clase para probarse a sí mismo. Esto le da acceso a todo lo público y privado para probar.
Además, en un lenguaje de scripting (con capacidades OO, como Python, Ruby y PHP), puede hacer que el archivo se pruebe por sí mismo cuando se ejecuta. Buena forma rápida de asegurarte de que tus cambios no rompieran nada. Esto obviamente hace una solución escalable para probar todas sus clases: simplemente ejecútelas todas. (También puede hacer esto en otros idiomas con un vacío principal que siempre ejecuta sus pruebas también).
No debes probar los métodos privados de tu código en primer lugar. Debería estar probando la ''interfaz pública'' o API, las cosas públicas de sus clases. La API son todos los métodos públicos que expones a los llamantes externos.
La razón es que una vez que comienzas a probar los métodos privados y los aspectos internos de tu clase, estás uniendo la implementación de tu clase (las cosas privadas) a tus pruebas. Esto significa que cuando decida cambiar los detalles de su implementación, también tendrá que cambiar sus pruebas.
Por este motivo, debe evitar el uso de InternalsVisibleToAtrribute.
Aquí hay una gran charla de Ian Cooper que cubre este tema: Ian Cooper: TDD, ¿dónde salió todo mal?
No estoy de acuerdo con la filosofía de "usted solo debería estar interesado en probar la interfaz externa". Es un poco como decir que un taller de reparación de automóviles solo debería tener pruebas para ver si las ruedas giran. Sí, en última instancia, me interesa el comportamiento externo, pero me gusta que mis propias pruebas internas y privadas sean un poco más específicas y precisas. Sí, si me refactorizo, es posible que tenga que cambiar algunas de las pruebas, pero a menos que sea un refactor masivo, solo tendré que cambiar algunas y el hecho de que las otras pruebas internas (sin cambios) todavía funcionen es un gran indicador de que La refactorización ha tenido éxito.
Puede tratar de cubrir todos los casos internos utilizando solo la interfaz pública y, en teoría, es posible probar todos los métodos internos (o al menos cada uno que sea importante) utilizando la interfaz pública, pero es posible que tenga que terminar parándose de cabeza para lograr Esto y la conexión entre los casos de prueba que se ejecutan a través de la interfaz pública y la parte interna de la solución que están diseñados para probar pueden ser difíciles o imposibles de discernir. Habiendo señalado, las pruebas individuales que garantizan que la maquinaria interna funciona correctamente valen la pena los pequeños cambios de prueba que se producen con la refactorización, al menos esa ha sido mi experiencia. Si tiene que hacer grandes cambios en sus pruebas para cada refactorización, entonces tal vez esto no tenga sentido, pero en ese caso, tal vez debería repensar su diseño por completo. Un buen diseño debe ser lo suficientemente flexible como para permitir la mayoría de los cambios sin rediseños masivos.
Para cualquier persona que quiera ejecutar métodos privados sin toda la fess y el desastre. Esto funciona con cualquier marco de prueba de unidad utilizando nada más que un buen Reflejo antiguo.
public class ReflectionTools
{
// If the class is non-static
public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
{
Type t = objectUnderTest.GetType();
return t.InvokeMember(method,
BindingFlags.InvokeMethod |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.Static,
null,
objectUnderTest,
args);
}
// if the class is static
public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
{
MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
foreach(var member in members)
{
if (member.Name == method)
{
return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
}
}
return null;
}
}
Luego, en tus pruebas reales, puedes hacer algo como esto:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
Puede que no sea útil probar métodos privados. Sin embargo, a veces también me gusta llamar a los métodos privados de los métodos de prueba. La mayoría de las veces para evitar la duplicación de código para la generación de datos de prueba ...
Microsoft proporciona dos mecanismos para esto:
Accesorios
- Ir al código fuente de la definición de clase.
- Haga clic derecho en el nombre de la clase
- Elija "Crear acceso privado"
- Elija el proyecto en el que se debe crear el elemento de acceso => Terminará con una nueva clase con el nombre foo_accessor. Esta clase se generará dinámicamente durante la compilación y ofrecerá a todos los miembros públicos disponibles.
Sin embargo, el mecanismo a veces es un poco intratable cuando se trata de cambios de la interfaz de la clase original. Entonces, la mayoría de las veces evito usar esto.
Clase PrivateObject La otra forma es usar Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject
// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );
// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );
// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
Quiero crear un ejemplo de código claro aquí que puede usar en cualquier clase en la que quiera probar un método privado.
En su clase de caso de prueba solo incluya estos métodos y luego empleelos como se indica.
/**
*
* @var Class_name_of_class_you_want_to_test_private_methods_in
* note: the actual class and the private variable to store the
* class instance in, should at least be different case so that
* they do not get confused in the code. Here the class name is
* is upper case while the private instance variable is all lower
* case
*/
private $class_name_of_class_you_want_to_test_private_methods_in;
/**
* This uses reflection to be able to get private methods to test
* @param $methodName
* @return ReflectionMethod
*/
protected static function getMethod($methodName) {
$class = new ReflectionClass(''Class_name_of_class_you_want_to_test_private_methods_in'');
$method = $class->getMethod($methodName);
$method->setAccessible(true);
return $method;
}
/**
* Uses reflection class to call private methods and get return values.
* @param $methodName
* @param array $params
* @return mixed
*
* usage: $this->_callMethod(''_someFunctionName'', array(param1,param2,param3));
* {params are in
* order in which they appear in the function declaration}
*/
protected function _callMethod($methodName, $params=array()) {
$method = self::getMethod($methodName);
return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
}
$ this -> _ callMethod (''_ someFunctionName'', array (param1, param2, param3));
Simplemente emita los parámetros en el orden en que aparecen en la función privada original
Si desea probar un método privado, algo puede estar mal. Las pruebas unitarias son (en términos generales) destinadas a probar la interfaz de una clase, es decir, sus métodos públicos (y protegidos). Por supuesto, puede "piratear" una solución para esto (incluso si solo se hacen públicos los métodos), pero también puede considerar:
- Si el método que desea probar realmente vale la pena probar, puede valer la pena moverlo a su propia clase.
- Agregue más pruebas a los métodos públicos que llaman al método privado, probando la funcionalidad del método privado. (Como indicaron los comentaristas, solo debe hacer esto si la funcionalidad de estos métodos privados es realmente una parte de la interfaz pública. Si realmente realizan funciones que están ocultas para el usuario (es decir, la prueba de la unidad), esto probablemente sea incorrecto).
Si está utilizando .net, debe usar InternalsVisibleToAttribute .
También he usado el método InternalsVisibleToAttribute. También vale la pena mencionar que, si se siente incómodo al hacer que sus métodos previamente privados sean internos para lograr esto, entonces tal vez no deberían ser objeto de pruebas unitarias directas de todos modos.
Después de todo, está probando el comportamiento de su clase, en lugar de su implementación específica : puede cambiar esta última sin cambiar la primera y sus pruebas aún deberían pasar.
Tiendo a no usar directivas de compilación porque desordenan las cosas rápidamente. Una forma de mitigarlo si realmente los necesita es colocarlos en una clase parcial y hacer que su compilación ignore ese archivo .cs al crear la versión de producción.
1) Si tiene un código heredado, la única forma de probar métodos privados es por reflexión.
2) Si es un código nuevo, tienes las siguientes opciones:
- Usa la reflexión (a lo complicado)
- Escriba la prueba de la unidad en la misma clase (hace que el código de producción sea feo al tener un código de prueba también)
- Refactorizar y hacer público el método en algún tipo de clase util.
- Use la anotación @VisibleForTesting y elimine lo privado
Prefiero el método de anotación, el más sencillo y el menos complicado. El único problema es que hemos aumentado la visibilidad, lo que creo que no es una gran preocupación. Siempre deberíamos estar codificando para la interfaz, así que si tenemos una interfaz MyService y una implementación MyServiceImpl, entonces podemos tener las clases de prueba correspondientes que son MyServiceTest (métodos de interfaz de prueba) y MyServiceImplTest (métodos privados de prueba). De todos modos, todos los clientes deberían utilizar la interfaz, de modo que, aunque la visibilidad del método privado haya aumentado, en realidad no debería importar.
Aquí hay un ejemplo, primero la firma del método:
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^//[/]]|/[[^]]*/])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
Aquí está la prueba:
/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
object[] values = new object[] { 2, "Martin" };
XPathString xp = new XPathString(path, values);
PrivateObject param0 = new PrivateObject(xp);
XPathString_Accessor target = new XPathString_Accessor(param0);
string[] expected = new string[] {
"pair[path/to/@Key={0}]",
"Items",
"Item[Name={1}]",
"Date"
};
string[] actual;
actual = target.SplitInternal();
CollectionAssert.AreEqual(expected, actual);
}
En mi opinión, solo deberías probar por unidad la API pública de tu clase.
Al hacer público un método, para probarlo por unidades, se rompe el encapsulado y se exponen los detalles de la implementación.
Una buena API pública resuelve un objetivo inmediato del código del cliente y resuelve ese objetivo por completo.
También puede declararlo público o interno (con InternalsVisibleToAttribute) mientras se construye en modo de depuración:
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
Infla el código, pero estará private
en una versión de lanzamiento.
Aquí hay un buen article sobre pruebas unitarias de métodos privados. Pero no estoy seguro de qué es lo mejor, para hacer que su aplicación esté diseñada especialmente para pruebas (es como crear pruebas solo para pruebas) o usar la reflexión para las pruebas. Bastante seguro de que la mayoría de nosotros elegiremos la segunda manera.
Puede generar el método de prueba para el método privado desde Visual Studio 2008. Cuando crea una prueba de unidad para un método privado, se agrega una carpeta de Referencias de prueba a su proyecto de prueba y se agrega un elemento de acceso a esa carpeta. El acceso también se menciona en la lógica del método de prueba de unidad. Este elemento de acceso le permite a su prueba de unidad llamar a métodos privados en el código que está probando. Para más detalles echar un vistazo a
También tenga en cuenta que InternalsVisibleToAtrribute tiene el requisito de que su ensamblaje tenga un nombre sólido , lo que crea su propio conjunto de problemas si trabaja en una solución que no tenía ese requisito anteriormente. Uso el accessor para probar métodos privados. Vea esta pregunta que para un ejemplo de esto.
Una forma de hacer esto es tener su método protected
y escribir un dispositivo de prueba que herede su clase para ser probado. De esta manera, usted no está cambiando su método public
, pero habilita las pruebas.
Yo uso la clase PrivateObject . Pero como se mencionó anteriormente mejor para evitar probar métodos privados.
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public
"CC" es el compilador de línea de comando en el sistema que uso. -Dfoo=bar
hace el equivalente de #define foo bar
. Entonces, esta opción de compilación cambia efectivamente todas las cosas privadas a públicas.