Skip to Content

Blog Archives

Gherkin and Cucumber ทางเลือกใหม่ในการจัดการ Automation Testing ให้เป็นระบบ

ในกระบวนการพัฒนา Software แบบ Agile นั้น ใช้ User Story เพื่ออธิบาย Feature, Requirement ต่าง ๆ ของ Software ในมุมมองของ end-user แต่อย่างไรก็ตาม ก็ยังคงมีอุปสรรคในการสื่อสาร User Story เหล่านี้ระหว่าง Business Team และ Tech Team ให้เข้าใจตรงกัน Gherkin ถูกประดิษฐ์ขึ้นเพื่อช่วยแก้ไขปัญหานี้

 

Gherkin เป็นภาษาสำหรับบรรยาย Requirement และ Acceptance Criteria ในรูปแบบที่คล้าย Human Language ซึ่งช่วยให้ทีมงานทำความเข้าใจ Requirement ต่าง ๆ ได้ง่าย ทั้งในเชิง Business และ Technical

 

Gherkin Systax

การอธิบาย Scenario ด้วย Gherkin มีองค์ประกอบดังนี้

  1. Scenario: อธิบายสถานการณ์ของการใช้ระบบ
  2. Given: Precondition / Context / Step ที่ต้องเกิดขึ้นเพื่อนำระบบไปอยู่ใน State ที่ต้องการ ก่อนที่ผู้ใช้งานจะมี Interaction ใด ๆ กับระบบ ส่วนของ Given สามารถมีหลายข้อได้ และเชื่อมกันด้วย And
  3. When: Interaction ต่างที่ User กระทำ หากมีหลาย Action ให้เชื่อมด้วย And
  4. Then: ผลลัพธ์ที่เกิดขึ้น มักจะกล่าวถึงสิ่งที่เราต้องตรวจสอบ เช่น user interface, report, message, command output เป็นต้น และยังรวมถึง External System ที่เกี่ยวข้องด้วย เช่น หากมีการส่งข้อมูลไปยังระบบอื่น

 

Case Study: Login

ในบทความนี้ขอยกกรณีศึกษา User Story การเข้าสู่ระบบ (Login) โดยให้มี User Story ดังนี้

As a customer, I want to login using username and password, so that I can see information in the homepage.

 

Business Analyst (BA) สามารถเขียน Scenario ต่างๆ ในรูป Gherkin ได้ดังต่อไปนี้


Scenario Outline: Check login is successful with valid credentials

Given user is on login page

When user enters valid username and password – username: <username>, password: <password>

And clicks on login button

Then user is navigated to the home page

Examples:

| username | password |

| Raghav   |    12345 |

| Ele      |    12345 |


Scenario Outline: Check login is failed with invalid credentials

Given user is on login page

When user enters invalid username and password – username: <username>, password: <password>

And clicks on login button

Then error message is displayed – invalid credential

Examples:

| username | password |

|                    |    11111 |


 

จากตัวอย่าง มีการใช้ Example เพื่อกำหนด Test Data (Sample) สำหรับการทดสอบ โดยแต่ละ Scenario แรกมี 2 sample และ Scenario ที่สองมี 1 sample สิ่งที่เกิดขึ้นคือ เมื่อทำการทดสอบ โปรแกรมจะนำค่าใน Sample นี้ไปแทนค่า Variable ต่าง ๆ ของ Scenario นั้น เช่น <username>, <password>

นอกจากนี้แล้วเราสามารถใช้ Tag เพื่อใช้แบ่งกลุ่ม จัดระเบียบ จำแนก scenario ได้ โดยใส่เครื่องหมาย @ ตามด้วยข้อความใด ๆ ที่เราต้องการ ไว้หน้า Scenario เช่น เราต้องการแยก Positive/Negative Test หรือต้องการกำหนด Scenario สำหรับ Smoke Test ก็สามารถใส่ Tag @positive, @negative, @smoke ลงไปได้

Tag เหล่านี้จะถูกใช้ตอน Execute Test โดยระบุ Tag Name ของ Scenario ที่เราต้องการทดสอบลงไป โปรแกรมก็จะ Execute เฉพาะ Scenario เหล่านั้นให้

 


