Cuando las pruebas a través del método público comienzan a apestar (ejemplo)

En el artículo sobre la prueba de métodos públicos, me referí a las pruebas unitarias de la lógica de clase privada. Creo que valdría la pena rehacer la tesis, ya que la mayoría, en mi opinión, percibió que estábamos hablando de probar métodos privados, aunque estábamos hablando de lógica privada. En este artículo quiero ilustrar la tesis principal con un ejemplo práctico. Bajo kat un ejemplo con un pequeño análisis.

Ejemplo de stock


Para ilustrar el problema, se me ocurrió un ejemplo. Su idea está tomada de un proyecto real. Idealmente, como alguien podría notar, la clase debería escribirse de manera diferente. Pero ahora nadie lo reescribirá (o refactorizará), porque Esto costará mucho tiempo editar y probar lo que funciona. Por lo tanto, no será aprobado. En este caso, debe cambiar el código y los cambios deben ser correctos. Y entonces, aquí está el código. La lógica importante se concentra en el método ProcessMessage. Objetivo: Introducir una nueva bandera de lógica empresarial en el procesamiento de mensajes. Esta bandera tiene una lógica no trivial.

¿Cómo implementará y probará la bandera?

using System; using System.Threading.Tasks; using System.Threading; using System.Messaging; namespace PublicMethodNottrivialSample { public class MessageProcessorService { private object _lockObject = new object(); private CancellationTokenSource _cancellationSource; private Action _receiveMessage; private int _listenerThreads = 5; public void Start() { lock (_lockObject) { _cancellationSource = new CancellationTokenSource(); Task.Factory.StartNew(() => InitializeReceiveLoop(_cancellationSource.Token), _cancellationSource.Token); } } private void InitializeReceiveLoop(CancellationToken cancellationToken) { _receiveMessage = () => { while (!cancellationToken.IsCancellationRequested) { using (MessageQueueTransaction msgTx = new MessageQueueTransaction()) { try { msgTx.Begin(); //   MessageQueue queue = new MessageQueue(); Message message = queue.Receive(msgTx); //    ,    if (!cancellationToken.IsCancellationRequested) { ProcessMessage(message); } msgTx.Commit(); } catch (Exception ex) { // some logging } } } }; for (int n = 0; n < _listenerThreads; n++) { StartProcessingLoop(cancellationToken); } } private void ProcessMessage(Message message) { //   ,     } private void StartProcessingLoop(CancellationToken cancellationToken) { Task.Factory.StartNew(_receiveMessage, cancellationToken); } } } 

La clase muestra que el único método público es Start (). Puede probarlo si cambia la firma, pero en este caso la interfaz pública cambiará. Además, deberá cambiar varios métodos para devolver subprocesos en ejecución y luego esperar a que se completen en la prueba.

Pero, como recordamos, el requisito se refería solo a la implementación de la bandera en el proceso de procesamiento de mensajes, y no implicaba un cambio en la operación del mecanismo de recepción de mensajes. Por lo tanto, no importa quién haga los cambios, esperaría que solo un método fuera reparado, y la prueba unitaria solo se relacionaría con la lógica mutable. Para lograr esto, permanecer dentro del marco del principio es difícil: la prueba resultará no trivial, lo que implicará una negativa a escribirlo o un código complicado. Así es como las pruebas comienzan a través del método público. Y luego alguien escribirá emocionalmente: "TDD es mucho tiempo", "El cliente no paga" o "Las pruebas no funcionan bien".

En general, es necesario probar dicho código, pero no con pruebas unitarias, sino con pruebas de integración.

"Usted damas, o vaya"


Por supuesto, es necesario escribir una prueba unitaria para la lógica modificada. Creo que el dilema, en este caso, es elegir la forma menos costosa de escribir una prueba, en lugar de un código útil. Aquí me refiero al hecho de que, haga lo que haga: refactorizar para el método público u otra solución, lo hará para escribir una prueba y no resolver el requisito de la tarea del cliente. En este caso, es aconsejable evaluar el costo y el efecto. Además de la solución anterior con refactorización, existen varias soluciones alternativas. Traigo todo, a continuación discutiremos los pros y los contras:

  1. método privado puede ser probado
  2. ¿Se puede hacer público el método?
  3. puede hacerse interno
  4. puede ser arrastrado a una clase separada como método público

Si comparamos los primeros tres métodos con la solución a través del método público, pero todos requieren menos costos laborales (con privado no es un hecho). Además, todos ellos, de hecho, son la misma solución, con ligeras diferencias estilísticas. Porque el resultado se obtendrá de cualquier manera, por lo tanto, en mi opinión, la elección a favor de una de estas soluciones no debe basarse en las capacidades técnicas del lenguaje, sino en lo que desea mostrar a otros desarrolladores:

  1. si el método se puede ejecutar desde cualquier lugar de la solución y es parte del comportamiento de la clase, entonces publíquelo y llévelo a la interfaz de la clase;
  2. si el método puede usarse desde cualquier otra clase, pero solo dentro de este ensamblaje, hágalo internamente ;
  3. si el método solo se puede usar dentro de la clase principal, donde se puede invocar desde cualquier otro método, haga que esté protegido internamente .

Los métodos internos pueden hacerse visibles para el ensamblaje de prueba utilizando el atributo InternalsVisibleTo y llamarse como de costumbre. Este enfoque facilita las pruebas de escritura y el resultado será el mismo.

Proceso de prueba de mensaje


Volvamos a la tarea. Siguiendo el enfoque anterior, haría algunos cambios:

  • haría público ProcessMessage
  • crearía un nuevo método interno protegido para la lógica del indicador (por ejemplo, GetFlagValue)
  • escribiría pruebas para GetFlagValue para todos los casos
  • Escribiría una prueba para ProcessMessage para asegurarme de que GetFlagValue se usa correctamente: los parámetros se pasan correctamente y realmente se usa

Aclararé que no escribiría pruebas unitarias para todos los casos de uso de GetFlagValue en el método ProcessMessage, siempre que probé estos casos en las pruebas unitarias GetFlagValue. En el caso de casos descubiertos, deben agregarse. En este caso, el enfoque principal sigue siendo:

  • todos los casos están cubiertos por las pruebas unitarias GetFlagValue
  • Las pruebas unitarias de ProcessMessage verifican que el método de marca se usa correctamente

Creo que de acuerdo con esto, puede escribir solo una prueba unitaria para ProcessMessage y varias para GetFlagValue.

Algo asi. Sus opiniones

Source: https://habr.com/ru/post/es420567/


All Articles