Thursday, December 22, 2011

Для чего нам нужен Foreach

Постановка задачи.
Ответ (response, он же респонс - мильпардон за слэнг) одного из запросов (request, он же реквест) возвращает несколько числовых значений одновременно, из которых потом нужно сформировать строку определённого вида ("[1,2,3,4,5]") и засунуть её в переменную для дальнейшего использования в качестве параметра в другом запросе.

Для описанного ниже регулярного выражения, запрос возвращал респонс, состоящий из нескольких элементов (с разными значениями, естественно).

"335817" : {
"name" : "Price",
"id" : 335817,
"type" : "price",
"source" : "UTP",
"mId" : 335816
}
На выходе мне нужно было получить строку, которая бы содержала все id в виде ['<'id1'>','<'id2'>',...,'<'idn'>']

Шаг первый - дёргаем группы.
Делается это элементарно. К первому респонсу (что возвращает, в том числе, и искомые числовые значения), добавляем как child (дочерний элемент) Regular Expression Extractor c такими параметрами:


Где
Name ставите произвольное.
Apply to - где будете искать. Мне нужно искать в основном семпле (не в дочерних запросах).
Response field to check - в каком месте запроса искать нужное. Мне нужно искать в самом теле запроса.
Reference Name - имя "переменной", по которой мы будем обращаться к наденному фрагменту(ам).
Regular Expression - само регулярное выражение. Я проверяю его с помощью очень удобного фришного инструмента - Rad Software Regular Expression Designer.
Template - какую порядковую группу из найденных мы используем (если ищем несколько, можем и искать несколько). У меня единственная, она же первая группа.
Match No - тут поступаем так:
Если нужно использовать любое из найденных, ставим 0.
Если нужно использовать какое-то n-ное вхождение, ставим целое положительное n. (Например, 1 будет нам находить первое вхождение из группы).
Чтобы выгрести все - используем отрицательное значение (именно поэтому у меня стоит -1).
Default Value - исходное значение, которое будет подставляться, если искомый фрагмент(ы) не найден.


Шаг второй. Используем ForEach контроллер.

После запроса, из которого мы дёрнули группу, добавляем ForEach контроллер с такими параметрами:

Где
Name ставите произвольное.
Input variable prefix - то самое Reference Name из предыдушего запроса, по которому мы будем обращаться к выделенной группе.
Output variable name - куда мы будем складывать полученное значение.


Шаг третий. Слепляем элементы группы в одну строчку.
К нашему Foreach контроллеру как дочерний элемент добавляем BeanShell Sampler, в коде которого мы пишем, как именно мы хотим видеть строчку.

Я написала так:
String y=vars.get("mIds");

if (y.length() != 0)
{
     y=y.substring(0,y.length()-2);
     y+=",";
}
else
{
     y+="[";
}

y+=String.valueOf("${
meterval}");
y+="]";
vars.put("
mIds",y);


Где переменная mIds была описана в User Defined Variables с пустым значением.

На выходе из ForEach мы получим переменную mIds, которую в дальнейшем можно использовать где угодно. Значением этой переменной и будет искомая строчка.


ПыСы. При отладке запроса, использующего нашу переменную (mIds), не забываем enabl'ить шаги, с помощью которых мы эту переменную получаем :))))
ПыПыСы. Программист из меня тот ещё... Только что дошло, как убрать 4й шаг :)
Потому апдейтим скрипт.

Friday, December 16, 2011

Как запомнить HTTP статусы :)

Не могла удержаться и не свистнуть вот отсюда.

Огромное спасибо автору GirlieMac, aka Tomomi Imura за позитив!





































Wednesday, December 14, 2011

Как подружить Jmeter и Jenkins?

У нас на проекте существует какая-никакая Continuous Integration (CI) с выделенным серваком и прикрученным к нему Дженкинсом (вариант Hudson - оба являются CI-серверами).

Постановка задачи: для начала прикрутить два несложных джиметровских скрипта с тем, чтобы можно было смотреть что-то типа aggregate report родного Джиметра. Имя хоста передаётся параметром.

Шаг 1. Установить на Jenkins Performance Plugin путём стандартных средств Дженкинса: Jenkins -> Manage Jenkins -> Manage Plugins -> Available. Найти нужный и нажать [Install].

