28 ก.พ. 2558

[Tutorial] JMeter - Process JSON response with BeanShell Processor

บ่อยครั้งที่ request body นั้นต้องการเอาค่าจาก response ของอีก service หนึ่ง.
JMeter ได้เตรียมเครื่องมือเอาไว้ extract data จาก response เอาไว้ให้หลายแบบด้วยกัน. แต่ทว่าในกรณีที่ response นั้นมีโครงสร้างซับซ้อน เช่น JSON หรือ XML, เราอาจจะต้องเขียนโปรแกรมเพื่อประมวลผล response ด้วยตัวเอง.
ในบทความนี้จะแสดงตัวอย่างการประมวลผล response ที่เป็น JSON โดยใช้ BeanShell Processor.

เตรียม Library เพื่อประมวลผล JSON

  1. เริ่มต้นด้วยการไปโหลด library สำหรับจัดการ JSON มาก่อนนะฮะ. โดยส่วนตัวผมชอบใช้ json-simple, สามารถ download JAR ได้จาก https://code.google.com/p/json-simple/ มองหาคำว่า Downloads จากเมนูซ้ายมือ ให้เลือกโหลดไฟล์ .jar นะฮะ. ยกตัวอย่างเช่น json-simple-1.1.1.jar.
  2. จากนั้นให้ copy json-simple-1.1.1.jar ไปวางใน folder lib ที่อยู่ภายใน folder ของ JMeter. เช่น \apache-jmeter-2.12\lib ดัง Figure 1.

Figure 1 Address ของ folder lib 

