Prueba unitaria que afirma que el hilo actual es el hilo principal


8

La pregunta es ¿tiene sentido escribir una prueba unitaria que afirme que el hilo actual es el hilo principal? ¿Pros contras?

Recientemente he visto la prueba de la unidad que afirma el hilo actual para la devolución de llamada del servicio. No estoy seguro, es una buena idea, creo que es más una prueba de integración. En mi opinión, la prueba unitaria debe afirmar el método de forma aislada y no debe conocer la naturaleza del consumidor del servicio.

Por ejemplo, en iOS, el consumidor de este servicio está destinado a ser una interfaz de usuario que, por defecto, tiene la restricción de ejecutar un código en el hilo principal.


1
FWIW, en nuestro código, en lugar de convertirlo en una afirmación de prueba unitaria, lo convertimos en una afirmación de tiempo de ejecución, ya que ahí es donde realmente importa.
user1118321

Parece más una afirmación que un caso de prueba real.
whatsisname

Respuestas:


11

Déjame mostrarte mis principios de prueba de unidad favoritos:

Una prueba no es una prueba unitaria si:

  1. Habla a la base de datos
  2. Se comunica a través de la red.
  3. Toca el sistema de archivos
  4. No puede ejecutarse al mismo tiempo que cualquiera de sus otras pruebas unitarias
  5. Tienes que hacer cosas especiales en tu entorno (como editar archivos de configuración) para ejecutarlo.

Un conjunto de reglas de prueba de unidad - Michael Feathers

Si su prueba de unidad insiste en que es el "hilo principal", es difícil no entrar en conflicto con el número 4.

Esto puede parecer duro, pero recuerde que una prueba unitaria es solo uno de los muchos tipos diferentes de pruebas.

ingrese la descripción de la imagen aquí

Eres libre de violar estos principios. Pero cuando lo haga, no llame a su prueba una prueba unitaria. No lo pongas con las pruebas unitarias reales y ralentízalas.


¿Cómo prueba la unidad de un registrador? ¿Tienes que moc IO? Eso parece desagradable.
cuando

1
@opa si su registrador tiene una lógica comercial interesante, la aísla y la prueba unitaria. El código aburrido que confirma que tenemos un sistema de archivos no necesita pruebas unitarias.
candied_orange

@candied_orange ¿Entonces ahora tienes que exponer la lógica interna? Porque, ¿de qué otra manera va a separar la cadena generada por el registrador antes de que se envíe a un archivo separándolo en un método privado ?
cuando

@opa Si necesita probar que puede hacerlo, pase su propia secuencia. Las pruebas unitarias prueban unidades no recursos del sistema
candied_orange

@candied_orange, excepto cuando el registrador solo toma el nombre de archivo al que debe enviar como entrada. El punto no es que "las unidades de prueba de las unidades de prueba no son los recursos del sistema" está mal, es 100% correcto. No puede ser tan dogmático como para decir cuando las pruebas tocan el archivo io que no es una prueba de unidad válida. Probar un registrador mediante la lectura de la salida de su archivo es mucho más fácil que hacer pública la funcionalidad privada por el simple hecho de realizar pruebas unitarias, lo que realmente nunca debería hacer . No está probando el IO, está probando el registrador, simplemente sucede que la forma más fácil de hacerlo es leer el archivo si aún desea un código limpio.
cuando

5

¿Debería escribir una prueba unitaria que pruebe si un hilo es el hilo principal? Depende, pero probablemente no sea una buena idea.

Si el objetivo de una prueba unitaria es verificar el correcto funcionamiento de un método, entonces probar este aspecto casi con certeza no es probar este aspecto. Como usted mismo dijo, conceptualmente es más una prueba de integración, ya que no es probable que el funcionamiento adecuado de un método cambie según el hilo que lo está ejecutando, y el responsable de determinar qué hilo se usa es la persona que llama y no método en sí mismo.

Sin embargo, es por eso que digo que depende, porque puede muy bien depender del método en sí mismo, si el método genera nuevos hilos. Aunque con toda probabilidad este no es tu caso.

Mi consejo sería dejar esta comprobación fuera de la prueba de la unidad y transferirla a una prueba de integración adecuada para este aspecto.


2

no debe saber sobre la naturaleza del consumidor del servicio.

Cuando un consumidor consume un servicio, hay un "contrato" expreso o implícito sobre qué funcionalidad se proporciona y cómo. Las restricciones sobre el hilo en el que puede ocurrir la devolución de llamada son parte de ese contrato.