Шаг 2. Заливаем на сервер джиметр. У меня на сервере живёт бубунта, и её стандартный package manager ставит джиметр версии 2.3. Потому я сверху установленного 2.3 стянула последнюю версию и распаковала её в другом месте _path_. Потом мы на это место будем ссылаться.

Шаг 3. Делаем job для каждого скрипта и запускаем его, дабы создались директории в /var/lib/hudson/jobs

Шаг4. Если мы задаём имя хоста в User Defined Variables, то там пишем следующее:
HOST (имя переменной) ${__P(server,_defaultHost_)} (значение переменной)
Где _defaultHost_ - имя хоста по умолчанию, а server - имя параметра, передаваемого в командной строке.
Такой скрипт можно гонять как из ГУИ, так и из командной строки.

Шаг 5. Для каждого job'a копируем в папку /var/lib/hudson/jobs/_JobName_/workspace/ необходимый параметризованный джиметровский скрипт и csv файлы. Допустим, у нас есть скрипт testjm.jmx.

Шаг 6. Настраиваем job'ы. (Configure)
Для этого открываем в Дженкинсе нужный Job (для каждого из двух скриптов был создан отдельный) и делаем следующее:

- Ставим галочку для "This build is parameterized" и пишем что-то типа
Name hostname
Default Value _servername_
Description Host name where the test will be run


- В разделе Build жмём кнопочку [Add build step] -> Execute shell.
В поле Command пишем:

echo $WORKSPACE
JMETER_HOME=/home/hudson/jakarta-jmeter-2.5/bin (_path_ куда был записан свежий джиметр)
PATH=$JMETER_HOME:$PATH
java -jar $JMETER_HOME/ApacheJMeter.jar -n -t $WORKSPACE/testjm.jmx -Jserver=${hostname} -l $WORKSPACE/jmeter.jtl


Этот скрипт будет выполнять наш скрипт testjm.jmx (-t) в командной строке (-n), передавая туда переменную для джиметра server, значение которой будет равняться параметру, заданному hostname.
Также в папку /var/lib/hudson/jobs/_JobName_/workspace/ будет положен лог джиметра с именем jmeter.jtl (-l).

- Ставим галочку Publish Performance test result report. Тут же нажимаем [Add a new report] и выбираем JMeter. В Report Files пишем имя нашего лога - jmeter.jtl

Шаг 7. Сохраняем.

Шаг 8. Бегаем.


Для того, чтобы после выполнения посмотреть лог, идём на

http://_server_/view/_path_/job/_JobName_/lastBuild/performance/

Тут _server_ - имя Дженкинс-сервера.
_path_ - путь до папочки "jobs".
_JobName_ - имя текущего job'a.

Перебираем все строки в csv файле.

За помощь в неравной борьюе большое спасибо Илье Пономаренко и Дэну Аркуше.

Постановка задачи: необходимо, чтобы цикл выполнялся ровно столько раз, сколько есть строк в csv-файле. Для каждого треда отдельно.
Т.е., если у нас есть
Готового решения в инете я не нашла (может, искала плохо, конечно).

Раньше проблема решалась таким образом: я создавала Loop Counter, задавала число проходов равным числу строк в csv, которое мне надо было пройти, и тогда всё работало замечательно.

Но проблема заключалась в том, что файл мог меняться, сответственно, могло меняться и количество строк в этом файле. Да и интересно было заставить джиметр автоматом считать, сколько раз нужно бегать.

Мучилась я долго, но придумала, наконец, нижеследующее.

Допустим, у меня есть csv файл с такими полями: userName,Pwd,grName. Точнее, файл содержит столбцы, чьи столбцы парсятся в переменные JMeter c именами userName,Pwd,grName.
В этом файле Х строк.

1. Редактируем файл, ставим в Х+1 строке слово FALSE в первом столбце (отвечающем за переменную userName). Сохраняем изменения.

2. Создаем Add -> Logic Controller -> While Controller, которому ставим значение для Condition (function or variable) равное ${username}

3. Добавляем в этот контроллер CSV Data Set Config, в котором прописываем нужный нам csv файл и все необходимые имена переменных из него.

4. Добавляем в контролер все шаги, которые необходимо выполнить в цикле.

Профит! :)

Единственный минус - цикл выполняется не Х, а Х+1 раз, и последний раз вместо значения переменной userName передаётся FALSE.

Но, как по мне, это лучше, чем править ручками.

