BDD Testing คืออะไร? กรอบการพัฒนาแบบเน้นพฤติกรรม (Behaviour-Driven Development Framework)
⚡ สรุปอย่างชาญฉลาด
การทดสอบแบบ BDD อธิบายพฤติกรรมของแอปพลิเคชันด้วยภาษา Given-When-Then ที่เรียบง่าย เพื่อให้นักวิเคราะห์ นักพัฒนา และผู้ทดสอบใช้ข้อกำหนดเดียวกัน บทความนี้จะนำแนวทางดังกล่าวมาใช้กับการทดสอบ REST API โดยใช้ Behave ซึ่งเป็นเครื่องมือทดสอบ REST API Python กรอบการทำงานที่ครอบคลุมการตั้งค่า ไฟล์คุณลักษณะ การใช้งานตามขั้นตอน การดำเนินการ และการรายงาน
การทดสอบ BDD (การพัฒนาพฤติกรรมขับเคลื่อน) คืออะไร?
การทดสอบ BDD (การพัฒนาที่ขับเคลื่อนด้วยพฤติกรรม) BDD (Behavior-Driven Development) เป็นเทคนิคการพัฒนาซอฟต์แวร์แบบ Agile และเป็นส่วนขยายของ TDD (Test Driven Development) หรือการพัฒนาแบบเน้นการทดสอบ ใน BDD นั้น กรณีทดสอบจะเขียนด้วยภาษาธรรมชาติที่แม้แต่ผู้ที่ไม่ใช่โปรแกรมเมอร์ก็สามารถอ่านได้
การทดสอบ BDD ทำงานอย่างไร
สมมติว่าคุณได้รับมอบหมายให้สร้างโมดูลโอนเงินในแอปพลิเคชันธนาคารออนไลน์
มีหลายวิธีในการทดสอบ:
- การโอนเงินควรดำเนินการได้ก็ต่อเมื่อมีเงินคงเหลือในบัญชีต้นทางเพียงพอ
- การโอนเงินควรเกิดขึ้นหากรายละเอียดบัญชีปลายทางถูกต้อง
- การโอนเงินจะดำเนินการได้หากรหัสผ่านการทำธุรกรรม / รหัส RSA / การตรวจสอบความปลอดภัยที่ผู้ใช้ป้อนถูกต้อง
- การโอนเงินควรดำเนินการต่อไปแม้ว่าจะเป็นวันหยุดธนาคารก็ตาม
- การโอนเงินควรเกิดขึ้นในอนาคตตามที่เจ้าของบัญชีกำหนด
การขอ สถานการณ์ทดสอบ จะมีความซับซ้อนมากขึ้นเมื่อเราพิจารณาคุณสมบัติเพิ่มเติม เช่น การโอนเงินจำนวน X ในช่วงเวลา Y วันหรือเดือน การหยุดping การโอนเงินตามกำหนดเวลาเมื่อยอดรวมถึง Z และอื่นๆ
โดยทั่วไปแล้ว นักพัฒนาซอฟต์แวร์มักจะพัฒนาฟีเจอร์ต่างๆ ก่อน แล้วค่อยเขียนโค้ดทดสอบในภายหลัง ดังที่เห็นได้จากกรณีข้างต้น กรณีทดสอบ การพัฒนาในพื้นที่นี้มีความซับซ้อน ดังนั้นผู้พัฒนาจึงจะเลื่อนกำหนดการออกไป การทดสอบ จนกว่าจะถึงวันวางจำหน่าย ซึ่งในขั้นตอนนี้จะมีการทดสอบอย่างรวดเร็วแต่ไม่มีประสิทธิภาพ
เพื่อแก้ไขปัญหาดังกล่าว จึงได้มีการคิดค้น Behavior Driven Development (BDD) ขึ้นมา ซึ่งทำให้กระบวนการทดสอบทั้งหมดง่ายขึ้นสำหรับนักพัฒนา
ใน BDD สิ่งที่คุณเขียนจะต้องเข้าไปอยู่ในนั้น ให้-เมื่อ-แล้ว ขั้นตอนต่างๆ ลองพิจารณาตัวอย่างเดียวกันข้างต้นในรูปแบบ BDD:
Given that a fund transfer module in net banking application has been developed And I am accessing it with proper authentication When I shall transfer with enough balance in my source account Or I shall transfer on a Bank Holiday Or I shall transfer on a future date And destination a/c details are correct And transaction password / rsa code / security authentication for the transaction is correct And press or click send button Then amount must be transferred And the event will be logged in log file
แบบฟอร์มนี้เขียนง่าย อ่านง่าย และเข้าใจง่าย ครอบคลุมทุกกรณีทดสอบที่เป็นไปได้สำหรับโมดูลการโอนเงิน และสามารถปรับเปลี่ยนได้อย่างรวดเร็วเพื่อรองรับกรณีทดสอบเพิ่มเติม นอกจากนี้ยังอ่านง่ายเหมือนเอกสารประกอบการใช้งานโมดูลที่มีการปรับปรุงอยู่เสมอ เนื่องจาก BDD พัฒนามาจาก TDD ความแตกต่างระหว่างทั้งสองจึงควรทำความเข้าใจให้ชัดเจนก่อนที่จะก้าวไปสู่การใช้งานเครื่องมือ
BDD กับ TDD: ความแตกต่างที่สำคัญ
ทั้งสองวิธีเน้นการทดสอบก่อน และช่วยลดระยะเวลาในการได้รับผลตอบรับ ความแตกต่างอยู่ที่ผู้เขียนการทดสอบ ภาษาที่ใช้ในการทดสอบ และส่วนใดของแอปพลิเคชันที่ถูกทดสอบ
| แง่มุม | TDD (การพัฒนาแบบขับเคลื่อนด้วยการทดสอบ) | BDD (การพัฒนาที่ขับเคลื่อนด้วยพฤติกรรม) |
|---|---|---|
| โฟกัส | วิธีการทำงานภายในของหน่วยโค้ด | ระบบทำงานอย่างไรในมุมมองของผู้ใช้ |
| ภาษาที่ใช้ | การยืนยันภาษาการเขียนโปรแกรม | สถานการณ์ Given-When-Then ในภาษาธรรมชาติ |
| ผู้เขียนหลัก | ผู้พัฒนา | นักวิเคราะห์ธุรกิจ เจ้าของผลิตภัณฑ์ ผู้ทดสอบ และนักพัฒนาซอฟต์แวร์ ทำงานร่วมกัน |
| Readable โดยผู้ที่ไม่ใช่โปรแกรมเมอร์ | ไม่ | มี (ใบกำกับภาษีเต็มรูปแบบ) |
| ขอบเขตทั่วไป | ระดับหน่วย | คุณสมบัติ, API และระดับการยอมรับ |
| เครื่องมือทั่วไป | JUnitpytest, NUnit | ประพฤติ, CucumberSpecFlow, JBehave |
ทั้งสองอย่างเป็นสิ่งที่ส่งเสริมซึ่งกันและกันมากกว่าที่จะแข่งขันกัน โดยทั่วไปแล้วทีมต่างๆ มักจะรักษาไว้ซึ่งสิ่งต่างๆ เหล่านี้ TDD สำหรับการออกแบบในระดับหน่วย และเพิ่มสถานการณ์ BDD เข้าไปเพื่ออธิบายพฤติกรรมที่ผู้มีส่วนได้ส่วนเสียอนุมัติจริง ๆ เนื่องจาก REST endpoint อยู่ในระดับการยอมรับนั้นพอดี จึงเหมาะสมอย่างยิ่งสำหรับ BDD
การทดสอบ REST API คืออะไร
เนื่องจาก REST ได้กลายเป็นรูปแบบที่ได้รับความนิยมในการสร้าง API การทดสอบ REST API แบบอัตโนมัติจึงมีความสำคัญไม่แพ้กันเมื่อเทียบกับการทดสอบ UI การทดสอบ API เกี่ยวข้องกับการทดสอบการดำเนินการ CRUD (สร้าง-อ่าน-อัปเดต-ลบ) ด้วยวิธีการ POST, GET, PUT และ DELETE ตามลำดับ
ประพฤติคืออะไร?
ประพฤติเป็นหนึ่งในความนิยม Python เฟรมเวิร์กการทดสอบ BDD ต่อไปนี้คือวิธีการทำงานของฟังก์ชัน:
- ไฟล์รายละเอียดคุณลักษณะ (Feature file) เขียนโดยนักวิเคราะห์ธุรกิจ ผู้สนับสนุน หรือผู้รับผิดชอบสถานการณ์พฤติกรรมต่างๆ ไฟล์รายละเอียดคุณลักษณะใช้รูปแบบภาษาธรรมชาติในการอธิบายคุณลักษณะ หรือส่วนหนึ่งของคุณลักษณะ พร้อมตัวอย่างที่แสดงถึงผลลัพธ์ที่คาดหวัง
- ขั้นตอนสถานการณ์เหล่านี้ถูกแมปเข้ากับการดำเนินการตามขั้นตอนที่เขียนไว้ใน Python.
- นอกจากนี้ ระบบควบคุมสภาพแวดล้อมยังสามารถรันโค้ดก่อนและหลังขั้นตอน สถานการณ์ คุณสมบัติ หรือการทำงานทั้งหมดได้อีกด้วย
เมื่อเข้าใจบทบาทของแต่ละไฟล์อย่างชัดเจนแล้ว ขั้นตอนต่อไปคือการติดตั้งเฟรมเวิร์ก
การตั้งค่า BDD Testing Framework ทำงาน Windows
การติดตั้ง:
- ดาวน์โหลดและติดตั้ง Python 3 จาก https://www.python.org/
- เรียกใช้คำสั่งต่อไปนี้ที่พรอมต์คำสั่งเพื่อติดตั้ง Behave
pip install behave- ไอดี: PyCharm ในที่นี้ใช้ Community Edition — https://www.jetbrains.com/pycharm/download/
การตั้งค่าโครงการ:
- สร้างโครงการใหม่
- สร้างโครงสร้างไดเร็กทอรีดังต่อไปนี้
ภาพหน้าจอข้างต้นแสดงเค้าโครงที่ Behave คาดหวัง: a ที่ ไดเร็กทอรีที่เก็บไฟล์ฟีเจอร์ และไดเร็กทอรีย่อยที่ซ้อนกัน ทำตามขั้นตอน ไดเร็กทอรีที่เก็บ Python การใช้งาน Behave ค้นหาทั้งสองอย่างโดยใช้ชื่อ ดังนั้นชื่อโฟลเดอร์ต้องตรงกันทุกประการ
ไฟล์คุณสมบัติ:
ต่อไปเรามาสร้างไฟล์ฟีเจอร์กันเถอะ Sample_REST_API_Testing.featureโดยมีคุณสมบัติหลักคือการดำเนินการ CRUD บนบริการ 'โพสต์'
ตัวอย่างนี้ใช้ https://jsonplaceholder.typicode.com/ บทความนี้เสนอตัวอย่างบริการ REST ซึ่งเป็น API จำลองฟรี ที่รับคำขอเขียนและส่งคืนการตอบสนองที่สมจริงโดยไม่บันทึกการเปลี่ยนแปลง
ตัวอย่างสถานการณ์ POST
Scenario: POST post example -> creating a new post item using the 'posts' service Given I set post posts API endpoint -> prerequisite: sets the URL of the posts service When I set HEADER param request content type as "application/json" And set request body And send POST HTTP request -> the actual test step Then I receive valid HTTP response code 201 And Response body "POST" is non-empty -> verification of the response body
ในทำนองเดียวกัน คุณสามารถเขียนสถานการณ์ที่เหลือได้ดังนี้:
Sample_REST_API_Testing.feature
Feature: Test CRUD methods in Sample REST API testing framework Background: Given I set sample REST API url Scenario: POST post example Given I Set POST posts api endpoint When I Set HEADER param request content type as "application/json" And Set request Body And Send a POST HTTP request Then I receive valid HTTP response code 201 And Response BODY "POST" is non-empty Scenario: GET posts example Given I Set GET posts api endpoint "1" When I Set HEADER param request content type as "application/json" And Send GET HTTP request Then I receive valid HTTP response code 200 for "GET" And Response BODY "GET" is non-empty Scenario: UPDATE posts example Given I Set PUT posts api endpoint for "1" When I Set Update request Body And Send PUT HTTP request Then I receive valid HTTP response code 200 for "PUT" And Response BODY "PUT" is non-empty Scenario: DELETE posts example Given I Set DELETE posts api endpoint for "1" When I Send DELETE HTTP request Then I receive valid HTTP response code 200 for "DELETE"
ขั้นตอนการดำเนินการ
สำหรับขั้นตอนการทำงานที่ใช้ในสถานการณ์ข้างต้น คุณสามารถเขียนการใช้งานได้ดังนี้ Python ไฟล์ต่างๆ ภายในไดเร็กทอรี “steps”
เฟรมเวิร์ก Behave ระบุฟังก์ชันขั้นตอนโดยการจับคู่ตัวตกแต่งกับเงื่อนไขในไฟล์คุณลักษณะ ตัวอย่างเช่น ให้ เงื่อนไขในสถานการณ์ไฟล์คุณลักษณะจะค้นหาฟังก์ชันขั้นบันไดที่บรรจุ @given นักตกแต่ง การจับคู่แบบเดียวกันนี้เกิดขึ้นสำหรับ เมื่อ และ แล้วก็ในกรณีของ 'But' และ 'And' ฟังก์ชันขั้นตอนจะใช้ตัวตกแต่ง (decorator) เดียวกันกับขั้นตอนก่อนหน้า ตัวอย่างเช่น ถ้า 'And' ตามหลัง Given ตัวตกแต่งฟังก์ชันขั้นตอนที่ตรงกันคือ @given.
ตัวอย่างเช่น ขั้นตอน "เมื่อไร" สำหรับ POST สามารถนำไปใช้ได้ดังนี้ โปรดสังเกตวิธีการ "application/json" จะถูกส่งผ่านจากไฟล์คุณลักษณะไปยังตัวยึดตำแหน่งที่มีวงเล็บ — ซึ่งเรียกว่าการกำหนดพารามิเตอร์
# Decorator: the braced name captures a value from the feature file @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): # Step implementation: set the content type on the request header request_headers['Content-Type'] = header_content_type
ในทำนองเดียวกัน การดำเนินการตามขั้นตอนอื่นๆ ในขั้นตอนนี้ Python ไฟล์จะมีลักษณะดังนี้:
ตัวอย่าง_step_implementation.py
from behave import given, when, then, step import requests api_endpoints = {} request_headers = {} response_codes = {} response_texts = {} request_bodies = {} api_url = None @given(u'I set sample REST API url') def step_impl(context): global api_url api_url = 'https://jsonplaceholder.typicode.com' # START POST Scenario @given(u'I Set POST posts api endpoint') def step_impl(context): api_endpoints['POST_URL'] = api_url + '/posts' print('url :' + api_endpoints['POST_URL']) @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): request_headers['Content-Type'] = header_content_type # "And" or "But" steps are renamed by behave to match their preceding step @when(u'Set request Body') def step_impl(context): request_bodies['POST'] = {"title": "foo", "body": "bar", "userId": "1"} @when(u'Send POST HTTP request') def step_impl(context): # send the request and save the response object response = requests.post(url=api_endpoints['POST_URL'], json=request_bodies['POST'], headers=request_headers) response_texts['POST'] = response.text print("post response :" + response.text) response_codes['POST'] = response.status_code @then(u'I receive valid HTTP response code 201') def step_impl(context): print('Post rep code ;' + str(response_codes['POST'])) assert response_codes['POST'] == 201 # END POST Scenario # START GET Scenario @given(u'I Set GET posts api endpoint "{id}"') def step_impl(context, id): api_endpoints['GET_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['GET_URL']) @when(u'Send GET HTTP request') def step_impl(context): response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers) response_texts['GET'] = response.text response_codes['GET'] = response.status_code @then(u'I receive valid HTTP response code 200 for "{request_name}"') def step_impl(context, request_name): print('Get rep code for ' + request_name + ':' + str(response_codes[request_name])) assert response_codes[request_name] == 200 @then(u'Response BODY "{request_name}" is non-empty') def step_impl(context, request_name): print('request_name: ' + request_name) print(response_texts) assert response_texts[request_name] is not None # END GET Scenario # START PUT/UPDATE @given(u'I Set PUT posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['PUT_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['PUT_URL']) @when(u'I Set Update request Body') def step_impl(context): request_bodies['PUT'] = {"title": "foo", "body": "bar", "userId": "1", "id": "1"} @when(u'Send PUT HTTP request') def step_impl(context): response = requests.put(url=api_endpoints['PUT_URL'], json=request_bodies['PUT'], headers=request_headers) response_texts['PUT'] = response.text print("update response :" + response.text) response_codes['PUT'] = response.status_code # END PUT/UPDATE # START DELETE @given(u'I Set DELETE posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['DELETE_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['DELETE_URL']) @when(u'I Send DELETE HTTP request') def step_impl(context): response = requests.delete(url=api_endpoints['DELETE_URL']) response_texts['DELETE'] = response.text print("DELETE response :" + response.text) response_codes['DELETE'] = response.status_code # END DELETE
⚠️หมายเหตุ: การเปรียบเทียบรหัสสถานะกับ == มากกว่า is เรื่องราวต่างๆ is ตัวดำเนินการจะทดสอบเอกลักษณ์ของวัตถุ ไม่ใช่ความเท่าเทียมกัน และ Python 3.8 และต่อมาก็เพิ่มขึ้น SyntaxWarning: “is” with a literal สำหรับรูปแบบนั้น ข้อความยืนยันใดๆ ที่เขียนไว้ดังนี้ assert code is 201 ควรเขียนใหม่เป็น assert code == 201.
ดำเนินการทดสอบ
การพัฒนาสคริปต์ทดสอบเสร็จสมบูรณ์แล้ว ดังนั้นเรามาทำการทดสอบกัน เรียกใช้ไฟล์ฟีเจอร์โดยใช้คำสั่งต่อไปนี้ที่พรอมต์คำสั่ง:
:: Run a single feature file with the pretty formatter behave -f pretty features\feature_files_folder\Sample_REST_API_Testing.feature :: Run every feature in the project behave
ผลลัพธ์ของการทดสอบจะแสดงดังต่อไปนี้:
รายงานการแสดงผลบนคอนโซล
การแสดงผลข้อมูลผ่านคอนโซลนั้นสะดวกในระหว่างการพัฒนา แต่โดยทั่วไปแล้วผู้มีส่วนได้ส่วนเสียมักต้องการรายงานที่อ่านง่าย ซึ่ง Allure ก็สามารถมอบให้ได้
รายงาน
ขั้นแรก ติดตั้งโปรแกรมจัดรูปแบบ Allure Behave และเครื่องมือบรรทัดคำสั่ง Allure โปรแกรมจัดรูปแบบนี้คือ... Python แพ็คเกจ; เครื่องมือบรรทัดคำสั่งเป็นไฟล์ดาวน์โหลดแยกต่างหาก ซึ่งมีรายละเอียดอยู่ในเอกสารประกอบ เอกสารรายงาน Allure.
:: 1. Install the formatter pip install allure-behave :: 2. Run the tests and write raw results to a folder behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results features/ :: 3. Render and open the HTML report allure serve reports/allure-results
วิธีนี้จะสร้างรายงานผลการทดสอบในรูปแบบที่ดูดีและให้ข้อมูลครบถ้วน ดังนี้:
รายงานการทดสอบในรูปแบบ HTML
รายงานการทดสอบแสดงผลสถานการณ์แต่ละรายการ
ชุดเครื่องมือที่ใช้งานได้นั้นจะมีประโยชน์ก็ต่อเมื่อมันยังคงอ่านง่ายแม้ว่า API จะมีการพัฒนาไปเรื่อยๆ ซึ่งนี่คือจุดที่ระเบียบวินัยในการจัดการไฟล์ฟีเจอร์จะให้ผลตอบแทนที่ดี
แนวทางปฏิบัติที่ดีที่สุดสำหรับการเขียนไฟล์ฟีเจอร์ Behave
ชุด Behave จะด้อยประสิทธิภาพลงอย่างรวดเร็วเมื่อสถานการณ์จำลองอธิบายถึงการคลิกและข้อมูลที่ส่งผ่านแทนที่จะเป็นพฤติกรรม แนวทางปฏิบัติด้านล่างนี้จะช่วยให้ไฟล์ฟีเจอร์อ่านง่ายสำหรับผู้มีส่วนได้ส่วนเสียทางธุรกิจ ในขณะเดียวกันก็รักษา...ping Python เลเยอร์นี้ช่วยให้วิศวกรสามารถดูแลรักษาได้ง่าย
- อธิบายพฤติกรรม ไม่ใช่การนำไปใช้งาน เขียนว่า “เมื่อลูกค้ามีเงินคงเหลือเพียงพอ” ไม่ใช่ “เมื่อยอดเงินคงเหลือเท่ากับ 500” ไฟล์ฟีเจอร์ระบุว่าระบบทำอะไร ส่วนไฟล์สเต็ประบุวิธีการตรวจสอบ
- จำกัดพฤติกรรมไว้เพียงหนึ่งอย่างต่อสถานการณ์ สถานการณ์ที่ระบุรหัสสถานะ เนื้อหาการตอบกลับ และแถวในฐานข้อมูลนั้น แท้จริงแล้วคือสถานการณ์ย่อยสามสถานการณ์ การแยกสถานการณ์เหล่านี้ทำให้ความล้มเหลวสามารถระบุสาเหตุได้อย่างตรงจุด
- ย้ายการตั้งค่าที่ใช้ร่วมกันไปไว้ในพื้นหลัง ตัวอย่างข้างต้นใช้
Background: Given I set sample REST API urlเพื่อให้ทุกสถานการณ์สืบทอดพื้นฐานนั้น URL โดยไม่พูดซ้ำ - ใช้การกำหนดพารามิเตอร์แทนการทำซ้ำ ตัวยึดตำแหน่ง เช่น
"{request_name}"ให้ฟังก์ชันขั้นตอนเดียวทำหน้าที่ตรวจสอบความถูกต้องของข้อมูล (GET, PUT และ DELETE) ซึ่งเป็นเหตุผลว่าทำไมตัวอย่างนี้จึงต้องการคำจำกัดความของขั้นตอนน้อยกว่าสถานการณ์จำลองมาก - ใช้โครงร่างสถานการณ์จำลองสำหรับข้อมูลที่มีความแปรผัน เมื่อต้องพิสูจน์พฤติกรรมเดียวกันสำหรับอินพุตหลายตัว
Examples:ตารางมีความชัดเจนกว่าการคัดลอกสถานการณ์จำลอง - หลีกเลี่ยงการพึ่งพาซึ่งกันและกันระหว่างสถานการณ์ต่างๆ Behave ไม่รับประกันว่าสถานการณ์ POST จะทำงานก่อนสถานการณ์ GET ในทุกการตั้งค่า สถานการณ์แต่ละอย่างควรสร้างสถานะที่จำเป็นของตนเอง
- แทนที่ตัวแปรส่วนกลางระดับโมดูลด้วยตัวแปรบริบท ตัวอย่างนี้จัดเก็บเอนด์พอยต์และคำตอบไว้ในพจนานุกรมของโมดูลเพื่อความกระชับ ในชุดทดสอบจริง ควรจัดเก็บไว้ใน Behave
contextเพื่อให้สถานะของวัตถุถูกรีเซ็ตอย่างราบรื่นระหว่างสถานการณ์ต่างๆ - สถานการณ์การติดแท็กสำหรับการทดลองใช้งานแบบเลือกเฉพาะ แท็กต่างๆ เช่น
@smokeor@regressionอนุญาตbehave --tags=@smokeซึ่งช่วยให้การตอบรับจากกระบวนการทำงานรวดเร็วขึ้น
การนำนิสัยเหล่านี้ไปใช้ตั้งแต่ไฟล์ฟีเจอร์แรก จะทำให้ชุดเครื่องมือ BDD มีคุณค่ามากกว่าแค่ตัวอย่าง CRUD เริ่มต้น ทีมที่ต่อยอดแนวทางนี้มักจะนำไปใช้ร่วมกับวิธีการอื่นๆ ด้วย Cucumber สำหรับโปรเจ็กต์ JVM หรือ Postman สำหรับการตรวจสอบ API ในเชิงสำรวจ