Supongo que su código se ejecuta en un contexto donde hay algún tipo de "motor de eventos" que se ejecuta en el "hilo principal" y una forma para que otros hilos soliciten que el código de programación del motor de eventos se ejecute en el hilo principal.

En la vista más pura de las pruebas unitarias, se burlaría absolutamente de cada dependencia. En el mundo real, muy pocas personas hacen eso. El código bajo prueba tiene permitido usar las versiones reales de al menos las características básicas de la plataforma.

Entonces, la verdadera pregunta de la OMI es si considera que el motor de eventos y el soporte de subprocesos de tiempo de ejecución son parte de las "características básicas de la plataforma" de las que las pruebas unitarias pueden depender o no. Si lo hace, tiene mucho sentido comprobar si se produce una devolución de llamada en el "hilo principal". Si se está burlando del motor de eventos y del sistema de subprocesos, entonces diseñaría sus simulacros para que puedan determinar si la devolución de llamada habría ocurrido en el subproceso principal. Si se está burlando del motor de eventos pero no del soporte de subprocesos de tiempo de ejecución, querrá probar si la devolución de llamada ocurre en el hilo donde se ejecuta el motor de eventos simulados.


1

Puedo ver por qué sería una prueba atractiva. Pero, ¿qué sería realmente probar? decimos que tenemos la función

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

Ahora puedo probar esto y ejecutarlo en un subproceso sin interfaz de usuario, lo que demuestra que se produce un error. Pero realmente para que esa prueba tenga algún valor, la función necesita tener un comportamiento específico que desee que se haga cuando se ejecuta en un subproceso que no es UI.

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

Ahora tengo algo que probar, pero no está claro que este tipo de alternativa sea útil en la vida real.


1

Si se documenta que la devolución de llamada no es segura para subprocesos para invocar en cualquier otro que no sea, por ejemplo, el subproceso de interfaz de usuario o el subproceso principal, entonces parece perfectamente razonable en una prueba unitaria de un módulo de código que hace que la invocación de esta devolución de llamada se asegure no está haciendo algo que no sea seguro para subprocesos al trabajar fuera del subproceso que el sistema operativo o el marco de la aplicación está documentado para garantizar la seguridad.


1

Organizar
Actuar
Afirmar

¿Debería una prueba unitaria afirmar que está en un hilo específico? No, porque no está probando una unidad en ese caso, ni siquiera es una prueba de integración, ya que no dice nada sobre la configuración que tiene lugar en la unidad ...

Afirmar en qué subproceso está la prueba, sería como Afirmar el nombre de su servidor de prueba, o mejor aún, el nombre del método de prueba.

Ahora, puede tener sentido organizar la prueba para que se ejecute en un subproceso específico, pero solo cuando desee asegurarse de que devolverá un resultado específico en función del subproceso.


1

Si tiene una función documentada que solo se ejecuta correctamente en el subproceso principal, y esa función se llama en un subproceso en segundo plano, ese no es el error de las funciones. La falla es culpa de la persona que llama, por lo que las pruebas unitarias no tienen sentido.

Si se modifica el contrato para que la función se ejecute correctamente en el subproceso principal e informe un error cuando se ejecuta en un subproceso en segundo plano, puede escribir una prueba unitaria para eso, donde lo llama una vez en el subproceso principal y luego en un subproceso de fondo y compruebe que se comporta como se documenta. Entonces, ahora tiene una prueba unitaria, pero no una prueba unitaria para verificar que se ejecuta en el subproceso principal.

Sugeriría mucho que la primera línea de su función debería afirmar que se ejecuta en el hilo principal.


1

La prueba unitaria solo es útil si está probando la implementación correcta de dicha función, no el uso correcto, porque una prueba unitaria no puede decir que una función no se llamará desde otro hilo en el resto de su base de código, tal como puede ' Diga que las funciones no se llamarán con parámetros que violen sus condiciones previas (y técnicamente lo que está tratando de probar es básicamente una violación de una condición previa en el uso, que es algo contra lo que no puede probar efectivamente porque la prueba puede ' t restrinja cómo otros lugares en la base de código usan tales funciones; sin embargo, puede probar si las violaciones de las condiciones previas resultan en errores / excepciones apropiados.

Este es un caso de afirmación para mí en la implementación de las funciones relevantes como otros han señalado, o incluso más sofisticado es asegurarse de que las funciones sean seguras para subprocesos (aunque esto no siempre es práctico cuando se trabaja con algunas API).