Если кто знает лучший способ - буду рада, если поделитесь.

Tuesday, November 22, 2011

Перебираем циклом пользовательские переменные

Постановка задачи. Имеется N пользовательских переменных, значения которых вычисляются в BeanShall скрипте (в данном случае это - разные интервалы времени, но суть не важно). У этих пользовательских переменных user-friendly имена, так что сразу понятно, что передаётся этой переменной.
Необходимо выполнить некоторый однотипный набор действий, передавая параметрами все эти переменные.
То есть, нужно сделать цикл, который будет просто перебирать все эти переменные.

Первой мыслью было создать файл, куда запихиваются имена этих кастомных переменных с тем, чтобы потом их оттуда взять. Не сработало. Как бы ни мучались, имена передаются как текст.

Пришлось искать обходные пути.

Решение вышло несколько кривоватым, но, тем не менее, рабочим.

Итак в BeanShell скрипт и User Defined Variables добавляем T1..TN переменные, куда дублируем все нужные нам переменные с user-friendly именами.

Далее, создаем Loop, куда помещаем все нужные нам операции, которые должны быть выполнены в цикле. Ставим Loop Counter равным N (количество переменных).

Там, где нам нужно передавать значение переменной, в параметрах ставим
${__V(T${__counter(FALSE)})}
в нужном месте.

Всё работает :)

ПыСы. Отдельное спасибо Дэну Аркуше за подсказку решения "в лоб"

Дополнение. Нашлось лучшее решение.

Данный метод работает, если цикл у вас всего один. А что делать, если вложенных циклов несколько?

Параметр false у функции {__counter(FALSE)} означает, что этот счётчик будет увеличиваться только в пределах одного треда.
Но, если у вас будет сверху ещё 2 цикла с числом проходов X и Y, то счётчик ваш будет увеличиваться от 1 до X*Y*N, что приведёт к тому, что переменные у нас просто закончатся. нам-то нужно, чтобы этот счётчик работал от 1 до N.

В этом случае на помощь приходит конфигурационный элемент Counter


Задаём ему такие значения:
Start - 1
Increment - 1
Maximum - N (количество переменных)
Number format - 0 (в этом случает счёт начинается с 1, если Вы поставите 00, то счёт будет вестись с 01, если 000 - то с 001 соответственно).
Reference Name - Count (произвольное имя счётчика, на которое мы будем ссылаться).
Обязательно поставить галочку для "Track counter independently for each user".

После этого смело пишем этот счётчик везде, где он нам может понадобиться.

Т.е., ${__V(T${Count})}

Wednesday, November 9, 2011

Проверяйте системное время на виртуалках!!!

Два дня ковырялась, чего скрипт у меня генерит шопопало.
Время - 30е октября, хоть тресни.
Пока не дошло зайти и проверить системное время виртуалки (бубунта, штоп её).
И вы-таки не поверите...

Блондинко, блин...

Thursday, August 4, 2011

Добавляем кастомные функции в BeanShell

Исходя из предыдущего поста, возинкла необходимость написания простейшей функции, которая бы приписывала нолик к входному значению, в случае, если это значение меньше 10.
Два дня копала форумы, ничего не нашла.
А всего-то надо было почитать про BeanShell!!!!

Решение оказалось банальным до безобразия.

add( a, b )
{
return a + b;
}

foo = add(1, 2); // 3
foo = add("Oh", " baby"); // "Oh baby"

Итак, результирующий код выглядит следующим образом:

Calendar cal=Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles"));

Conv(val)
{
String m = "";

if (val<10)

{
m = "0" + String.valueOf(val);
}

else
{
m =String.valueOf(val);
}

return m;
}

String newTime = Conv(cal.get(Calendar.YEAR)) + "-" + Conv(cal.get(Calendar.MONTH)+1) + "-" + Conv(cal.get(Calendar.DAY_OF_MONTH)) + "T" + Conv(cal.get(Calendar.HOUR_OF_DAY)) + "%3A" + Conv(cal.get(Calendar.MINUTE)) + "%3A" + Conv(cal.get(Calendar.SECOND)) + "Z";

vars.put("myTime", newTime);