@positive

@smoke

Scenario Outline: Check login is successful with valid credentials


@negative

Scenario Outline: Check login is failed with invalid credentials


 

เรารวบรวม Scenario ที่เป็นรูปแบบ Gherkin ไว้ใน File ที่เรียกว่า Feature File และ File นี้จะถูกนำมาสร้างเป็น Code สำหรับ Automated Test ได้ โดยอาศัยเครื่องมือที่ชื่อว่า Cucumber โดยในบทความต่อไปนี้จะยกตัวอย่างการใช้ Cucumber ร่วมกับ Maven, Eclipse, Seleninum

เมื่อเราใช้ Cucumber อ่าน Feature File เราจะได้ Output ออกมาเป็น Code Snippet ซึ่งเป็นโครงร่างเพื่อนำไปเขียน Code ต่อดังรูปตัวอย่าง

รูปฝั่งซ้ายเป็น Feature File (userlogin.feature) ส่วนฝั่งขวาเป็น Code Snippet ในภาษา Java (LoginSteps.java) สำหรับนำไปเขียน code ทำ Automated Test ต่อ ให้สังเกตว่าใน Code จะมี Annotation @Given, @When, @Then ซึ่งจะสอดคล้องกับ Scenario ใน userlogin.feature

 

เมื่อเราได้ Code Snippet แล้ว เราสามารถใช้ Selenium Library for Java เพื่อสร้าง “Glue Code” สำหรับ Automated Test ต่อไปได้ โดยจะแบ่งเป็น Class Page Factory และ Class Test Step

Page Factory Class เป็น Class ที่เป็นตัวแทนของ Page (หน้าจอ) ที่เราต้องการทดสอบ เราจะประกาศตัวแปร Web Element ต่าง ๆ เท่าที่จำเป็นในการทดสอบ เช่น หน้าจอ Login ก็จะมี Web Element ได้แก่ name (Textbox), password (Textbox), login (Button) นอกจากนี้จะมีส่วนที่เป็น division สำหรับแสดงข้อความ error message อยู่ 2 ที่ ซึ่งไม่ได้กำหนด ID แต่ใช้ CSS Class ชื่อว่า invalid-feedback

 

สร้าง Class LoginPage_PF เป็น Page Factory โดยให้มีตัวแปร Web Element สอดคล้องกับหน้าจอ ดังนี้

 

สังเกตใน Class LoginPage_PF จะมีตัวแปร WebElement ผูกกับ ID ของ Object ต่าง ๆ บนหน้าจอ ด้วย Annotation @FindBy(id) นอกจากนี้เราสามารถสร้าง List ของ WebElement ได้ เช่น ค้นหา Object บนหน้าจอ ที่ใช้ CSS Class ชื่อว่า “invalid-feedback”

 

สำหรับ Constructor นั้น ให้รับ Parameter เป็น WebDriver เข้ามา ซึ่งจะทำให้เราสามารถผูก WebElement กับ Object บนหน้าจอได้

 

จากนั้นเราเขียน code selenium ที่สั่งให้ทำ Action ต่าง ๆ บนหน้าจอ เช่น การกรอก username ในช่อง name, การกรอก password ในช่อง password, การคลิกปุ่ม Login, การตรวจหาข้อความ “Password is invalid”

 

หลังจากที่ทำ Page Factor Class LoginPage_PF เสร็จแล้ว เราก็จะมาเขียน “Glue Code” ใน LoginSteps Class ต่อ ซึ่ง Class นี้จะเรียกใช้งาน LoginPage_PF และมี Method ต่าง ๆ สอดคล้องกับกับ Scenario ใน Feature File (userlogin.feature)

 