También solo una nota al margen, pero "main thread"! = "UI thread" en todos los casos. Muchas API GUI no son seguras para subprocesos (y hacer un kit GUI seguro para subprocesos es muy difícil), pero eso no significa que tenga que invocarlas desde el mismo subproceso que tiene el punto de entrada para su aplicación. Eso podría ser útil incluso al implementar sus afirmaciones dentro de las funciones relevantes de la interfaz de usuario para distinguir el "hilo de la interfaz de usuario" del "hilo principal", como capturar el ID del hilo actual cuando se crea una ventana para comparar en lugar de desde el punto de entrada principal de la aplicación (que al menos reduce la cantidad de supuestos / restricciones de uso que la implementación aplica solo a lo que es realmente relevante).

La seguridad de los subprocesos era en realidad el punto de tropiezo de un antiguo equipo mío, y en nuestro caso particular lo habría calificado como la "microoptimización" más contraproducente de todos ellos, de un tipo que generó más costos de mantenimiento que incluso Asamblea manuscrita. Tuvimos una cobertura de código bastante completa en nuestras pruebas unitarias, junto con pruebas de integración bastante sofisticadas, solo para encontrar puntos muertos y condiciones de carrera en el software que eludió nuestras pruebas. Y eso se debió a que los desarrolladores de código multiproceso al azar sin ser conscientes de todos los efectos secundarios que podrían ocurrir en la cadena de llamadas de funciones que resultarían de los suyos, con una idea bastante ingenua de que podrían solucionar esos errores en retrospectiva simplemente lanzando se cierra a izquierda y derecha,

Estaba sesgado en la dirección opuesta, ya que un tipo de la vieja escuela que desconfiaba de los subprocesos múltiples, era un recién llegado a adoptarlo, y pensé que la corrección supera el rendimiento hasta el punto de que rara vez se usen todos estos núcleos que tenemos ahora, hasta que descubrí cosas como funciones puras y diseños inmutables y estructuras de datos persistentes que finalmente me permitieron utilizar completamente ese hardware sin preocuparme en el mundo por las condiciones de carrera y los puntos muertos. Debo admitir que hasta 2010, más o menos, odié el subprocesamiento múltiple con pasión, excepto por algunos bucles paralelos aquí y allá en áreas que son triviales para razonar sobre la seguridad de los hilos, y favorecí mucho más código secuencial para el diseño de productos dado mi dolor con multihilo en antiguos equipos.

Para mí, esa forma de multiprocesar primero y corregir errores más tarde es una estrategia terrible para multiprocesar hasta el punto de casi hacerme odiar la multiproceso inicialmente; puede asegurarse de que sus diseños sean seguros para subprocesos y que sus implementaciones solo utilicen funciones con garantías similares (por ejemplo, funciones puras), o evite el subprocesamiento múltiple. Eso puede parecer un poco dogmático, pero supera descubrir (o peor, no descubrir) problemas difíciles de reproducir en retrospectiva que eluden las pruebas. No tiene sentido optimizar un motor de cohete si eso va a hacer que sea propenso a explotar inesperadamente a mitad de camino a lo largo de su viaje hacia el espacio.

Si inevitablemente tiene que trabajar con código que no es seguro para subprocesos, entonces no lo veo como un problema para resolver con las pruebas de unidad / integración. Lo ideal sería restringir el acceso . Si su código GUI está desacoplado de la lógica empresarial, es posible que pueda aplicar un diseño que restrinja el acceso a dichas llamadas desde cualquier cosa que no sea el hilo / objeto que lo crea *. Ese modo lejano ideal para mí es hacer que sea imposible para otros hilos llamar a esas funciones que intentar asegurarse de que no lo hagan.

  • Sí, me doy cuenta de que siempre hay formas de evitar las restricciones de diseño que usted impone, en general, donde el compilador no puede protegerlo. Solo estoy hablando prácticamente; si puede abstraer el objeto "GUI Thread" o lo que sea, entonces podría ser el único que entregó un parámetro a los objetos / funciones GUI, y podría restringir que ese objeto tenga acceso a otros hilos. Por supuesto, podría ser capaz de evitar y cavar profundamente y abrirse camino a través de dichos aros para pasar dichas funciones / objetos GUI a otros hilos para invocar, pero al menos hay una barrera allí, y puede llamar a cualquiera que lo haga un "idiota" ", y no estar equivocado, al menos, por pasar por alto y buscar vacíos en lo que el diseño obviamente intentaba restringir. :-D Eso '
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.