Примечание! Если использовать Calendar cal=Calendar.getInstance() то у вас будет использоваться ваше _ЛОКАЛЬНОЕ_ время.
Мне это не подходило, т.к. объекты на сервере должны были создаваться по серверному времени.
Проблему решил перевод в формат UTC. Я использовала метод TimeZone.getTimeZone("America/Los_Angeles")) (список временных зон можно посмотреть вот здесь )
Однако, многим не нравится использование метода getTimeZone(), потому есть альтернативное решение.

Да, и ещё! Не забывайте о правильном использовании галочки "Encode?" в параметрах.
Если используются URL escape characters, то эту галочку ставить не нужно!

Thursday, July 14, 2011

Jmeter. Борьба с кастомным форматом даты/времени.

Понадобилось мне в один из вызовов вставить дату в следующем формате: yyyy-MM-ddThh:Amm:ssZ

Первый хинт - нельзя напрямую передавать ":", потому пользуемся URL escape code (http://www.december.com/html/spec/esccodes.html) - %3A.

Описанный в мануале способ не сработал (http://jakarta.apache.org/jmeter/usermanual/functions.html#__time) - изменённая строчка в jmeter.properties #time.USER1=yyyy-MM-ddTHH%3Amm%3Assz с последующим вызовом функции ${__time(USER1)}не сработала. Не сработало даже #time.USER1=yyyy-MM-dd.


Потому был найден обходной путь.

1. В наш любимый Config Element -> User Defined Variables помимо всего прочего добавляем переменную myTime.
2. К текущей Thread Group добавляем Sampler -> BeanShell Sampler, в теле скрипта которого пишем следующее:

Calendar cal=Calendar.getInstance();
int mm = cal.get(Calendar.MONTH);
mm = mm +1; //почему-то джава месяцы считает 0-11
String month = "";
if (mm<10)
{
   month = "0" + String.valueOf(mm);
}
    else
       {
         month =String.valueOf(mm);
       }
String newTime = String.valueOf(cal.get(Calendar.YEAR)) + "-" + month + "-" + String.valueOf(cal.get(Calendar.DAY_OF_MONTH)) + "T" + String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) + "%3A" + String.valueOf(cal.get(Calendar.MINUTE)) + "%3A" + String.valueOf(cal.get(Calendar.SECOND)) + "Z";
vars.put("myTime", newTime);

3. Тадам - вызываем нашу переменную {myTime} в нужном месте реквеста.


Небольшая поправка. Дата-то вычисляется, но, если число или месяц меньше 10, то дата получается без нолика.
То же самое относится и к часам, минутам и секундам.

Потому возникла необходимость написания функции.
О том, как это делается - следующий пост.

Jmeter, Как же залогиниться на https, да ещё и с сертификатом? - 2

Предыдущий способ для меня не сработал, потому с помощью нашего программиста мы нашли следующую лазейку.
1. К текущей Thread Group ( о том, что это такое, и с чем его едят, я расскажу чуть позже) добавляем HTTP Cookie Manager (правый клик на имени Thread Group -> Add -> Config Element -> HTTP Cookie Manager).

2. К этой же группе добавляем Add -> Config Element -> HTTP Authorization Manager со следующими параметрами:
Base URL - https://${host}:8181/
Username - ${username}
Password - ${password}

где HOST, username, password - это переменные, заданные в User Defined Variables (об этом тоже чуть позже).

3. В ту же Thread Group добавляем Add -> Sampler-> HTTP Request, с помощью которого мы вытягиваем cookies.
В пути (path) указываем 100% работающую api'ку, у которой мы можем отобрать печеньку. (Именно за этим и нужен программист, ибо у нас просто нет списка апишек с описанием, а он-то уж точно знает, что куда и как).
Переименуем этот запрос, допустим, в Get Cookies (для того, чтобы переименовать достаточно просто написать желаемое в поле Name).

3.1. Правый клик на свежесозданном Get Cookies показывает нам контекстное меню, из которого мы выбираем Add -> Post Processor -> Regular Expressions Extractor. В нём мы записываем следующее:
Reference Name: csrfmiddlewaretoken
Regular Expression: Set-Cookie: csrftoken=(\w+);
Template: $1$
Match No. (0 for Random): 1
Default value: not found

Я подозреваю, что Reference Name и регулярное выражение могут варьироваться для каждого конкретного случая, потому помощь девелоперов тут более чем приветствуется.

4. Создаём ещё один HTTP Request  с именем, скажем, Login to apache со следующими параметрами:
Server Name or IP: ${HOST}
Path: относительный путь к логину, например, /login/ (т.е., что дописывается после имени хоста, чтобы попасть на страничку логина).

Send Parameters with the Request:
username - ${username}
password - ${password}
csrfmiddlewaretoken - ${csrfmiddlewaretoken}

Усё :) После этого у меня заработало!

