Привет, Хабр!
Работаю на большом интеграционном проекте (IBM WAS, WebSphere MQ, Oracle) и оплетаю наш кровавый энтерпрайз паутиной функциональных тестов в JMeter, который крутится на тестовом стенде и пробуждается по зову Jenkins после деплоя нового билда. По мере увеличения количества тестов столкнулся с проблемой поддержания тестовой документации в актуальном виде.
Само дерево тестов в JMeter по сути является документом — трэды разбивают функциональность на логические куски, внутри трэдов контроллеры содержат тесты, а каждый сэмплер внутри контроллера — отдельный шаг. Иерархия объектов четко пронумерована, за исключением служебных штук навроде ассершенов, таймеров и прочего менее интересного с точки зрения бизнес-логики.
В итоге получается достаточно аккуратная картинка:

Впрочем, не каждый менеджер готов запустить у себя JMeter для просмотра положения дел в области QA. Исторически сложилось, что вся документация проекта ведется в Confluence.
Я не был готов вручную копипастить описание тест-кейсов на страницу Confluence после разработки их в JMeter. Отчаянные гугления не дали результата — не обнаружил готового и легкого решения для экспорта дерева объектов из JMeter в текст (если таки есть, напиши о нем в комментарии, пожалуйста, а я посыплю голову пеплом из ачивки "умею в гугл").
Заглянув во внутренности JMX-файла (стандартное расширение тест-плана JMeter), обнаружил, что все интересные мне объекты отмечены атрибутом testname:
<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>
Осталось дело за малым — написать парсер, который:
- Добудет желанный текст с описанием шага\теста\группы из JMX-файла
- Выкинет строки с описанием неинтересных объектов (ассершены, таймеры и прочее)
- Запишет всё по порядку в файл, чтобы актуализация документа включала один сиротливый копипаст
С пунктом1 успешно справилось регулярное выражение:
(?<=testname=\")(.*)(?=\" )
От использования xpath-селектора меня уберег рефлекс не использовать xpath, приобретенный в процессе написания селекторов для Selenium-тестов.
Так как я не нумеровал служебные объекты в дереве, пункт2 удалось реализовать без проблем в цикле, в котором:
- достаю первый символ строки
- привожу к int
- в случае успеха записываю строку в список
- иначе игнорирую
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"); } } } }
И поскольку обработка файла идет подряд сверху вниз + нумерация объектов в дереве подчиняется четкой логике, ничего страшного для пункта3 также придумывать не пришлось:
FileWriter writer = null;
try {
writer = new FileWriter(RESULT_FILE);
for (String str : matchd) {
writer.write(str + "\n");
}
} finally {
if (writer != null) {
writer.close();
}
}
Итоговый результат уместился в один маленький (~50 строк) класс:
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();
}
}
}
}
В порядке эксперимента пробовал интегрировать этот код непосредственно в тест-план JMeter, но столкнулся с проблемами непонимания дженериков и импортов, и решил пока удовлетвориться вызовом полученного экспортера дерева в IDEA.
Берегите своё время. И спасибо за внимание.