เป็นอันเสร็จสิ้นการเตรียมการ ต่อไปให้เปิด JMeter ขึ้นมาเลยฮะ.


    การประมวลผล JSON

    ในตัวอย่างนี้ บัวบานจะประมวลผล JSON ที่ได้จาก auto-suggest service ของ google ให้ชม. โดยจะใช้ keyword ว่า "json parser example"
    Figure 2 ตัวอย่าง auto-suggest ของ google
    server: www.google.com.sg
    path: /s?biw=1600&bih=809&sclient=psy-ab&q=json%20parser%20example&oq=&gs_l=&pbx=1&bav=on.2,or.r_qf.&bvm=bv.85970519,d.c2E&fp=a91f5977af3367b7&es_sm=0&pf=p&sugexp=msedr&gs_rn=61&gs_ri=psy-ab&pq=json%20parser%20example&cp=20&gs_id=1e&xhr=t&es_nrs=true&tch=1&ech=2&psi=XHTlVOPiIonmuQSD7oLIBg.1424323680476.3
    1. สร้าง HTTP Sampler
      - ใส่ server เป็น www.google.com.sg
      - ใส่ path เป็น  /s?biw=1600&bih=809&sclient=psy-ab&q=json%20parser%20example&oq=&gs_l=&pbx=1&bav=on.2,or.r_qf.&bvm=bv.85970519,d.c2E&fp=a91f5977af3367b7&es_sm=0&pf=p&sugexp=msedr&gs_rn=61&gs_ri=psy-ab&pq=json%20parser%20example&cp=20&gs_id=1e&xhr=t&es_nrs=true&tch=1&ech=2&psi=XHTlVOPiIonmuQSD7oLIBg.1424323680476.3
    2. คลิกขวาที่ HTTP Sampler ที่เพิ่งสร้างขึ้นมา แล้วเลือก Add > Post Processors > BeanShell PostProcessor
    3.  Copy เอา script ข้างล่างไปใส่ใน BeanShell PostProcessor. ดู Figure 3 ประกอบ.
      Figure 3 การสร้าง BeanShell PostProcessor
    4. จากนั้นให้เปิด Log Viewer ขึ้นมาโดยไปกดที่เมนู Options > Log Viewer. พอกดแล้วจะมีกรอบยื่นออกมาทางด้านล่าง. 
    5. กด Start teste ได้เลย 
    ถ้าไม่มีอะไรผิดพลาด น่าจะได้ผลลัพธ์ดัง Figure 4 นะฮะ. จะเห็นว่าสีแดงๆที่วงไว้ ตรงกับผลที่ได้ดัง Figure 2 เลย หุหุ.
    Figure 4 ผลลัพธ์จากการทดสอบ


    โค้ดตัวอย่างนะฮะ. ที่ Highlight เอาไว้นั่นคือส่วนที่เรียกใช้ JSON Simple เพื่อ parse JSON string ที่เราเตรียมไว้.

    import org.json.simple.JSONObject;
    import org.json.simple.JSONArray;
    import org.json.simple.parser.JSONParser;
    import org.json.simple.parser.ParseException;
      
    try {
     /////// Start prepare JSON string //////
     // get response from last response message
     String response = prev.getResponseDataAsString();
    
     // Google returns a lot of JSON object in one response, each object is separated by /*""*/ 
     // So we split response by /*""*/ 
     response = response.substring(0,response.length()-6);
     String[] res = response.split("/\\*\\\"\\\"\\*/");
    
     // Walk through array of JSON object
     for(int i=0; i < res.length; i++)
     {
      // Some nodes in JSON contains "[" and "]" which are invalid so we replace them
      // Replace \" with "
      // Replace "[ with [  
      // Replace ]" with ] 
      String tmpRes = res[i];
      tmpRes = tmpRes.replace("\\\"","\"");
      tmpRes = tmpRes.replace("\"[","[");
      tmpRes = tmpRes.replace("]\"","]");
    
      // Finally, JSON string is ready for us. Let's parse it
      log.info(tmpRes);
      JSONParser parser = new JSONParser(); 
      Object obj = parser.parse(tmpRes);
      JSONObject jsonObj = (JSONObject) obj; 
      // Auto-suggest results are in JSON node "d"
      JSONArray dArr = (JSONArray) jsonObj.get("d");
      JSONArray sugestArr = (JSONArray) dArr.get(1);
    
      // Print each result to Log
      for(int j=0; j < sugestArr.size();j++)
      {
       log.info(""+sugestArr.get(j)); 
      }
     }
    }
    catch(ParseException e) {
     e.printStackTrace();
    }


    ท่านผู้ชมสามารถ download ตัวอย่างได้จาก: https://drive.google.com/file/d/0B69Rt-ghTQqyMkFISDlLNHpuOE0/view?usp=sharing

    17 ก.พ. 2558

    [Tutorial] Performance test web application that needs credentials using JMeter (Windows platform)

    บทความนี้บัวบานจะกล่าวถึงการทำ Performance test Web application ที่มีการ authenticate โดยตัว web application เองนะฮะ. ในอนาคตจะมีบทความใหม่เกี่ยวกับ authenticate ด้วย windows authentication.

    โดยปกติแล้วการ authenticate ด้วยตัว application เองนั้นมักจะใช้วิธีใส่ username และ password ผ่าน web form แล้วให้ user submit form นั้น, ต่อมาฝั่ง application บน server ก็จะตรวจสอบตัวตน (Authenticate) และตรวจสอบสิทธิ์ (Authorization), ผลลัพธ์สุดท้าย ฝั่ง application บน server จะมอบ token กลับมาหนึ่งชุด (token ก็คือ string อะไรสักอย่างหนึ่ง)  ให้ web browser ของ client เก็บไว้ โดยมักจะจัดเก็บ token นั้นไว้ใน cookies.

    Workflow of Testing

    Figure 1 Workflow ของการทำ Performance Test Web Application ที่ต้องการ credentials

    Figure 1 แสดง Workflow ของการทำ performance test web application ที่ทำ authenticate โดยตัว web application เอง. แบ่งเป็นขั้นตอนดังนี้
    1. Open Login page and Extract VIEWSTATE
      จุดประสงค์เพื่อเก็บ token บางอย่างที่อาจจะต้องใช้ในการ submit login form เช่น viewstate. เราต้องให้ JMeter ส่ง request ไปที่ URL ของ Login page, แล้วเก็บค่าที่ต้องการไว้ในตัวแปร.
    2. Submit login form
      ส่วนนี้เป็นการส่ง username และ password ไปให้ฝั่ง application บน server. Response มักจะเป็น token พร้อมกับ URL ของ landing page.
    3. Navigate to landing page
      ขั้นตอนนี้เป็นการทดสอบการใช้ token ที่เราได้มา ว่าสามารถเข้าถึง web page หรือ service ที่ต้องการ credential ได้.
    4. Start requesting data
      เริ่มขั้นตอนการทดสอบจริงๆ.

    บัวบานจะยกตัวอย่างการ Search flight ของ airasia
    Login page: https://member.airasia.com/login.aspx?culture=th-TH
    Landing page: https://member.airasia.com/profile-landing.aspx

    Test Plan Development

    Structure of test plan

    บัวบานนำเสนอ structure สองแบบ.

    1st Choice - Simple plan

    ถ้าจะทำแบบง่ายๆใช้เร็วๆ บัวบานแนะนำให้มี Thread Group อันเดียว, เพื่อความง่ายในการจัดการ cookies. Figure 2 แสดง structure ของ Test Plan แบบ simple.
    Figure 2 Structure ของ Test Plan แบบ Simple

    ภายใน Thread Group จะประกอบด้วย

    1. HTTP Cookie Manager
      เมื่อเราใส่ HTTP Cookie Manager เข้าไปใน Thread Group, JMeter จะจัดการ cookie ให้โดยอัตโนมัติ. ซึ่ง HTTP Cookie Manager นี้จะมีผลภายใน Thread Group เดียวเท่านั้น. (แต่บางทีก็แชร์ข้าม Thread Group ได้. คาดว่าเป็นบั๊กนะฮะ).
      วิธีการเพิ่ม HTTP Cookie Manager ก็เพียงแค่คลิกขวาที่ Thread Group, เลือก Add > Configure Element > HTTP Cookie Manager.
    2. HTTP Request Defaults
      เอาไว้กำหนดค่า default ให้การทำ HTTP Sampler เช่น Server name/IP, Port, Proxy name/IP, ...
    3. Loop Controller สำหรับขั้นตอนการ login
      ใช้สำหรับกำหนดจำนวนครั้งในการ login.​โดยเราจะกำหนดให้ทำเพียง 1 ครั้ง. ภายใต้ Loop Controller นี้จะประกอบไปด้วย:
      - HTTP Sampler เพื่อใช้สำหรับการเปิดหน้า login และ submit login form
      - Regular Expression Extractor เพื่อใช้เก็บค่าบางอย่างจาก response ที่ได้กลับมา
      หลังจาก loop นี้ทำงานเสร็จ, ใน cookies ของเราจะมี token ที่ได้จากการ login, ซึ่งเป็นเปรียบเสมือนบัตรผ่านสำหรับเข้าใช้ web application.
    4. Loop Controller สำหรับ performance test
      ใช้สำหรับกำหนดจำนวนครั้งของการยิง request เพื่อทำ performance test.

    2nd Choice - Advanced plan

    ในกรณีที่การทำ performance test นั้นมีส่วนประกอบเยอะ เช่น ต้องสร้าง background load ให้ server หรือมีการเรียกหลายๆ service ที่ authenticate/authorize ต่างกัน, บัวบานแนะนำให้สร้าง Thread Group แยกสำหรับแต่ละ service ไป, และควรแยก  Group สำหรับ Authenticate/Authorize ด้วย.
    Figure 3 แสดง structure ของ Test Plan แบบ Advance.
    Figure 3 Structure ของ Test Plan แบบ Advance


    ภายใน  Test Plan จะประกอบด้วย
    1. Setup Thread Group
      Setup Thread Group คือ Thread Group ที่จะถูก execute แรกสุดเลย, และ Thread Group ปกติจะเริ่มทำงานหลังจากที่ Setup Thread Group ทำงานจนจบแล้ว. ภายใน Thread นี้จะประกอบไปด้วย HTTP Cookie Manager, HTTP Request Defaults, HTTP Sampler, BeanShell PostProcessor.
      BeanShell คือภาษา Java แบบ script, ไม่ต้อง compile ก็รันได้เลย. เราสามารถเขียน BeanShell script สำหรับ handle event ต่างๆที่เกิดขึ้นภายใน JMeter ได้.
      BeanShell PostProcessor คือ BeanShell script ที่จะถูก execute หลังจาก sample เสร็จแล้ว. ถ้าเราเอา BeanShell PostProcessor ใส่ไว้ใน HTTP Request, BeanShell script นั้นจะถูก execute หลังจากที่ sampler ได้รับ response กลับมาจาก server, ซึ่งทำให้เราสามารถประมวลผล response นั้นแล้วนำมาเก็บใส่ตัวแปรเพื่อใช้ต่อในอนาคตได้.
    2. Thread Group สำหรับ performance test. ภายใน Thread Group จะต้องมี BeanShell PreProcessor เพื่อใช้เขียน token ที่ได้จาก Setup Thread Group ลงใน cookies ของ Thread นี้ก่อนที่จะเริ่มทำการ sample.


    Login

    สิ่งแรกที่เราต้องรู้ก็คือ เราต้อง submit login form ไปที่ URL อะไร?
    วิธีที่บัวบานนิยมใช้ก็คือเปิด Google developer tools (dev tool) ขึ้นมา แล้วไปยัง tab Network. จากนั้นไปที่หน้าเว็บและกรอก username, password แล้วกด login. กลับมาที่ dev tool เพื่อดูการส่งข้อมูลไปยัง server. ไล่กดดูทีละตัวจนกว่าจะพบ request ที่มีการ submit form ซึ่งมี username และ password ที่เรากรอกเข้ามา​.
    พอเจอแล้ว ก็ให้ตรวจดูว่าเราจะต้องส่งอะไรเข้าไปใน login form บ้าง.
    จากรูป เราจะพบการ submit form ไปที่ https://member.airasia.com/login.aspx?culture=th-TH
    Figure 4 Form Data ที่ถูกส่งไปเมื่อ user กด login

    Figure 4 แสดงรายการ requests ที่เกิดขึ้นเมื่อ user กดปุ่ม login. วงกลมสีเขียวซ้ายมือ แสดงชื่อของ resource ที่ request, วงสีเขียวขวามือคือ Form Data, วงสีแดงคือ username และ password ที่เรากรอกเข้ามาในหน้าเว็บเมื่อสักครู่.
    ภายใน Form Data จะเห็น variable 4 ตัวที่มีค่าคือ __VIEWSTATE, ctl00$body$txtUsername, ctl00$body$txtPassword, และ ctl00$body$btnLogin. และจะมี variable ที่มีค่าเป็นว่างๆ อีก 3 ตัวคือ __EVENTTARGET, __EVENTARGUMENT, และ __VIEWSTATEENCRYPTED.

    การหาค่า VIEWSTATE

    ขั้นตอนถัดไป เราจะต้องหาที่มาของ VIEWSTATE.
    โดยทั่วไป VIEWSTATE นี้จะตรงกับชื่อของ <input> ซักอันใน html ของหน้า login. ดังนั้นเราต้องเปิดหน้า login อีกครั้งหนึ่ง (อาจจะใช้ incognito window) แล้ว view page source หรือใช้ Inspect element ก็ได้ แล้วมองหาคำว่า VIEWSTATE ใน HTML. Figure 5 แสดงตำแหน่งของ __VIEWSTATE ใน HTML.
    Figure 5 VIEWSTATE ภายใน HTML
    ถ้าไม่พบ ให้พยามมองหาสิ่งของที่มีค่าตรงกับค่าที่ส่งไปใน Form Data. (แต่ค่า __VIEWSTATE นี่จะเปลี่ยนทุกครั้งที่เปิดหน้า login ใหม่นะฮะ).
    เมื่อได้ที่มาของ __VIEWSTATE แล้วให้กลับไปที่ JMeter > Open Login Page sampler, click ขวา แล้วเลือก PostProcessor > Regular Expression Extractor.

    จากนั้นใส่ค่าให้ parameter ต่างๆ ตามรายการข้างล่าง. Figure 6 แสดง Regular Expression Extractor.
    Figure 6 การตั้งค่า Regular Expression Extractor
    • Reference Name: เป็นชื่อตัวแปร เราตั้งได้ตามใจชอบ. บัวบานขอตั้งชื่อว่า VIEWSTATE.
    • Regular Expression: เป็น Expression ที่ใช้หาและดึงเอาค่าของ __VIEWSTATE ใน HTML. ในกรณีของ airasia นี้ให้ใช้ expression:
      id="__VIEWSTATE" value="(.+?)"
      ภายในวงเล็บ (.+?) คือค่าที่เราจะดึงมา โดยจะถูกเก็บไว้ในตัวแปร $1$. ในกรณีที่มีวงเล็บหลายๆที่เพื่อดึงค่าจากหลายที่ ชื่อตัวแปรก็จะเรียงตามลำดับ $2$, $3$, ...
    • Template: เป็น Template ของค่าที่จะถูกเก็บไว้ในตัวแปร VIEWSTATE ที่เราตั้งไว้. ในกรณีนี้คือค่าที่ได้จากการ match ครั้งที่ 1 ซึ่งก็คือ $1$. 
    • Match No.: ... ไม่รู้จะอธิบายยังไงดี ฮ่ะๆ.
    • Default Value: ค่าตั้งต้นของตัวแปร.

    Submit Login

    HTTP Sampler ตัวนี้จะทำหน้าที่ submit login จริงๆ. โดยเราต้องใส่ค่าให้ parameter 4 ตัว ดังนี้:

    1. __VIEWSTATE: ${VIEWSTATE}
    2. ctl00$body$txtUsername: username ของเรา
    3. ctl00$body$txtPassword: password ของ user เรา
    4. ctl00$body$btnLogin: %E0%B8%A5%E0%B9%87%E0%B8%AD%E0%B8%81%E0%B8%AD%E0%B8%B4%E0%B8%99%E0%B9%80%E0%B8%82%E0%B9%89%E0%B8%B2%E0%B8%AA%E0%B8%B9%E0%B9%88%E0%B8%A3%E0%B8%B0%E0%B8%9A%E0%B8%9A
      อันนี้เป็น code ของภาษาไทยคำว่า "ล็อกอินเข้าสู่ระบบ"
    5. __EVENTTARGET:
    6. __EVENTARGUMENT:
    7. __VIEWSTATEENCRYPTED: 
    8. ctl00$body$chkRememberMe: on


    เป็นอันเสร็จสิ้นการ Login แบบ simple plan. ให้ลอง Add Listener > View Result In Tree, แล้วกด Start เพื่อเริ่มต้นการเทสต์. เมื่อกดดูที่ URL ภายใต้ sampler Submit Login form ก็จะเห็น token อยู่ใน Cookie ดัง Figure 7.
    Figure 7 Token ที่ได้หลังจาก Submit Login form

    ณ จุดนี้ Test ที่มี structure แบบ simple plan ก็จะมี token ให้ใช้สำหรับผ่านเข้าออกหน้าที่ต้องการ credentials ได้แล้ว. โดยที่ HTTP Cookie Manager จะคอยจัดการเพิ่ม cookie ให้ sampler ภายใน Thread Group โดยอัตโนมัติ.

    แต่ทว่า structure test plan แบบ advance ยังไม่จบเท่านี้. เนื่องจาก HTTP Cookie Manager นั้นจะจัดการ cookie ภายใน Thread ของตัวเองเท่านั้น. นั่นหมายความว่า เราต้องหาทางแชร์ Cookie จาก Setup Thread Group มาให้ Thread Group อื่นๆ.
    เราจะใช้ BeanShell Pre/Post Processor เข้ามาช่วย โดยให้ BeanShell PostProcessor ภายใน Setup Thread Group เขียนค่าของ cookies ลงในตัวแปรหรือ JMeter Properties, จากนั้นใช้ BeanShell PreProcessor อ่านค่านั้นแล้วเขียนใส่ Cookie ของ HTTP Sampler ภายใน Thread Group, แล้วปล่อยให้ HTTP Cookie Manager ภายใน Thread Group นั้นจัดการค่า cookie ต่อไป.

    การอ่านเขียนค่า Cookies ด้วย BeanShell Processor

    มี 2 แนวทางที่จะอ่านเขียนค่า cookies. วิธีแรกคืออ่านเขียนเฉพาะ cookie ที่เราต้องการ, วิธีที่สองคืออ่านเขียนทุกค่า.

    อ่าน/เขียนเฉพาะ cookie ที่ต้องการ ผ่านตัวแปร COOKIE_ชื่อcookie

    อันดับแรก, เราต้องตั้งค่าให้ JMeter ให้ save ค่าของ cookies ลงตัวแปร, โดยเข้าไปหา config file ตัวหนึ่งที่ folder ของ JMeter > bin > jmeter.properties, เมื่อเจอแล้วก็ให้ edit ด้วย notepad. จากนั้นใน search หา "CookieManager.save.cookies=false" แล้วเปลี่ยนให้เป็น CookieManager.save.cookies=true ดัง Figure 8.
    Figure 8 JMeter Properties สำหรับ save cookies เป็น variable
    จากนั้นเราจะสามารถอ่านค่าแต่ละ cookie ภายใน cookies ได้จากตัวแปรที่ชื่อ "COOKIE_ชื่อcookie". ยกตัวอย่างเช่น ใน cookies ของ airasia member นี้มี cookie ชื่อ ticket (ที่เป็น token ยาวๆ), เราสามารถเข้าถึงค่านี้ได้จากตัวแปรชื่อ COOKIE_ticket.

    อ่าน/เขียนทุก cookies

    ในกรณีที่ cookies ที่ต้องใช้มันมีหลายค่า และเราไม่รู้ว่าจะมีการเปลี่ยนแปลงอะไรในอนาคตรึเปล่า, ก็ควรใช้วิธีนี้นะฮะ. วิธีการนี้จะวนลูปอ่านค่าจาก HTTP Cookie Manager ใน Setup Thread Group แล้วเขียนค่าลงใน JMeter Property. จากนั้นในแต่ละ Thread Group ก็จะอ่านค่าจาก JMeter Property แล้วเขียนค่านั้นลงใน cookies.

    Code สำหรับ BeanShell PostProcessor เพื่ออ่าน cookies แล้วเขียนค่าลง JMeter Property


    import org.apache.jmeter.protocol.http.control.CookieManager;
    
    CookieManager mgr = ctx.getCurrentSampler().getProperty("HTTPSampler.cookie_manager").getObjectValue();
    
    props.put("CookieCount", String.valueof(mrg.getCookieCount()));
    
    for(int i=0; i < mgr.getCookieCount(); i++) {
     props.put("cookie_name_" + i, mgr.get(i).getName());
     props.put("cookie_value_" + i, mgr.get(i).getValue());
     props.put("cookie_domain_" + i, mgr.get(i).getDomain());
     props.put("cookie_path_" + i, mgr.get(i).getPath());
     props.put("cookie_secure_" + i, mgr.get(i).getSecure());
     props.put("cookie_expires_" + i, mgr.get(i).getExpires());
    }
    
    
    
    //props.put("COOKIE1","${COOKIE_userLogin}");
    //props.put("COOKIE2","${COOKIE_ticket}");
    //props.put("COOKIE3","${COOKIE_firstnm}");
    //props.put("COOKIE4","${COOKIE_lastnm}");
    //props.put("COOKIE5","${COOKIE_memberLogin}");
    

    Code สำหรับ BeanShell PreProcessor เพื่ออ่าน JMeter Property แล้วเขียนค่าลง cookies
    import org.apache.jmeter.protocol.http.control.CookieManager;
    import org.apache.jmeter.protocol.http.control.Cookie;
    
    int count = Integer.ParseInt(props.getProperty("CookieCount"));
    CookieManager manager = sampler.getCookieManager();
    
    for(int i=0; i < count;i++) {
     props.put("cookie_name_" + i, mgr.get(i).getName());
     props.put("cookie_value_" + i, mgr.get(i).getValue());
     props.put("cookie_domain_" + i, mgr.get(i).getDomain());
     props.put("cookie_path_" + i, mgr.get(i).getPath());
     props.put("cookie_secure_" + i, mgr.get(i).getSecure());
     props.put("cookie_expires_" + i, mgr.get(i).getExpires());
     
     Cookie cookie = new Cookie(props.get("cookie_name_" + i), props.get("cookie_value_" + i),props.get("cookie_domain_" + i),props.get("cookie_path_" + i),props.get("cookie_secure_" + i),props.get("cookie_expires_" + i));
     manager.add(cookie);
    }
    

    ท่านผู้ชมสามารถ download ตัวอย่าง Test Plan ทั้งสองแบบได้จาก
    Simple Plan: https://drive.google.com/file/d/0B69Rt-ghTQqyYk45NWpWZFM0dms/view?usp=sharing
    Advance Plan: https://drive.google.com/file/d/0B69Rt-ghTQqyalgzeXNhb1EzYWs/view?usp=sharing