unit testing - unitarias - ¿Cuál es la mejor manera de probar el código de Erlang concurrente?
test unitarios java (6)
Paso un poco de tiempo con Erlang, y quiero aplicar TDD al código que estoy escribiendo.
Mientras que EUnit en el lib estándar proporciona un buen marco de pruebas de unidades tradicionales para probar el código de estilo regular, no parece haber nada que ayude específicamente a probar el código concurrente, que se usa MUCHO en Erlang.
Tenga en cuenta que estamos hablando aquí de Erlang, que utiliza el paso de mensajes (en oposición al estado compartido) para la comunicación entre procesos concurrentes, por lo que las técnicas para probar unidades de código concurrente en lenguajes de estado compartidos pueden no ser aplicables.
¿Alguien encontró una buena manera de probar el código concurrente en Erlang?
Agregando a lo que dijo Diomidis, la única manera real de ganar algo de confianza con su código simultáneo es ejecutar pruebas extendidas bajo condiciones constantemente variables; e incluso entonces, es solo una prueba de que no falló en esas condiciones .
Debido a que los errores en el código simultáneo se manifiestan según el orden en que se ejecutan varias partes, creo que es mejor no confiar en las pruebas para descubrir los errores en la parte simultánea de su código. Esos errores son muy fáciles de deslizar y extremadamente difíciles de localizar. Vea, por ejemplo, cómo la técnica de doble bloqueo, que fue ampliamente citada y utilizada como un método eficiente para implementar la inicialización lenta en un entorno multiproceso, más tarde se descubrió que estaba rota . Es mucho mejor utilizar abstracciones y técnicas apropiadas para hacer que las partes simultáneas de su código sean correctas "por diseño".
Puede aislar la parte que le gustaría probar con los simulacros al usar esto: http://github.com/charpi/erl_mock/tree/master
No tengo experiencia con erl_mock.
La única herramienta que conozco que puede usar para probar los programas de Erlang en escenarios concurrentes es QuickCheck: http://www.quviq.com/
Puede especificar su comportamiento esperado utilizando propiedades, y la herramienta puede ejercitar su programa Erlang sobre un rango de entradas y tiempo para asegurar que las propiedades estén satisfechas.
Desafortunadamente es una herramienta comercial
También eche un vistazo al proyecto ProTest: http://www.protest-project.eu
Actualización: El proyecto ProTest publicó una encuesta, "Resultados para: encuesta de herramientas de prueba Erlang para el proyecto ProTest"
La pregunta es un poco vaga ("Erlang es concurrente, ¡pruébalo con Erlang!") Pero intentaré explayarme un poco.
Probar el código de Erlang puede ir desde lo directamente simple (la entrada correcta produce el resultado correcto) hasta la configuración de arneses de prueba complejos que verifican que su componente se comporte de la manera que debería. Lo mejor para su situación dada depende totalmente de los requisitos que tenga y de la cantidad de pruebas de caja negra / caja blanca que desee hacer.
Parte de la belleza de Erlang es la capacidad de hacer que la concurrencia sea transparente. Considere el siguiente ejemplo (una función que paraleliza la suma de una lista de listas):
deep_sum(ListOfLists) ->
Parent = self(),
[spawn(fun() -> Parent ! lists:sum(List) end) || List <- ListOfLists],
lists:sum([receive Sum -> Sum end || _ <- ListOfLists]).
Por lo general, probaría esto con un caso de prueba EUnit muy simple:
deep_sum_test() ->
?assertEqual(0, deep_sum([0, 0, 0, 0])),
?assertEqual(40, deep_sum([10, 10, 10, 10]).
Ahora, digamos que tenemos una API un poco más explícita para esta funcionalidad: un grupo de procesos como argumento:
deep_sum(Pool, ListOfLists) ->
distribute_lists(Pool, ListOfLists),
lists:sum([receive Sum -> Sum end || _ <- ListOfLists]).
distribute_lists(Pool, ListOfLists) -> distribute_lists(Pool, Pool, ListOfLists).
distribute_lists([P|Pool], All, [L|ListOfLists]) ->
P ! {self(), L},
distribute_lists(Pool, All, ListOfLists);
distribute_lists([], All, ListOfLists) ->
distribute_lists(All, All, ListOfLists);
distribute_lists(_Pool, _All, []) ->
ok.
Al probar esto, tenemos que lidiar con la falsificación de este grupo de procesos:
deep_sum_test() ->
Pool = [spawn_link(fun() -> fake_pool(1) end) || _ <- lists:seq(1, 3)],
?assertEqual(4, deep_sum(Pool, [lists:seq(1, 3) || _ <- list:seq(1, 4)]),
?assertEqual(7, deep_sum(Pool, [lists:seq(1, 3) || _ <- list:seq(1, 7)]),
[P ! stop || P <- Pool].
fake_pool(CannedResponse) ->
receive
{From, _L} -> From ! CannedResponse;
stop -> ok
end,
fake_pool(CannedResponse).
Como puede ver, probar los programas de concurrencia en Erlang puede tomar diferentes formas. Estos son ejemplos extremadamente simples, pero con las primitivas de simultaneidad incorporadas de Erlang es muy fácil crear el tipo de arnés de prueba que desee, abstrayendo en los niveles correctos.
Por lo general, considero que TDD es ortogonal, ya sea que esté probando el código concurrente o no, por lo que dichas técnicas de prueba también se pueden usar para las pruebas de unidades normales.
Acabo de encontrar un software recientemente desarrollado (a partir de 2011) para probar aplicaciones concurrentes de Erlang llamadas Concuerror . Hay algunos documentos y un repositorio en github . Aparentemente funciona usando su propio programador y prueba sistemáticamente diferentes intercalaciones entre los procesos.
También vale la pena mencionar es Dialyzer (DIscrepancy AnaLYZer para ERlang) ( Documentos , Tutorial , Manual ), que es una herramienta para el análisis estático del código para encontrar errores. Esto también tiene soporte para detectar algunos errores de concurrencia (ver el documento ).
No he probado ninguno de estos, aunque el dializador parece ser un software relativamente maduro. Ambos programas tienen una GUI para trabajar con las pruebas.
PD. Para las partes no simultáneas, EUnit y QuickCheck (también hay una versión gratuita) deberían funcionar bien.