Modificador de acceso "interno" de C # al realizar pruebas unitarias

469

Soy nuevo en pruebas unitarias y estoy tratando de averiguar si debo comenzar a usar más modificadores de acceso 'internos'. Sé que si usamos 'interno' y configuramos la variable de ensamblaje 'InternalsVisibleTo', podemos probar funciones que no queremos declarar públicas desde el proyecto de prueba. Esto me hace pensar que siempre debería usar 'interno' porque al menos cada proyecto (¿debería?) Tiene su propio proyecto de prueba. ¿Pueden decirme una razón por la que no debería hacer esto? ¿Cuándo debo usar 'privado'?

Hertanto Lie
fuente
1
Vale la pena mencionar: a menudo puede evitar la necesidad de que la unidad pruebe sus métodos internos al usarlos System.Diagnostics.Debug.Assert()dentro de los mismos métodos.
Mike Marynowski

Respuestas:

1195

Las clases internas deben probarse y hay un atributo de ensamblaje:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Agregue esto al archivo de información del proyecto, por ejemplo Properties\AssemblyInfo.cs.

EricSchaefer
fuente
70
Agréguelo al proyecto bajo prueba (por ejemplo, en Propiedades \ AssemblyInfo.cs). "MyTests" sería el ensamblaje de prueba.
EricSchaefer
86
Esta realmente debería ser la respuesta aceptada. No sé ustedes, pero cuando las pruebas están "demasiado lejos" del código que están probando, tiendo a ponerme nervioso. Estoy dispuesto a evitar probar cualquier cosa marcada como private, pero demasiadas privatecosas podrían apuntar a una internalclase que está luchando por ser extraída. TDD o no TDD, prefiero tener más pruebas que prueben mucho código, que tener pocas pruebas que ejerzan la misma cantidad de código. Y evitar probar internalcosas no ayuda exactamente a lograr una buena relación.
sm
77
Hay una gran discusión entre @DerickBailey y Dan Tao sobre la diferencia semántica entre lo interno y lo privado y la necesidad de probar los componentes internos . Bien vale la pena leerlo.
Kris McGinnes
31
Al envolver y #if DEBUG, el #endifbloque habilitará esta opción solo en las compilaciones de depuración.
El verdadero Edward Cullen
17
Esta es la respuesta correcta. Cualquier respuesta que diga que solo los métodos públicos deben ser probados en unidades está perdiendo el punto de las pruebas unitarias y es una excusa. Las pruebas funcionales están orientadas a cajas negras. Las pruebas unitarias están orientadas a cajas blancas. Deben probar "unidades" de funcionalidad, no solo API públicas.
Gunnar
127

Si desea probar métodos privados, eche un vistazo a PrivateObjecty PrivateTypeen el Microsoft.VisualStudio.TestTools.UnitTestingespacio de nombres. Ofrecen envoltorios fáciles de usar alrededor del código de reflexión necesario.

Documentos: PrivateType , PrivateObject

Para VS2017 y 2019, puede encontrarlos descargando el MSTest.TestFramework nuget

Brian Rasmussen
fuente
15
Al votar abajo, por favor deje un comentario. Gracias.
Brian Rasmussen
35
Es estúpido rechazar esta respuesta. Apunta a una nueva solución y una muy buena no mencionada anteriormente.
Ignacio Soler Garcia
Aparentemente, hay algún problema con el uso del TestFramework para la aplicación dirigida a .net2.0 o más reciente: github.com/Microsoft/testfx/issues/366
Johnny Wu
41

Agregando a la respuesta de Eric, también puede configurar esto en el csprojarchivo:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>MyTests</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

O si tiene un proyecto de prueba por proyecto para probar, puede hacer algo como esto en su Directory.Build.propsarchivo:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

Ver: https://stackoverflow.com/a/49978185/1678053
Ejemplo: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

gldraphael
fuente
2
Esta debería ser la mejor respuesta de la OMI. Todas las otras respuestas están muy desactualizadas ya que .net se aleja de la información del ensamblaje y mueve la funcionalidad a las definiciones de csproj.
mBrice1024
12

Sigue usando privado por defecto. Si un miembro no debería exponerse más allá de ese tipo, no debería exponerse más allá de ese tipo, incluso dentro del mismo proyecto. Esto mantiene las cosas más seguras y ordenadas: cuando usa el objeto, queda más claro qué métodos debe usar.

Dicho esto, a veces creo que es razonable hacer que los métodos naturalmente privados sean internos para fines de prueba. Prefiero eso a usar la reflexión, que es refactorizadora-hostil.

Una cosa a considerar podría ser un sufijo "ForTest":

internal void DoThisForTest(string name)
{
    DoThis(name);
}

private void DoThis(string name)
{
    // Real implementation
}

Luego, cuando usa la clase dentro del mismo proyecto, es obvio (ahora y en el futuro) que realmente no debería estar usando este método, solo está allí para fines de prueba. Esto es un poco hacky, y no es algo que yo haga, pero al menos vale la pena considerarlo.

Jon Skeet
fuente
2
Si el método es interno, ¿esto no excluye su uso del conjunto de prueba?
Ralph Shillington
77
De vez en cuando uso el ForTestenfoque, pero siempre lo encuentro feo (agregando código que no proporciona un valor real en términos de lógica comercial de producción). Por lo general, encuentro que tuve que usar el enfoque porque el diseño es algo desafortunado (es decir, tener que restablecer instancias
únicas
1
Tentado a desestimar esto, ¿cuál es la diferencia entre este truco y simplemente hacer que la clase sea interna en lugar de privada? Bueno, al menos con compilaciones condicionales. Entonces se pone muy desordenado.
Bloque CAD el
66
@CADbloke: ¿Te refieres a hacer que el método sea interno en lugar de privado? La diferencia es que es obvio que realmente quieres que sea privado. Cualquier código dentro de su base de código de producción que llame a un método obviamenteForTest es incorrecto, mientras que si solo hace que el método sea interno, parece que está bien usarlo.
Jon Skeet el
2
@CADbloke: puede excluir métodos individuales dentro de una compilación de lanzamiento tan fácilmente en el mismo archivo como usar clases parciales, IMO. Y si lo hace, sugiere que no está ejecutando sus pruebas contra su versión de lanzamiento, lo que me parece una mala idea.
Jon Skeet el
11

También puede usar privado y puede llamar a métodos privados con reflexión. Si está utilizando Visual Studio Team Suite, tiene una buena funcionalidad que generará un proxy para llamar a sus métodos privados por usted. Aquí hay un artículo de proyecto de código que muestra cómo puede hacer el trabajo usted mismo para probar métodos privados y protegidos:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

En términos de qué modificador de acceso debe usar, mi regla general es comenzar con privado y escalar según sea necesario. De esa manera, expondrá los detalles internos de su clase tan pocos como sean realmente necesarios y ayudará a mantener ocultos los detalles de implementación, como deberían ser.

Steven Behnke
fuente
3

Estoy usando Dotnet 3.1.101y las .csprojadiciones que funcionaron para mí fueron:

<PropertyGroup>
  <!-- Explicitly generate Assembly Info -->
  <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
  <_Parameter1>MyProject.Tests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

Espero que esto ayude a alguien por ahí!

Sunfish flotante
fuente
1
La adición de la generación explícita de información de ensamblaje fue lo que finalmente hizo que funcionara también para mí. ¡Gracias por publicar esto!
thevioletsaber