Да, ещё нужно учитывать, что токен может передаваться, как в заголовке, так и в теле. В зависимости от этого надо ставить соответствующую галочку а обработчике регулярных выражений.

Jmeter, Как же залогиниться на https, да ещё и с сертификатом?

Способ номер раз, предложенный Артёмом Розуменко.

В командной строке сделать следующие действия (это чтоб ССЛ был по барабану устанавливается 1 раз и на всю жизть)
Для установки сертификата синатра.гармошка.ком
C:\Program Files\Java\jdk1.6.0_19\bin>keytool -import -alias sinatra.eharmony.com -file c:\sinatra.eharmony.com -keystore C:\Progra~1\Java\jdk1.6.0_19\jre\lib\security\cacerts
где C:\Program Files\Java\jdk1.6.0_19\bin - путь к бину ЖДК, c:\sinatra.eharmony.com - путь к месту где лежит сертификат, а C:\Progra~1\Java\jdk1.6.0_19\jre\lib\security\cacerts - в каталоге ЖДК по такому пути есть этот файл он за сертификаты отвечает.
если все введено правильно, то спросит пароль в консоли, а пароль "changeit"
потом проверьте что все поставилось такой командой:
C:\Program Files\Java\jdk1.6.0_19\bin>keytool -list -keystore C:\Progra~1\Java\jdk1.6.0_19\jre\lib\security\cacerts
В большом списке должон быть и новый серт.
Потом проделать следующее, можно и без консоли
1. Add the three key jars to your JVM's "ext" (extentions) directory; e.g.
    cp jcert.jar jnet.jar jsse.jar $JAVA_HOME/jre/lib/ext/
2. After the jars are in place, you must modify the file "java.security" to allow usage of the providers found within the jars. Find the file
   find $JAVA_HOME -name "java.security" как правило оно в C:\Program Files\Java\jdk1.6.0_19\jre\lib\security
3.  Add the following line to the file java.security (in case it's not exsists):
    security.provider.2=com.sun.net.ssl.internal.ssl.Provider а такая строчка у меня была, если есть то не вставляйте.
И после конфигурации в 15-20 минут наступает непреодолимое счастье :).
Сертификат тянется с файерфокса по средствам нажатия на замочек и экспорт его потом на тачку.
Там ещё очень аккуратно надо импортить в caserts. На всякий случай нужно этот файлик забэкапить (хотя, оттуда, по необходимости, можно и удалять).

Хинт - добавляя сертификат в caserts, его имя нужно прописывать точно так же, как оно появляется в самом сертификате. Т.е., если у Вас самописный сертификат говорит, что он выпущен для vasya-pupkin, то его надо прописывать именно, как vasya-pupkin, хоть вы и ходите на сайт vasya-pupkin.com через https.
Кому нужны файлы - свистите, скину :)

Джиметр для блондинок и не только. Intro.

Не столь давно вплотную столкнулась с необходимостью решать довольно нетривиальные задачи с помощью джиметра.
До того, весь опыт общения с оным сводился исключительно к записи каких-то простеньких действий, которые просто воиспроизводились нужное количество раз после несложных правок.
Вследствие того, что блондинка я натуральная, покурив гугль и мануал, сходу понять ничего не удалось, потому методом проб и ошибок, пришлось осваивать азы работы с джиметром, выкидывать разный ненужный мусор из выцепленных из Гугля последовательностей действий.

Да, задача осложнялась тем, что работать мне нужно было с https'ом. Потому простая запись логина не срабатывала.
Не помог и BadBoy, который умеет записывать скрипты в UI режиме, и затем экспортировать их в JMeter.

Раньше джиметр не умел записывать https запросы, потому BadBoy был единствненным приемлемым выходом (плюс, с более удобным интерфейсом). Однако, начиная с версии 2.3, джиметр-таки научился работать с https, потому необходимость в BadBoy'е отпала.

Тем не менее, работа с BadBoy'ем помогла мне гораздо красивее оформлять джиметровские скрипты.

Но обо всём по порядку :)