java - test - nunit is a code coverage tool.
¿Cómo agregar cobertura de prueba a un constructor privado? (15)
Este es el código:
package com.XXX;
public final class Foo {
private Foo() {
// intentionally empty
}
public static int bar() {
return 1;
}
}
Esta es la prueba:
package com.XXX;
public FooTest {
@Test
void testValidatesThatBarWorks() {
int result = Foo.bar();
assertEquals(1, result);
}
@Test(expected = java.lang.IllegalAccessException.class)
void testValidatesThatClassFooIsNotInstantiable() {
Class cls = Class.forName("com.XXX.Foo");
cls.newInstance(); // exception here
}
}
Funciona bien, la clase está probada. Pero Cobertura dice que no hay cobertura de código cero del constructor privado de la clase. ¿Cómo podemos agregar cobertura de prueba a un constructor privado?
¡Finalmente, hay una solución!
public enum Foo {;
public static int bar() {
return 1;
}
}
A veces, Cobertura marca un código que no debe ejecutarse como ''no cubierto'', no hay nada de malo en eso. ¿Por qué te preocupa tener un 99%
cobertura en lugar de un 100%
?
Técnicamente, sin embargo, todavía puedes invocar ese constructor con reflexión, pero me suena muy mal (en este caso).
Aunque no es necesariamente para la cobertura, creé este método para verificar que la clase de utilidad esté bien definida y cubra un poco también.
/**
* Verifies that a utility class is well defined.
*
* @param clazz
* utility class to verify.
*/
public static void assertUtilityClassWellDefined(final Class<?> clazz)
throws NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException {
Assert.assertTrue("class must be final",
Modifier.isFinal(clazz.getModifiers()));
Assert.assertEquals("There must be only one constructor", 1,
clazz.getDeclaredConstructors().length);
final Constructor<?> constructor = clazz.getDeclaredConstructor();
if (constructor.isAccessible() ||
!Modifier.isPrivate(constructor.getModifiers())) {
Assert.fail("constructor is not private");
}
constructor.setAccessible(true);
constructor.newInstance();
constructor.setAccessible(false);
for (final Method method : clazz.getMethods()) {
if (!Modifier.isStatic(method.getModifiers())
&& method.getDeclaringClass().equals(clazz)) {
Assert.fail("there exists a non-static method:" + method);
}
}
}
He colocado el código completo y ejemplos en https://github.com/trajano/maven-jee6/tree/master/maven-jee6-test
Bueno, hay formas en las que podrías usar el reflejo, etc., pero ¿realmente lo vale? Este es un constructor que nunca debería llamarse , ¿verdad?
Si hay una anotación o algo similar que pueda agregar a la clase para que Cobertura comprenda que no se llamará, haga eso: no creo que valga la pena pasar por aros para agregar cobertura artificialmente.
EDITAR: Si no hay forma de hacerlo, solo vive con una cobertura ligeramente reducida. Recuerde que la cobertura debe ser algo útil para usted : debe estar a cargo de la herramienta, y no al revés.
Con Java 8 , es posible encontrar otra solución.
Supongo que simplemente desea crear clases de utilidad con pocos métodos estáticos públicos. Si puede usar Java 8, puede usar la interface
lugar.
package com.XXX;
public interface Foo {
public static int bar() {
return 1;
}
}
No hay constructor y ninguna queja de Cobertura. Ahora necesita probar solo las líneas que realmente le interesan.
El razonamiento detrás del código de prueba que no hace nada es lograr una cobertura de código del 100% y observar cuándo cae la cobertura del código. De lo contrario, siempre se podría pensar, hey, ya no tengo cobertura de código 100%, pero PROBABLEMENTE es debido a mis constructores privados. Esto hace que sea fácil detectar los métodos no probados sin tener que comprobar que solo era un constructor privado. A medida que crezca su base de código, sentirá una agradable sensación de calor observando el 100% en lugar del 99%.
OMI es mejor utilizar la reflexión aquí ya que de lo contrario tendrías que obtener una mejor herramienta de cobertura de código que ignore estos constructores o de alguna manera decirle a la herramienta de cobertura de código que ignore el método (tal vez una Anotación o un archivo de configuración) porque entonces estarías estancado con una herramienta de cobertura de código específica.
En un mundo perfecto, todas las herramientas de cobertura de código ignorarían los constructores privados que pertenecen a una clase final porque el constructor está allí como una medida de "seguridad" nada más :)
Yo usaría este código:
@Test
public void callPrivateConstructorsForCodeCoverage() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
{
Class<?>[] classesToConstruct = {Foo.class};
for(Class<?> clazz : classesToConstruct)
{
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
assertNotNull(constructor.newInstance());
}
}
Y luego simplemente agregue clases a la matriz sobre la marcha.
Hice privado el constructor de mi clase de funciones de utilidad estática, para satisfacer CheckStyle. Pero al igual que el póster original, Cobertura se quejaba de la prueba. Al principio probé este enfoque, pero esto no afecta el informe de cobertura porque el constructor nunca se ejecuta realmente. Entonces, realmente todas estas pruebas son si el constructor permanece privado, y esto se vuelve redundante con la verificación de accesibilidad en la prueba subsiguiente.
@Test(expected=IllegalAccessException.class)
public void testConstructorPrivate() throws Exception {
MyUtilityClass.class.newInstance();
fail("Utility class constructor should be private");
}
Seguí la sugerencia de Javid Jamae y utilicé la reflexión, pero agregué aserciones para atrapar a cualquiera que se metiera con la clase que se estaba probando (y la denominé para indicar High Levels Of Evil).
@Test
public void evilConstructorInaccessibilityTest() throws Exception {
Constructor[] ctors = MyUtilityClass.class.getDeclaredConstructors();
assertEquals("Utility class should only have one constructor",
1, ctors.length);
Constructor ctor = ctors[0];
assertFalse("Utility class constructor should be inaccessible",
ctor.isAccessible());
ctor.setAccessible(true); // obviously we''d never do this in production
assertEquals("You''d expect the construct to return the expected type",
MyUtilityClass.class, ctor.newInstance().getClass());
}
Esto es demasiado exagerado, pero debo admitir que me gusta la sensación cálida y difusa de la cobertura del método al 100%.
Las versiones más nuevas de Cobertura tienen soporte integrado para ignorar los captadores / definidores / constructores triviales:
https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial
Ignorar Trivial
Ignorar trivial permite la capacidad de excluir constructores / métodos que contienen una línea de código. Algunos ejemplos incluyen una llamada a un superconstructor solamente, métodos getter / setter, etc. Para incluir el argumento ignorar trivial, agregue lo siguiente:
<cobertura-instrument ignoreTrivial="true" />
o en una construcción de Gradle:
cobertura {
coverageIgnoreTrivial = true
}
No conozco Cobertura pero uso Clover y tiene un medio para agregar exclusiones de coincidencia de patrones. Por ejemplo, tengo patrones que excluyen las líneas de apache-commons-logging para que no se cuenten en la cobertura.
No estoy del todo de acuerdo con Jon Skeet. Creo que si puede obtener una ganancia fácil para darle cobertura y eliminar el ruido en su informe de cobertura, entonces debe hacerlo. Dígale a su herramienta de cobertura que ignore al constructor, o deje de lado el idealismo y escriba la siguiente prueba y termine con esto:
@Test
public void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<Foo> constructor = Foo.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
constructor.setAccessible(true);
constructor.newInstance();
}
No lo hagas ¿Cuál es el punto de probar un constructor vacío? Desde cobertura 2.0 hay una opción para ignorar casos tan triviales (junto con setters / getters), puedes habilitarlo en maven agregando la sección de configuración al plugin de cobertura maven:
<configuration>
<instrumentation>
<ignoreTrivial>true</ignoreTrivial>
</instrumentation>
</configuration>
Alternativamente, puede usar Anotaciones de cobertura : @CoverageIgnore
.
No puedes.
Aparentemente está creando el constructor privado para evitar la creación de instancias de una clase que solo contenga métodos estáticos. En lugar de tratar de obtener cobertura de este constructor (lo que requeriría que la clase sea instanciada), debería deshacerse de ella y confiar en que sus desarrolladores no agreguen métodos de instancia a la clase.
Otra opción es crear un inicializador estático similar al siguiente código
class YourClass {
private YourClass() {
}
static {
new YourClass();
}
// real ops
}
De esta forma, el constructor privado se considera probado, y la sobrecarga de tiempo de ejecución básicamente no se puede medir. Hago esto para obtener una cobertura del 100% con EclEmma, pero es probable que funcione para todas las herramientas de cobertura. El inconveniente de esta solución, por supuesto, es que se escribe el código de producción (el inicializador estático) solo para fines de prueba.
Si tuviera que adivinar el propósito de su pregunta, diría:
- Desea comprobaciones razonables para los constructores privados que hacen un trabajo real, y
- Desea que trébol excluya constructores vacíos para clases de utilidad.
Para 1, es obvio que desea que toda la inicialización se realice a través de los métodos de fábrica. En tales casos, sus pruebas deberían poder probar los efectos secundarios del constructor. Esto debería caer dentro de la categoría de prueba de método privado normal. Haga los métodos más pequeños para que solo hagan un número limitado de cosas determinadas (idealmente, una cosa y una cosa bien) y luego pruebe los métodos que dependen de ellos.
Por ejemplo, si mi constructor [privado] configura los campos de instancia de mi clase de a
a 5
. Entonces puedo (o más bien debo) probarlo:
@Test
public void testInit() {
MyClass myObj = MyClass.newInstance(); //Or whatever factory method you put
Assert.assertEquals(5, myObj.getA()); //Or if getA() is private then test some other property/method that relies on a being 5
}
Para 2, puede configurar trébol para excluir constructores Util si tiene un patrón de nomenclatura establecido para clases de Util. Por ejemplo, en mi propio proyecto utilizo algo como esto (porque seguimos la convención de que los nombres de todas las clases de Util deberían terminar con Util):
<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
<methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
</clover-setup>
He omitido deliberadamente un .*
Siguiente )
porque dichos constructores no deben arrojar excepciones (no están destinados a hacer nada).
Por supuesto, puede haber un tercer caso en el que desee tener un constructor vacío para una clase que no sea de utilidad. En tales casos, recomendaría que coloque un methodContext
con la firma exacta del constructor.
<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
<methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
<methodContext name="myExceptionalClassCtor" regexp="^private MyExceptionalClass()$"/>
</clover-setup>
Si tiene muchas clases excepcionales, puede elegir modificar el registro de constructor privado generalizado que sugerí y eliminar Util
de él. En este caso, deberá asegurarse manualmente de que los efectos colaterales de su constructor aún sean probados y cubiertos por otros métodos en su clase / proyecto.
<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
<methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+ *( *) .*"/>
</clover-setup>
@Test
public void testTestPrivateConstructor() {
Constructor<Test> cnt;
try {
cnt = Test.class.getDeclaredConstructor();
cnt.setAccessible(true);
cnt.newInstance();
} catch (Exception e) {
e.getMessage();
}
}
Test.java es tu archivo fuente, que está teniendo tu constructor privado