Hola Habr!
Trabajo en un gran proyecto de integración (IBM WAS, WebSphere MQ, Oracle) y envuelvo a nuestra empresa sangrienta con una red de pruebas funcionales en JMeter, que se ejecuta en un banco de pruebas y se despierta a la llamada de Jenkins después de la implementación de la nueva compilación. A medida que aumentaba el número de pruebas, me encontré con el problema de mantener actualizada la documentación de las pruebas.
El árbol de prueba en sí mismo en JMeter es esencialmente un documento: las operaciones dividen la funcionalidad en partes lógicas, los controladores contienen pruebas dentro de las operaciones y cada muestra dentro del controlador es un paso separado. La jerarquía de los objetos está claramente numerada, con la excepción de piezas de servicio como afirmaciones, temporizadores y otras cosas menos interesantes desde el punto de vista de la lógica empresarial.
El resultado es una imagen bastante precisa:

Sin embargo, no todos los gerentes están listos para lanzar JMeter en casa para ver la situación en el campo del control de calidad. Históricamente, toda la documentación del proyecto se mantiene en Confluence.
No estaba listo para copiar manualmente la descripción de los casos de prueba a la página Confluence después de desarrollarlos en JMeter. La búsqueda desesperada de Google no dio resultado: no encontré una solución fácil y lista para exportar un árbol de objetos de JMeter a texto ( si hay uno, escríbalo en los comentarios, por favor, y rociare mi cabeza con el widget "I can google" ).
Después de mirar dentro del archivo JMX (la extensión estándar del plan de prueba JMeter), descubrí que todos los objetos que me interesan estaban marcados con el atributo testname :
Ejemplo de segmento de archivo JMX<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="1.4.2 " enabled="true"> <collectionProp name="AuthManager.auth_list"> <elementProp name="" elementType="Authorization"> <stringProp name="Authorization.url">http://${ipKvp}:${portKvp}/TKVPImportTemporary</stringProp> <stringProp name="Authorization.username">${userKvp}</stringProp> <stringProp name="Authorization.password">${passKvp}</stringProp> <stringProp name="Authorization.domain">${domainKvp}</stringProp> <stringProp name="Authorization.realm"></stringProp> </elementProp> </collectionProp> <boolProp name="AuthManager.clearEachIteration">true</boolProp> </AuthManager>
Lo único que queda es escribir un analizador, que:
- El texto deseado con la descripción del paso \ prueba \ grupo del archivo JMX
- Lanza líneas con la descripción de objetos no interesantes (afirmados, temporizadores, etc.)
- Escribirá todo en un archivo para que la actualización del documento incluya un solo pegar y copiar
El párrafo 1 fue manejado con éxito por la expresión regular:
(? <= testname = \ ") (. *) (? = \")
El reflejo de no usar el xpath adquirido en el proceso de escribir selectores para las pruebas de selenio me salvó de usar el selector xpath.
Como no numeré los objetos de servicio en el árbol, el elemento 2 se implementó sin problemas en un bucle en el que:
- Me sale el primer caracter de la cadena
- Traigo a int
- si tiene éxito, escriba una línea en la lista
- de lo contrario ignorar
try (BufferedReader br = new BufferedReader(new FileReader(JMX_FILE))) { String line; while ((line = br.readLine()) != null) { Matcher m1 = p.matcher(line); if (m1.find()) { try { Integer.parseInt(m1.group().substring(0, 1)); matchd.add(m1.group()); } catch (NumberFormatException e) { System.out.println(m1.group().substring(0, 1) + ": excluding non-number string"); } } } }
Y dado que el archivo se procesa sucesivamente de arriba a abajo + la numeración de los objetos en el árbol sigue una lógica clara, tampoco fue necesario inventar nada terrible para el elemento 3:
FileWriter writer = null; try { writer = new FileWriter(RESULT_FILE); for (String str : matchd) { writer.write(str + "\n"); } } finally { if (writer != null) { writer.close(); } }
El resultado final cabe en una clase pequeña (~ 50 líneas):
Código fuente import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class App { private static final String SAMPLER_NAME_REGEXP = "(?<=testname=\")(.*)(?=\" )"; private static final File JMX_FILE = new File("C:\\temp\\Test-plan.jmx"); private static final File RESULT_FILE = new File("C:\\temp\\output.txt"); public static void main(String[] args) throws IOException { Pattern p = Pattern.compile(SAMPLER_NAME_REGEXP); List<String> matchd = new ArrayList<>(); try (BufferedReader br = new BufferedReader(new FileReader(JMX_FILE))) { String line; while ((line = br.readLine()) != null) { Matcher m1 = p.matcher(line); if (m1.find()) { try { Integer.parseInt(m1.group().substring(0, 1)); matchd.add(m1.group()); } catch (NumberFormatException e) { System.out.println(m1.group().substring(0, 1) + ": excluding non-number string"); } } } } if (RESULT_FILE.delete()) { System.out.println("Deleting previous result file"); } else { System.out.println("Creating new result file"); } FileWriter writer = null; try { writer = new FileWriter(RESULT_FILE); for (String str : matchd) { writer.write(str + "\n"); } } finally { if (writer != null) { writer.close(); } } } }
Como experimento, intenté integrar este código directamente en el plan de prueba de JMeter, pero me topé con problemas de malentendidos genéricos e importaciones, y hasta ahora decidí estar satisfecho con llamar al exportador de árboles resultante en IDEA.
Cuida tu tiempo. Y gracias por mirar.