เราเริ่มจาก Code สำหรับ Initialize และ Finalize ก่อน

  • Method browserSetup() ใช้สำหรับ setup การทดสอบ โดยทั่วไปจะ create driver ของ browser ขึ้นมา (เช่น Chrome, Firefox, Edge ซึ่งในตัวอย่างนี้ใช้ Microsoft Edge) กำหนดค่า setting ทั่วไป (เช่น timeout)
  • Method teardown() ใช้สำหรับสิ้นสุดการทำงาน โดยทั่วไปจะ close driver และ quit driver

 

 

  • จาก Code ที่แสดง เราสามารถกำหนดให้บาง Method ทำงานก่อนที่จะเริ่ม execute test ได้ โดยใส่ Annotation @Before(order) ได้ พร้อมระบุ Order เป็นเลขลำดับ ในที่นี้เราใส่ @Before ให้กับ method browserSetup ()
  • ในทางกลับกัน เราสามารถกำหนดให้บาง Method ทำงานหลังการ execute test ได้เช่นกัน โดยใส่ Annotation @After(order) ในที่นี้เราใส่ @After ให้กับ method teardown()

 

จากนั้นเราเขียน code ส่วนที่เหลือ คือ Test Step ต่าง ๆ ตาม Scenario ซึ่งจะไปเรียกใช้ Method ต่าง ๆ ที่เราสร้างไว้ใน LoginPage_PF

  • user_enters_valid_username_and_password() : การกรอก username และ Password
  • clicks_on_login_button() : การกดปุ่ม Login

 

 

สุดท้ายคือ Code สำหรับตรวจสอบผลลัพธ์ ซึ่งในที่นี้จะตรวจสอบว่าหน้าจอที่แสดงเป็นหน้าจอ Home Page หรือไม่โดยค้นหา Object ปุ่ม Logout ถ้าค้นหาพบ แสดงว่าหน้าจออยู่ที่ Home Page แล้ว

 

สังเกตว่าจะเรียกใช้ Page Factory อีก Class หนึ่งคือ HomePage_PF ซึ่งมี Web Element ที่เราสนใจคือปุ่ม Logout

 

สุดท้ายเมื่อ Build แล้ว เราสามารถ Run Automated Test ได้ผ่าน Eclipse หรือ Command Line ก็ได้

รูปต่อไปนี้แสดงการใช้ Command Line “mvn test”

 

โปรแกรมก็จะเปิด Browser ขึ้นมา และทำ Action ต่าง ๆ ที่เราเขียน Scenario ไว้ โดยอัตโนมัติ จะสังเกตว่าแต่ละการทดสอบของ Scenario จะแสดง Scenario Step (Gherkin) คู่กับ Glue Code ที่ทำงาน โดยแสดง Package Name, Class Name, Method Name เช่น

  • Step “Given user is on login page” มี Glue Code ที่คู่กันคือ LoginStep.user_is_on_login_page

ซึ่งสิ่งนี้เองที่ทำให้เห็นภาพชัดเจนขึ้นว่า การใช้ Gherkin-Cucumber จะช่วยจัดระเบียบ ให้ทำความความเข้าใจและเชื่อมโยงระหว่าง Scenario และ Test ได้ดีขึ้น นอกจากนี้ หากอนาคตมีการเปลี่ยนรายละเอียดของ Step ก็จะทราบส่วนที่ต้องแก้ไข Code Automated Test ได้เร็วขึ้นด้วย

 

ในตอนท้าย โปรแกรมจะแสดงสรุปผลการ Test ว่าผ่านหรือไม่ผ่านกี่ Scenario

 

หากเราต้องการทดสอบบาง Scenario เราสามารถระบุ Cucumber option ตอนที่ Run maven test ได้ ในที่นี้ขอยกตัวอย่างง่าย ๆ ดังต่อไปนี้

  • กรณีต้องการทดสอบ scenario ที่มี tag ชื่อที่เราต้องการ เช่น

    • ต้องการทดสอบ scenario tag positive

mvn test -Dcucumber.options=”–tags @positive”


    • ต้องการทดสอบ scenario tag smoke

mvn test -Dcucumber.options=”–tags @smoke”


 

  • กรณีที่ Project มีหลาย Feature File เราสามารถเจาะจง Feature File ที่ต้องการทดสอบได้

    • การเจาะจงทดสอบเพียง Feature File ที่เราต้องการ ให้ระบุ Relative path ของ Feature file นั้น

mvn test -Dcucumber.options=”src/test/resources/features/userlogin.feature”


    • การเจาะจงทดสอบเพียง Feature File ใน Directory ที่เราต้องการ ให้ระบุ Relative path ของ Directory นั้น

