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.
architecture
unit-testing
object-oriented-design
multithreading
ios
Vladimir Kaltyrin
fuente
fuente
Respuestas:
Déjame mostrarte mis principios de prueba de unidad favoritos:
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.
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.
fuente
¿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.
fuente
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.
fuente
Puedo ver por qué sería una prueba atractiva. Pero, ¿qué sería realmente probar? decimos que tenemos la función
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.
Ahora tengo algo que probar, pero no está claro que este tipo de alternativa sea útil en la vida real.
fuente
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.
fuente
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.
fuente
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.
fuente
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,
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.
fuente