c# - publicas - que es un modificador en programacion
Hacer público un método privado para probarlo... ¿buena idea? (30)
Nota para el moderador: ya hay 39 respuestas publicadas aquí (algunas se han eliminado). Antes de publicar su respuesta, considere si puede agregar algo significativo a la discusión. Es más que probable que solo estés repitiendo lo que alguien más ya ha dicho.
Ocasionalmente me encuentro necesitando hacer un método privado en una clase pública solo para escribir algunas pruebas unitarias para él.
Por lo general, esto se debe a que el método contiene lógica compartida entre otros métodos en la clase y es más ordenado probar la lógica por sí mismo, o podría ser posible otra razón si quiero probar la lógica utilizada en subprocesos sincrónicos sin tener que preocuparme por problemas de subprocesos .
¿Se encuentran otras personas haciendo esto, porque realmente no me gusta hacerlo? Personalmente, creo que los bonos superan los problemas de hacer público un método que realmente no proporciona ningún servicio fuera de la clase ...
ACTUALIZAR
Gracias por las respuestas a todos, parece haber despertado el interés de la gente. Creo que el consenso general es que las pruebas deberían realizarse a través de la API pública, ya que esta es la única forma en que se utilizará una clase, y estoy de acuerdo con esto. Los dos casos que mencioné anteriormente donde haría esto anteriormente fueron casos poco comunes y pensé que los beneficios de hacerlo valieron la pena.
Sin embargo, puedo ver que todos señalan que nunca debería suceder realmente. Y cuando lo pienso un poco más, creo que cambiar su código para acomodar las pruebas es una mala idea; después de todo, supongo que las pruebas son una herramienta de soporte de alguna manera y cambiar un sistema para ''soportar una herramienta de soporte'' si lo desea, es descarado mala práctica.
Nota:
Esta respuesta se publicó originalmente para la pregunta ¿Las pruebas unitarias por sí solas son una buena razón para exponer variables de instancia privadas a través de getters? que se fusionó con este, por lo que puede ser un poco específico para el caso de uso presentado allí.
Como una declaración general, generalmente estoy a favor de refactorizar el código de "producción" para que sea más fácil de probar.
Sin embargo, no creo que sea una buena decisión aquí.
Una buena prueba unitaria (por lo general) no debería preocuparse por los detalles de implementación de la clase, solo por su comportamiento visible.
En lugar de exponer las pilas internas a la prueba, puede probar que la clase devuelve las páginas en el orden que espera después de llamar a
first()
o
last()
.
Por ejemplo, considere este pseudocódigo:
public class NavigationTest {
private Navigation nav;
@Before
public void setUp() {
// Set up nav so the order is page1->page2->page3 and
// we''ve moved back to page2
nav = ...;
}
@Test
public void testFirst() {
nav.first();
assertEquals("page1", nav.getPage());
nav.next();
assertEquals("page2", nav.getPage());
nav.next();
assertEquals("page3", nav.getPage());
}
@Test
public void testLast() {
nav.last();
assertEquals("page3", nav.getPage());
nav.previous();
assertEquals("page2", nav.getPage());
nav.previous();
assertEquals("page1", nav.getPage());
}
}
Como se observa ampliamente en los comentarios de otros, las pruebas unitarias deberían centrarse en la API pública.
Sin embargo, dejando de lado los pros / contras y la justificación, puede llamar a métodos privados en una prueba unitaria utilizando la reflexión.
Por supuesto, deberá asegurarse de que su seguridad JRE lo permita.
Llamar a métodos privados es algo que Spring Framework emplea con sus
ReflectionUtils
(vea el
makeAccessible(Method)
método).
Aquí hay una pequeña clase de ejemplo con un método de instancia privada.
public class A {
private void doSomething() {
System.out.println("Doing something private.");
}
}
Y una clase de ejemplo que ejecuta el método de instancia privada.
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
public static final void main(final String[] args) {
try {
Method doSomething = A.class.getDeclaredMethod("doSomething");
A o = new A();
//o.doSomething(); // Compile-time error!
doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
doSomething.invoke(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
}
Al ejecutar B, se imprimirá.
Doing something private.
Si realmente lo necesita, la reflexión podría usarse en pruebas unitarias para acceder a métodos de instancias privadas.
En .Net hay una clase especial llamada
PrivateObject
diseñada específicamente para permitirle acceder a métodos privados de una clase.
Vea más sobre esto en MSDN o aquí en
(Me pregunto que nadie lo haya mencionado hasta ahora).
Sin embargo, hay situaciones en las que esto no es suficiente, en cuyo caso tendrá que usar la reflexión.
Aún así, me apegaría a la recomendación general de no probar métodos privados, sin embargo, como siempre, siempre hay excepciones.
La guayaba tiene una anotación @VisibleForTesting para marcar métodos que tienen un alcance ampliado (paquete o público) que de lo contrario lo harían. Yo uso una anotación @Private para lo mismo.
Si bien la API pública debe probarse, a veces es conveniente y sensato llegar a cosas que normalmente no serían públicas.
Cuando:
- una clase se vuelve significativamente menos legible, in toto, al dividirla en varias clases,
- solo para hacerlo más comprobable,
- y proporcionar algún acceso de prueba a las entrañas haría el truco
Parece que la religión está superando a la ingeniería.
No, porque hay mejores formas de desollar a ese gato.
Algunos arneses de pruebas unitarias se basan en macros en la definición de clase que se expanden automáticamente para crear ganchos cuando se construyen en modo de prueba. Muy estilo C, pero funciona.
Un modismo OO más fácil es hacer que todo lo que desee probar sea "protegido" en lugar de "privado". El arnés de prueba hereda de la clase bajo prueba y luego puede acceder a todos los miembros protegidos.
O opta por la opción "amigo". Personalmente, esta es la característica de C ++ que menos me gusta porque rompe las reglas de encapsulación, pero resulta necesario para la forma en que C ++ implementa algunas características, así que, hola.
De todos modos, si realiza pruebas unitarias, es probable que necesite inyectar valores en esos miembros. Los mensajes de texto de caja blanca son perfectamente válidos. Y eso realmente sería romper su encapsulación.
Personalmente, tengo los mismos problemas al probar métodos privados y esto se debe a que algunas de las herramientas de prueba son limitadas. No es bueno que su diseño sea impulsado por herramientas limitadas si no responden a su necesidad de cambiar la herramienta, no el diseño. Debido a que solicita C #, no puedo proponer buenas herramientas de prueba, pero para Java hay dos herramientas poderosas: TestNG y PowerMock, y puede encontrar las herramientas de prueba correspondientes para la plataforma .NET
Por lo general, dejo esos métodos como
protected
y coloco la prueba de la unidad dentro del mismo paquete (pero en otro proyecto o carpeta de origen), donde pueden acceder a todos los métodos protegidos porque el cargador de clases los colocará dentro del mismo espacio de nombres.
¿Por qué no dividir el algoritmo de gestión de la pila en una clase de utilidad? La clase de utilidad puede administrar las pilas y proporcionar accesores públicos. Sus pruebas unitarias pueden centrarse en los detalles de implementación. Las pruebas profundas para clases complicadas algorítmicamente son muy útiles para eliminar casos extremos y garantizar la cobertura.
Entonces su clase actual puede delegar limpiamente a la clase de utilidad sin exponer ningún detalle de implementación. Sus pruebas se relacionarán con el requisito de paginación que otros han recomendado.
¿Qué tal hacer que el paquete sea privado? Entonces su código de prueba puede verlo (y también otras clases en su paquete), pero aún está oculto para sus usuarios.
Pero realmente, no deberías probar métodos privados. Esos son detalles de implementación y no forman parte del contrato. Todo lo que hacen debe cubrirse llamando a los métodos públicos (si tienen un código que no es ejercido por los métodos públicos, entonces eso debería desaparecer). Si el código privado es demasiado complejo, la clase probablemente está haciendo demasiadas cosas y necesita refactorizar.
Hacer público un método es un gran compromiso. Una vez que hagas eso, las personas podrán usarlo, y ya no podrás cambiarlos.
A menudo agrego un método llamado algo así como
validate
,
verify
,
check
, etc., a una clase para que pueda llamarse para probar el estado interno de un objeto.
A veces, este método está envuelto en un bloque ifdef (escribo principalmente en C ++) para que no se compile para su lanzamiento. Pero a menudo es útil en el lanzamiento para proporcionar métodos de validación que recorren los árboles de objetos del programa comprobando cosas.
Algunas buenas respuestas. Una cosa que no vi mencionado es que con el desarrollo basado en pruebas (TDD), se crean métodos privados durante la fase de refactorización (consulte el Método de extracción para ver un ejemplo de un patrón de refactorización) y, por lo tanto, ya debería tener la cobertura de prueba necesaria . Si se hace correctamente (y, por supuesto, obtendrá una mezcla de opiniones en lo que respecta a la corrección), no debería tener que preocuparse por tener que hacer público un método privado para poder probarlo.
Como han dicho otros, es algo sospechoso que la unidad pruebe métodos privados; la unidad prueba la interfaz pública, no los detalles de implementación privados.
Dicho esto, la técnica que uso cuando quiero probar la unidad algo que es privado en C # es degradar la protección de accesibilidad de privado a interno, y luego marcar el ensamblaje de prueba de la unidad como un ensamblaje amigo usando InternalsVisibleTo . El conjunto de prueba de la unidad podrá tratar las partes internas como públicas, pero no tiene que preocuparse por agregar accidentalmente a su área de superficie pública.
Diría que es una mala idea porque no estoy seguro de si obtiene algún beneficio y potencialmente problemas en el futuro. Si está cambiando el contrato de una llamada, solo para probar un método privado, no está probando la clase sobre cómo se usaría, sino creando un escenario artificial que nunca tuvo la intención de que suceda.
Además, al declarar el método como público, ¿qué quiere decir que dentro de seis meses (después de olvidar que la única razón para hacer público un método es para probar) que usted (o si ha entregado el proyecto) alguien completamente diferente ganó No lo use, lo que lleva a consecuencias potencialmente no deseadas y / o una pesadilla de mantenimiento.
En java, también existe la opción de hacer que el paquete sea privado (es decir, dejar el modificador de visibilidad). Si sus pruebas unitarias están en el mismo paquete que la clase que se está probando, entonces debería poder ver estos métodos, y es un poco más seguro que hacer que el método sea completamente público.
En mi opinión, debe escribir sus pruebas sin hacer suposiciones profundas sobre cómo su clase se implementó en su interior. Probablemente desee refactorizarlo más tarde utilizando otro modelo interno pero aún haciendo las mismas garantías que brinda la implementación anterior.
Teniendo esto en cuenta, le sugiero que se concentre en probar que su contrato aún se mantiene sin importar la implementación interna que tenga actualmente su clase. Pruebas basadas en propiedades de sus API públicas.
En realidad, hay situaciones en las que debe hacer esto (por ejemplo, cuando implementa algunos algoritmos complejos). Simplemente hazlo paquete privado y esto será suficiente. Pero en la mayoría de los casos, probablemente tenga clases demasiado complejas, lo que requiere descomponer la lógica en otras clases.
En su actualización, dice que es bueno simplemente probar usando la API pública. En realidad hay dos escuelas aquí.
-
Prueba de caja negra
La escuela de caja negra dice que la clase debe considerarse como una caja negra para que nadie pueda ver la implementación dentro de ella. La única forma de probar esto es a través de la API pública, tal como lo usará el usuario de la clase.
-
prueba de caja blanca.
La escuela de caja blanca cree que es natural usar el conocimiento sobre la implementación de la clase y luego probar la clase para saber que funciona como debería.
Realmente no puedo tomar partido en la discusión. Simplemente pensé que sería interesante saber que hay dos formas distintas de probar una clase (o una biblioteca o lo que sea).
Generalmente mantengo las clases de prueba en el mismo proyecto / ensamblaje que las clases bajo prueba.
De esta manera solo necesito visibilidad
internal
para hacer que las funciones / clases sean comprobables.
Esto complica un poco su proceso de construcción, que necesita filtrar las clases de prueba.
Lo logro nombrando todas mis clases de prueba
TestedClassTest
y usando una expresión regular para filtrar esas clases.
Por supuesto, esto solo se aplica a la parte C # / .NET de su pregunta
Los métodos privados generalmente se usan como métodos "auxiliares". Por lo tanto, solo devuelven valores básicos y nunca operan en instancias específicas de objetos.
Tienes un par de opciones si quieres probarlas.
- Usar reflejo
- Dar acceso al paquete de métodos
Alternativamente, podría crear una nueva clase con el método auxiliar como método público si es un candidato suficientemente bueno para una nueva clase.
Hay un muy buen artículo aquí sobre esto.
Los métodos privados que desea probar de forma aislada son una indicación de que hay otro "concepto" oculto en su clase. Extraiga ese "concepto" a su propia clase y pruébelo como una "unidad" separada.
Echa un vistazo a este video para una versión realmente interesante sobre el tema.
Muchas respuestas sugieren solo probar la interfaz pública, pero en mi humilde opinión, esto no es realista: si un método hace algo que lleva 5 pasos, querrás probar esos cinco pasos por separado, no todos juntos.
Esto requiere probar los cinco métodos, que
(aparte de las pruebas)
podrían ser
private
.
La forma habitual de probar métodos "privados" es dar a cada clase su propia interfaz y hacer
public
métodos "privados", pero no incluirlos en la interfaz.
De esta manera, todavía se pueden probar, pero no hinchan la interfaz.
Sí, esto resultará en hinchazón de archivos y clases.
Sí, esto hace que los especificadores
public
y
private
redundantes.
Sí, esto es un dolor en el culo.
Este es, desafortunadamente, uno de los muchos sacrificios que hacemos para que el código sea comprobable . Quizás un lenguaje futuro (o incluso una versión futura de C # / Java) tendrá características para hacer que la capacidad de prueba de clase y módulo sea más conveniente; pero mientras tanto, tenemos que saltar a través de estos aros.
Hay quienes argumentan que cada uno de esos pasos debería
ser su propia clase
, pero no estoy de acuerdo: si todos comparten el estado, no hay razón para crear cinco clases separadas donde serían cinco métodos.
Peor aún, esto da como resultado una hinchazón de archivos y clases.
Además
, infecta la API pública de su módulo: todas esas clases deben ser necesariamente
public
si desea probarlas desde otro módulo
(ya sea eso o incluir el código de prueba en el mismo módulo, lo que significa enviar el código de prueba con su producto )
Nunca debes dejar que tus pruebas dicten tu código. No estoy hablando de TDD u otros DD. Quiero decir, exactamente lo que estás preguntando. ¿Su aplicación necesita esos métodos para ser pública? Si es así, pruébelos. Si no es así, entonces no los haga públicos solo para probarlos. Lo mismo con las variables y otras. Deje que las necesidades de su aplicación dicten el código, y deje que sus pruebas prueben que se satisface la necesidad. (Nuevamente, no me refiero a la prueba primero o no, me refiero a cambiar la estructura de una clase para cumplir con un objetivo de prueba).
En su lugar, debe "probar más alto". Pruebe el método que llama al método privado. Pero sus pruebas deberían probar las necesidades de sus aplicaciones y no sus "decisiones de implementación".
Por ejemplo (pseudocódigo bod aquí);
public int books(int a) {
return add(a, 2);
}
private int add(int a, int b) {
return a+b;
}
No hay ninguna razón para probar "agregar", puede probar "libros" en su lugar.
Nunca permita que sus pruebas tomen decisiones de diseño de código por usted. Comprueba que obtienes el resultado esperado, no cómo obtienes ese resultado.
Personalmente, preferiría realizar pruebas unitarias utilizando la API pública y ciertamente nunca haría público el método privado solo para facilitar la prueba.
Si realmente desea probar el método privado de forma aislada, en Java puede usar Easymock / Powermock para permitirle hacer esto.
Debe ser pragmático al respecto y también debe ser consciente de las razones por las que las cosas son difíciles de probar.
'' Escucha las pruebas '': si es difícil de probar, ¿te está diciendo algo sobre tu diseño? ¿Podría refactorizar a dónde una prueba para este método sería trivial y fácilmente cubierta mediante pruebas a través de la API pública?
Esto es lo que Michael Feathers tiene que decir en '' Trabajar eficazmente con código heredado "
"Muchas personas pasan mucho tiempo tratando de descubrir cómo solucionar este problema ... la verdadera respuesta es que si tiene la necesidad de probar un método privado, el método no debería ser privado; si hace público el método te molesta, lo más probable es que sea porque es parte de una responsabilidad por separado; debería estar en otra clase ". [ Trabajando efectivamente con el código heredado (2005) por M. Feathers]
Primero vea si el método debe extraerse en otra clase y hacerse público. Si ese no es el caso, proteja el paquete y anote en Java con @VisibleForTesting .
Si está utilizando C #, puede hacer que el método sea interno. De esa manera no contamina la API pública.
Luego agregue atributo a dll
[ensamblaje: InternalsVisibleTo ("MyTestAssembly")]
Ahora todos los métodos son visibles en su proyecto MyTestAssembly. Tal vez no sea perfecto, pero mejor que hacer público el método privado solo para probarlo.
Tiendo a estar de acuerdo en que las bonificaciones de tener una unidad probada superan los problemas de aumentar la visibilidad de algunos de los miembros. Una ligera mejora es hacerlo protegido y virtual, luego anularlo en una clase de prueba para exponerlo.
Alternativamente, si es la funcionalidad que desea probar por separado, ¿no sugiere un objeto faltante en su diseño? Tal vez podría ponerlo en una clase comprobable por separado ... luego su clase existente simplemente delega a una instancia de esta nueva clase.
Una prueba unitaria debería probar el contrato público, la única forma en que una clase podría usarse en otras partes del código. Un método privado son los detalles de implementación, no debe probarlo, en la medida en que la API pública funcione correctamente, la implementación no importa y podría cambiarse sin cambios en los casos de prueba.
Use la reflexión para acceder a las variables privadas si es necesario.
Pero realmente, no te importa el estado interno de la clase, solo quieres probar que los métodos públicos devuelven lo que esperas en las situaciones que puedes anticipar.
en términos de pruebas unitarias, definitivamente no debe agregar más métodos;
Creo que sería mejor hacer un caso de prueba sobre su
first()
método
first()
, que se llamaría antes de cada prueba;
entonces puede llamar varias veces al
next()
,
previous()
y
last()
para ver si los resultados coinciden con sus expectativas.
Supongo que si no agrega más métodos a su clase (solo con fines de prueba), se apegaría al principio de prueba de la "caja negra";
Actualización : he agregado una respuesta más amplia y más completa a esta pregunta en muchos otros lugares. Esto se puede encontrar en mi blog .
Si alguna vez necesito hacer algo público para probarlo, esto generalmente sugiere que el sistema bajo prueba no está siguiendo el Principio de Responsabilidad Única . Por lo tanto, falta una clase que debería introducirse. Después de extraer el código en una nueva clase, hazlo público. Ahora puedes probar fácilmente y estás siguiendo SRP. Su otra clase simplemente tiene que invocar esta nueva clase a través de la composición.
Hacer públicos los métodos / usar trucos de lenguaje, como marcar el código como visible para los ensambles de prueba, siempre debe ser el último recurso.
Por ejemplo:
public class SystemUnderTest
{
public void DoStuff()
{
// Blah
// Call Validate()
}
private void Validate()
{
// Several lines of complex code...
}
}
Refactorice esto introduciendo un objeto validador.
public class SystemUnderTest
{
public void DoStuff()
{
// Blah
validator.Invoke(..)
}
}
Ahora todo lo que tenemos que hacer es probar que el validador se invoque correctamente. El proceso real de validación (la lógica previamente privada) se puede probar de forma aislada. No será necesario configurar pruebas complejas para garantizar que se apruebe esta validación.