mvn test -Dcucumber.options=”src/test/resources/features”


 

สำหรับบทความนี้ก็ขอจบเท่านี้ครับ หากสนใจโซลูชั่นด้านดิจิทัล สามารถติดต่อเราได้ที่อีเมล Marketing@stream.co.th หรือโทร. 02-679-2233 นะครับ

เรียบเรียงโดย Siripod Surabotsophon

Reference

  • Selenium Cucumber Java BDD Framework

https://youtu.be/4e9vhX7ZuCw

  • Source Code ในบทความนี้ สามารถ Download ได้จาก GitHub

https://github.com/siripods/SeleniumCucumberBDD

 

0 0 Continue Reading →

เขียน Script ใน Robot framework อย่างไร?
เมื่อใน 1 case มีมากกว่า 1 Scenario

หลังจากที่เราสามารถ ถอด Test Script ให้เป็น Robot Script และรัน Automate Test เป็นผลสำเร็จ เย้!!! \^O^/

แล้ว….ถ้าเกิดกรณีใน Test case มันดันมี Test scenario มากกว่า 1 Scenario ล่ะ เราจะเขียน Script ใน Robot framework อย่างไร?

 

ตัวอย่าง Test case ที่มี มากกว่า 1 Scenario

Case 2 : ลงชื่อเข้าสู่ระบบ Facebook  ถ้าระบุ Email Address หรือ Password ไม่ถูกต้อง ระบบจะแสดงข้อความแจ้งเตือนเพื่อให้ระบุค่าให้ถูกต้อง

31

 

Process การทำงานของแต่ละ Scenario ใน Test case จะมีลักษณะดังนี้

32

 

ทีนี้มาเริ่มเขียน Script กันเลย

33

 

คำอธิบาย :

**ใน Part นี้ขออธิบายตามโครงสร้างแต่ละส่วนละกันนะจ๊ะ

  1. Settings

ใน Part นี้นอกจากเราจะเอาไว้เรียก Library แล้ว ยังมีคีย์เวิร์ดเพิ่มขึ้นมา 2 ตัวนั่นคือ Test Setup และ Test Teardown

Test Setup กับ Test Teardown จะเหมือนเป็นการ Start – End process  เอาไว้ใช้ในกรณีที่ใน Test case มีหลาย ๆ Test Scenario แล้วในแต่ละ Scenario ต้องใช้คีย์เวิร์ดเหมือน ๆ กัน เช่นในกรณีนี้คือ แต่ละ Scenario ต้องทำการเปิดเว็บไซต์ขึ้นมาเพื่อกระทำ และ ปิดเว็บไซต์ทุกครั้ง ดังนั้น เราจึงนำ Test Setup และ Test Teardown มาใช้เพื่อจะได้ไม่ต้องพิมพ์คีย์เวิร์ด Open Browser และ Close Browser หลาย ๆ ครั้งให้ยืดยาว เปลืองเนื้อที่

นอกจากนี้ยังมี Test Template , Suite Setup , Suite Teardown ซึ่งเป็น Keyword ที่ทำงานในลักษณะคล้าย ๆ กัน ไว้จะมาอธิบายคราวหลังเนอะ

  1. Keywords

ตามตัวอย่างมีการสร้าง Keyword ขึ้นมา 2 ตัว คือ

Open facebook สร้างไว้สำหรับเรียกใช้ใน Test Setup เนื่องจาก เราไม่สามารถที่จะเอา Keyword ที่มี Argument ไปใส่โดด ๆ ตรงนั้น เราจึงจำเป็นต้องสร้าง Keyword ขึ้นมาก่อน

Log in   ปกติสคริปต์ที่เราเขียนเมื่อจะ Log in เข้าสู่ระบบของ facebook จะเป็นดังนี้

 34

ต้องพิมพ์ถึง 3 บรรทัดเลยทีเดียว ในกรณีที่ใน Test case มีหลาย Scenario เราต้องพิมพ์คีย์เวิร์ดชุดนี้ซ้ำ ๆ ดังนั้นจึงนำมาสร้างเป็นคีย์เวิร์ดและทำการกำหนด Argument ไว้สำหรับรับค่า

  1. Test cases

