2011-04-06 24 views
5

Estoy tratando de implementar algunas pruebas de unidad para un par de clases que dependen de WifiManager y los resultados devueltos de ScanResults. Lo que me gustaría hacer es poder controlar ScanResults que estoy recibiendo para probar una variedad de condiciones diferentes.Mocking up WifiManager for Android Unit Testing

Desafortunadamente, ha sido bastante difícil para mí simular con éxito WifiManager (aunque supongo que puedo pasar sus referencias nulas de constructor en mi MockWifiManager). Este solo será mi primer problema ya que una vez que tengo un MockWifiManager con el que jugar (si esto funciona) tendré que crear mi prueba ScanResults que no tiene un constructor público (imagine que ha sido creado por alguna fábrica).

Preguntas: Al no tener un constructor público ¿puedo incluso extenderlo?

¿Estoy hablando de algo así? A menudo me hacen preguntas sobre cómo hacer una tarea específica, pero en realidad están tratando de resolver un problema diferente de la manera equivocada, ¿quizás eso es lo que estoy haciendo aquí?

Soy muy nuevo en Android, así que tener que simular toda esta funcionalidad ha estado intentando decir lo menos.

Gracias por sus entradas!

Edit: Estoy teniendo muchísimas instancias instanciando un MockWifiManager también. El constructor para el administrador de wifi espera que un IWifiManager sea un tipo que no parece existir en el SDK de Android.

Respuesta

8

Cree una abstracción alrededor de WifiManager. Use esto para su burla. Burlarse de cosas que no le pertenecen es duro y quebradizo. Si lo haces bien, deberías poder cambiar las partes internas, además de que terminarás con una API simulable mejor.

Para su prueba puede tergiversar/falsificar el administrador a su contenido corazones. Para la producción pasarás en una instancia concreta.

Con respecto a su punto acerca de cambiar su código solo para que sea comprobable que es incorrecto. En primer lugar, debe burlarse de los roles, no los tipos, como se explica en el documento a continuación. Google para más información.

En segundo lugar, crear una abstracción alrededor del código de un tercero es una práctica recomendada según lo establecido por el principio de inversión de dependencia en SOLID. Siempre debe depender de abstracciones en lugar de implementaciones concretas, ya sea que se trate de pruebas unitarias o no.

http://www.objectmentor.com/resources/articles/dip.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

+0

Parece extraño diseñar alrededor de la necesidad de realizar pruebas. ¿Hay otros beneficios para envolver todas las API del sistema que pueda necesitar? – Brian

+0

@Brian sí. En primer lugar, puede cambiar el Administrador de Wifi en un solo lugar. ¿Qué sucede cuando sale la versión x y hay una diferencia en la API? Tendrás que cambiar muchas áreas en tu código base. Estoy actualizando mi respuesta con más información. – Finglas

+0

Ah sí, aísle las cosas que pueden cambiar. No tengo ningún control sobre la API de Android a menos que me proteja con esa abstracción. ¡Buena llamada! – Brian

2

Puede intentar crear las instancias de ScanResult utilizando el reflejo para acceder a los constructores privados. El código podría ser algo como esto:

 try { 
      Constructor<ScanResult> ctor = ScanResult.class.getDeclaredConstructor(null); 
      ctor.setAccessible(true); 
      ScanResult sr = ctor.newInstance(null); 
      sr.BSSID = "foo"; 
      sr.SSID = "bar"; 
      // etc... 
     } catch (SecurityException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (NoSuchMethodException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (IllegalArgumentException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (InstantiationException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (InvocationTargetException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

Para otras formas de pruebas, que la mayoría de veces convertir la información de casos como ScanResult y encapsular solamente la información que necesito en mis propios objetos. Estos me alimentan al método haciendo el trabajo duro. Esto facilita las pruebas ya que puede construir fácilmente estos objetos intermedios sin depender de los objetos reales de ScanResult.

+1

Cada vez que tengo que usar la reflexión, Dios mata a un cachorro. Puede ser la única forma desafortunadamente, gracias por el fragmento de código – Brian

0

he estado luchando por un tiempo para construir ScanResult objeto. He utilizado con éxito ese enfoque de reflexión anterior.

Si alguien está buscando una manera de clonar ScanResult objeto (o cualquier otro objeto que implementa Parcelable interfaz) puede utilizar este enfoque (lo he comprobado justo en una prueba de unidad):

@RunWith(RobolectricTestRunner.class) 
@Config(manifest=Config.NONE) 
public class MovingAverageQueueTests { 
    @Test 
    public void parcelTest() { 
     Parcel parcel = Parcel.obtain(); 

     ScanResult sr = buildScanResult("01:02:03:04:05:06", 70); 

     parcel.writeValue(sr); 
     parcel.setDataPosition(0); // required after unmarshalling 
     ScanResult clone = (ScanResult)parcel.readValue(ScanResult.class.getClassLoader()); 
     parcel.recycle(); 

     assertThat(clone.BSSID, is(equalTo(sr.BSSID))); 
     assertThat(clone.level, is(equalTo(sr.level))); 
     assertThat(clone, is(not(sameInstance(sr)))); 
    } 

    private ScanResult buildScanResult(String mac, int level) { 
     Constructor<ScanResult> ctor = null; 
     ScanResult sr = null; 

     try { 
      ctor = ScanResult.class.getDeclaredConstructor(null); 
      ctor.setAccessible(true); 
      sr = ctor.newInstance(null); 

      sr.BSSID = mac; 
      sr.level = level; 

     } catch (NoSuchMethodException e) { 
      e.printStackTrace(); 
     } catch (InstantiationException e) { 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      e.printStackTrace(); 
     } catch (InvocationTargetException e) { 
      e.printStackTrace(); 
     } 

     return sr; 
    } 
} 

Y en cuanto a rendimiento, este ingenuo de verificación:

@Test 
public void buildVsClonePerformanceTest() { 
    ScanResult sr = null; 

    long start = System.nanoTime(); 
    for (int i = 0; i < 1000000; i++) { 
     sr = buildScanResult("01:02:03:04:05:06", 70); 
    } 
    long elapsedNanos = System.nanoTime() - start; 

    LOGGER.info("buildScanResult: " + elapsedNanos); 

    start = System.nanoTime(); 
    for (int i = 0; i < 1000000; i++) { 
     sr = cloneScanResult(sr); 
    } 
    elapsedNanos = System.nanoTime() - start; 

    LOGGER.info("cloneScanResult: " + elapsedNanos); 
} 

mostrado estos resultados:

Oct 26 de, el año 2016 3:25:19 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFORMACIÓN: buildScanResult: 202072179 26 de Oct, el año 2016 3:25:21 PM com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest INFORMACIÓN: cloneScanResult:

Así clonación de esta manera es 10 veces menos eficaz que la creación de instancia, incluso con la reflexión. Sé que esta prueba no es sólida ya que se realizan optimizaciones durante la compilación ... Sin embargo, el factor de diez es difícil de mitigar. ¡También probé 10K iteraciones y luego el factor fue incluso 100! Solo para tu información.

P.S. obtener Parcel.obtain() y parcel.recycle fuera del ciclo no ayuda