ทีนี้เราก็เพียงแค่เรียกคีย์เวิร์ดที่เราสร้างมาใช้ แล้วกำหนดสิ่งที่เราคาดหวัง หรือผลลัพธ์ที่จะต้องปรากฎ ด้วยคีย์เวิร์ดWait Until Page Contains หรือ Wait Until Page Contains Element

เสร็จแล้วก็สั่งรันกด Ctrl+B

35

 

36

37

ผลการรันใน Sublime

39

ไฟล์ log.html

40

 

________________________________________________________________________

บทความอื่น ๆ ที่เกี่ยวกับ Robot framework

________________________________________________________________________

เรียบเรียงโดย

ทัศนีย์ คัดเจริญ
Quality Assurance

0 0 Continue Reading →

ถอด Test Script ให้เป็น Robot Script (Robot Framework)

หลังจากที่ทำความเข้าใจกับ Requirement ของระบบงานแล้ว ต่อมา ก็คือการเขียน Test script พอเขียนเสร็จ ก็จะเป็นขั้นตอนของการลงมือ Test ระบบตาม Step ที่เขียนใน Test script ซีงในส่วนของวิธีการที่ทำจะ Test ก็แล้วแต่ว่า Case ไหนเราสามารถทำ Automate test ได้ หรือ Case ไหนที่เราควร Manual Test

โดยในบทความนี้เราจะกล่าวถึงการทำ Automate Test โดยใช้ Robot framework ค่ะ

          “ Robot Framework คือซอฟต์แวร์ Open Source ที่ใช้สำหรับการทำ Acceptance Testing และ ATDD (Acceptance Test-Driven Development) โดยมีรูปแบบ Syntax ที่เป็นภาษาเขียนธรรมดาทำให้การ Test ระบบไม่น่าเบื่ออีกต่อไป ”

 

ตัวอย่างประโยคในการเขียน Test Script

Case 1 : ลงชื่อเข้าสู่ระบบ Facebook  กรณีระบุ Username  และ Password ถูกต้อง ระบบจะแสดงหน้าหลักของเว็บไซต์ Facebook

11

 

จากตัวอย่าง case ข้างต้นเราก็จะเห็น Process การทำงานที่เรียงเป็นลำดับได้ดังนี้

12

 

จาก Process ดังกล่าว เราสามารถนำมาเขียนเป็น Script ใน Robot framework ได้ดังนี้

  1. เริ่มที่การวางโครงสร้างโดยใน sublime สามารถเรียกโครงสร้างของ Robot ได้โดยคลิกขวา > Robot Framework > Snippets

ก็จะปรากฎโครงสร้างส่วนต่าง ๆ ของ Robot ให้เลือกโดยที่เราไม่ต้องพิมพ์เองเลย

13

          บันทึกไฟล์ชื่อ case-1-facebook-login.txt ไว้ในโฟล์เดอร์ที่ต้องการ

14

  1. เมื่อสร้างโครงสร้างเรียบร้อยแล้วก็เริ่มเขียน Test case ได้เลย ในกรอบสี่เหลี่ยมสีชมพูคือคีย์เวิร์ด ที่สั่งให้สคริปทำงานนั่นเองค่ะ สามารถเข้าไปดู Keyword ใน Selenium2Library ได้ที่นี่

15

คำอธิบาย :

Note : ช่องว่างระหว่าง Keyword กับ Argument ต้องห่างกัน 2 วรรคขึ้นไป ไม่เช่นนั้น Robot จะถือว่าเป็น Keyword เดียวกัน

 

  • Open Browser https://www.facebook.com/    gc

คำสั่งเปิดเว็บไซต์ facebook  จากตัวอย่างจะเขียนตามด้วย  gc  คือจะเป็นการกำหนดเว็บบราวเซอร์เปิดโดยเว็บบราวเซอร์ Google Chrome แต่ถ้าไม่มีการกำหนด ก็จะเปิดเว็บไซต์ด้วย Default web browser นั่นคือ Firefox

  • Wait Until Page Contains โลโก้ Facebook

Wait Until Page Contains เป็นคำสั่งที่ตรวจสอบว่า เจอสิ่งที่เราคาดหวังหรือไม่ จากตัวอย่างคือ เมื่อเปิดเว็บไซต์ facebook ขึ้นมาจะต้องเจอ “โลโก้ Facebook” ซึ่งสิ่งที่เราคาดหวังเราจะหาได้โดยการกด inspec ในหน้าเว็บไซต์

16

          ทั้งนี้สิ่งที่เราคาดหวังอาจจะเป็นได้ทั้งข้อความ, รูปภาพ, Text box หรือ Element อื่น ๆ ก็ได้ แต่ Wait Until Page Contains จะใช้ในกรณีสิ่งที่เราคาดหวังเป็น Text เท่านั้น ถ้าสิ่งที่เราคาดหวังเป็นรูปภาพอาจจะใช้คีย์เวิร์ดอื่นแทน เช่น

Wait Until Page Contains Element    <<Element locator>>

          ซึ่ง Element locator ได้แก่

17

เป็นคำสั่งให้กรอกค่าลงไปใน Text box หรือ Text area ซึ่งระบุ Text box ที่ต้องการให้กรอกด้วย Element locator นั่นเอง

  • Input Password pass    xxx

คำสั่งนี้ลักษณะการทำงานจะเหมือนกับ Input Text แต่ค่าข้อมูลที่ระบุลงไปจะเป็นลักษณะของการกรอก Password

18

  • Click Button เข้าสู่ระบบ

เป็นคำสั่งให้คลิกปุ่ม

  • Wait Until Page Contains หน้าหลัก

ตรวจสอบว่าเมื่อคลิกปุ่มเข้าสู่ระบบแล้ว หากกรอกอีเมล์และรหัสผ่านถูกต้อง ระบบจะแสดงหน้าหลักของ Facebok

  • Close Browser

คำสั่งปิดบราวเซอร์ เมื่อเสร็จสิ้นการทำงาน

  1. หลังจากที่เราทำการเขียนครบทุกคีย์เวิร์ดแล้ว กดบันทึกอีกครั้งแล้วทำการรันโดยกด Ctrl+B Robot ก็จะทำงานโดยเริ่มจากคีย์เวิร์ดในบรรทัดแรกไปจนถึงบรรทัดสุดท้าย

19

20

เมื่อรันเสร็จสิ้น Sublime จะแสดงผลการรันดังนี้

21

 

นอกจากนี้ตัว Robot framework ก็จะ Generate Log file หลังจากที่เสร็จสิ้นการรันเป็น .html ไฟล์ ในโฟล์เดอร์เดียวกับไฟล์ .txt ของเราด้วยซึ่งจะมีลักษณะดังนี้

22

 

Note: คีย์เวิร์ด “Wait Until Page Contain” หรือ “Wait …”  เป็นคีย์เวิร์ดที่สำคัญและจำเป็นที่จะต้องมีหลังจากที่เกิดการกระทำกับระบบที่เราทำการ Test เช่น Mouse action ต่าง ๆ เนื่องจากเป็นการกำหนดว่า พอเกิดการกระทำจากคำสั่งใด ๆ แล้วผลลัพธ์เมื่อการกระทำนั้นเกิดขึ้นจะเป็นอย่างไร

 

________________________________________________________________________

บทความอื่น ๆ ที่เกี่ยวกับ Robot framework

________________________________________________________________________

เรียบเรียงโดย

ทัศนีย์ คัดเจริญ
Quality Assurance

 

0 6 Continue Reading →

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายการใช้คุกกี้ และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

Privacy Preferences

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

Allow All
Manage Consent Preferences
  • คุกกี้ที่จำเป็น
    Always Active

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้

  • คุกกี้เพื่อการวิเคราะห์

    คุกกี้ประเภทนี้จะทำการเก็บข้อมูลการใช้งานเว็บไซต์ของคุณ เพื่อเป็นประโยชน์ในการวัดผล ปรับปรุง และพัฒนาประสบการณ์ที่ดีในการใช้งานเว็บไซต์ ถ้าหากท่านไม่ยินยอมให้เราใช้คุกกี้นี้ เราจะไม่สามารถวัดผล ปรังปรุงและพัฒนาเว็บไซต์ได้

Save