การพิจารณาว่าอะไรคือการทดสอบหน่วยที่มีประโยชน์


46

ฉันได้อ่านเอกสารของ phpunit และพบกับคำพูดต่อไปนี้:

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

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

ดังนั้นถ้ามันไม่เกี่ยวกับการทดสอบมันเกี่ยวกับอะไร มันเกี่ยวกับการหาสิ่งที่คุณกำลังพยายามทำก่อนที่คุณจะคลุ้มคลั่งเพื่อพยายามทำ คุณเขียนข้อกำหนดที่ตอกย้ำพฤติกรรมเล็ก ๆ น้อย ๆ ในรูปแบบที่กระชับไม่คลุมเครือและสามารถใช้งานได้ มันง่ายมาก นั่นหมายความว่าคุณเขียนการทดสอบหรือไม่? ไม่ได้หมายความว่าคุณเขียนข้อกำหนดของรหัสที่คุณต้องทำ หมายความว่าคุณระบุการทำงานของรหัสล่วงหน้า แต่ไม่ไกลเกินเวลา ที่จริงแล้วก่อนที่คุณจะเขียนโค้ดนั้นดีที่สุดเพราะนั่นคือเมื่อคุณมีข้อมูลมากพอ ๆ กับที่คุณจะทำจนถึงจุดนั้น เช่นเดียวกับ TDD ที่ทำได้ดีคุณทำงานทีละน้อย ... ระบุลักษณะเล็ก ๆ ของพฤติกรรมในแต่ละครั้งจากนั้นนำไปใช้ เมื่อคุณตระหนักว่ามันคือทั้งหมดที่เกี่ยวกับการระบุพฤติกรรมและไม่เขียนแบบทดสอบ มุมมองของคุณเปลี่ยนไป ทันใดนั้นความคิดของการมีคลาสทดสอบสำหรับแต่ละคลาสผลิตของคุณนั้น จำกัด อย่างน่าขัน และความคิดในการทดสอบแต่ละวิธีของคุณด้วยวิธีการทดสอบของตัวเอง (ในความสัมพันธ์ 1-1) จะน่าหัวเราะ - เดฟ Astels

ส่วนที่สำคัญคือ

* และความคิดในการทดสอบแต่ละวิธีของคุณด้วยวิธีการทดสอบของตัวเอง (ในความสัมพันธ์ 1-1) จะเป็นเรื่องน่าหัวเราะ * * * *

ดังนั้นหากการสร้างการทดสอบสำหรับแต่ละวิธีคือ 'น่าหัวเราะ' คุณจะเลือกสิ่งที่คุณเขียนการทดสอบได้อย่างไร / เมื่อไหร่?


5
เป็นคำถามที่ดี แต่สิ่งที่ฉันเป็นคำถามการเขียนโปรแกรมภาษาอิสระ - ทำไมคุณติดแท็กมัน php?

นี่คือบางบทความที่ดีจริง ๆ ที่ให้ทิศทางที่ดีแก่ฉันเกี่ยวกับการทดสอบหน่วยที่ฉันควรจะเขียนและพื้นที่ใดมีความสำคัญในการให้การทดสอบอัตโนมัติสำหรับ - blog.stevensanderson.com/2009/11/04/… - blog.stevensanderson.com/2009/08/24/… - ayende.com/blog/4218/scenario-driven-tests
Kane

คำตอบ:


26

วิธีการทดสอบจำนวนมากต่อวิธี?

ค่าสูงสุดของทฤษฏีและทำไม่ได้สูงคือความซับซ้อนของ N-Path (สมมติว่าการทดสอบทั้งหมดครอบคลุมวิธีที่แตกต่างกันผ่านทางรหัส;)) ขั้นต่ำคือ ONE! ตามวิธีการสาธารณะนั่นคือเขาไม่ได้ทดสอบรายละเอียดการใช้งานเฉพาะพฤติกรรมภายนอกของชั้นเรียน (คืนค่าและเรียกวัตถุอื่น ๆ )

คุณพูด:

* และความคิดในการทดสอบแต่ละวิธีของคุณด้วยวิธีการทดสอบของตัวเอง (ในความสัมพันธ์ 1-1) จะน่าหัวเราะ * * * *

แล้วถาม:

ดังนั้นหากการสร้างการทดสอบสำหรับแต่ละวิธีคือ 'น่าหัวเราะ' คุณจะเลือกสิ่งที่คุณเขียนการทดสอบได้อย่างไร / เมื่อไหร่?

แต่ฉันคิดว่าคุณเข้าใจผิดของผู้เขียนที่นี่:

แนวคิดของการมีone test methodต่อone method in the class to testคือสิ่งที่ผู้เขียนเรียกว่า "น่าหัวเราะ"

(สำหรับฉันอย่างน้อย) มันไม่เกี่ยวกับ 'น้อย' มันเกี่ยวกับ 'เพิ่มเติม'

ดังนั้นให้ฉันใช้ถ้อยคำใหม่เหมือนที่ฉันเข้าใจเขา:

และความคิดในการทดสอบแต่ละวิธีของคุณด้วยวิธีการเพียงวิธีเดียวเท่านั้น ( วิธีทดสอบของตัวเองในความสัมพันธ์ 1-1) จะทำให้คุณหัวเราะ

หากต้องการอ้างคำพูดของคุณอีกครั้ง:

เมื่อคุณตระหนักว่ามันคือทั้งหมดที่เกี่ยวกับการระบุพฤติกรรมและไม่เขียนแบบทดสอบมุมมองของคุณจะเปลี่ยนไป


เมื่อคุณฝึก TDD คุณจะไม่คิดว่า :

ฉันมีวิธีการcalculateX($a, $b);และจำเป็นต้องมีการทดสอบtestCalculcateXที่ทดสอบทุกอย่างเกี่ยวกับวิธีการ

สิ่งที่ TDD บอกคุณคือการคิดว่าโค้ดของคุณควรเป็นอย่างไร :

ฉันต้องคำนวณค่าที่ใหญ่กว่าของสองค่า ( กรณีทดสอบครั้งแรก! ) แต่ถ้า $ a มีขนาดเล็กกว่าศูนย์แล้วมันควรจะเกิดข้อผิดพลาด ( กรณีทดสอบที่สอง! ) และถ้า $ b มีขนาดเล็กกว่าศูนย์ก็ควร .... ( กรณีทดสอบที่สาม! ) และอื่น ๆ


คุณต้องการทดสอบพฤติกรรมไม่ใช่เพียงวิธีเดียวที่ไม่มีบริบท

ด้วยวิธีนี้คุณจะได้รับชุดทดสอบที่เป็นเอกสารสำหรับรหัสของคุณและจริงๆแล้วจะอธิบายสิ่งที่คาดว่าจะทำอาจเป็นเพราะเหตุใด :)


คุณจะตัดสินใจเกี่ยวกับชิ้นส่วนของรหัสที่คุณสร้างการทดสอบหน่วยสำหรับได้อย่างไร

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

หากคุณไม่มีการทดสอบมันจะยากขึ้น (แพงกว่า) ในการเปลี่ยนรหัสโดยเฉพาะอย่างยิ่งถ้าคุณไม่ทำการเปลี่ยนแปลง

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



การตอบสนองต่อความคิดเห็น:

ไม่สามารถทดสอบจำนวนวิธีที่เหมาะสมภายในบริบทเฉพาะเนื่องจากขึ้นอยู่กับหรือขึ้นอยู่กับวิธีอื่น

มีสามสิ่งที่วิธีการเหล่านั้นสามารถโทรได้:

วิธีการสาธารณะของชั้นเรียนอื่น ๆ

เราสามารถเยาะเย้ยชั้นเรียนอื่น ๆ เพื่อให้เราได้กำหนดรัฐที่นั่น เราอยู่ในการควบคุมของบริบทเพื่อไม่ให้มีปัญหา

* วิธีการป้องกันหรือส่วนตัวในเดียวกัน *

สิ่งใดก็ตามที่ไม่ได้เป็นส่วนหนึ่งของ API สาธารณะของคลาสจะไม่ได้รับการทดสอบโดยตรง

คุณต้องการที่จะมีพฤติกรรมการทดสอบและการไม่ดำเนินการและถ้าชั้นไม่ทั้งหมดของมันในการทำงานในวิธีสาธารณะขนาดใหญ่หรือในหลาย ๆ วิธีการป้องกันที่มีขนาดเล็กที่ได้รับการเรียกว่าการดำเนินงาน คุณต้องการที่จะเปลี่ยนวิธีการป้องกันเหล่านั้นโดยไม่ต้องสัมผัสการทดสอบของคุณ เพราะการทดสอบของคุณจะแตกถ้ารหัสของคุณเปลี่ยนพฤติกรรมการเปลี่ยนแปลง! นั่นคือสิ่งที่การทดสอบของคุณมีเพื่อบอกคุณเมื่อคุณทำลายบางสิ่งบางอย่าง :)

วิธีสาธารณะในชั้นเรียนเดียวกัน

มันไม่ได้เกิดขึ้นบ่อยนักหรอกเหรอ? และถ้ามันไม่ชอบในตัวอย่างต่อไปนี้มีวิธีการจัดการนี้ไม่กี่:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

ว่าตัวตั้งค่ามีอยู่และไม่ได้เป็นส่วนหนึ่งของลายเซ็นวิธีการดำเนินการเป็นอีกหัวข้อหนึ่ง;)

สิ่งที่เราสามารถทดสอบได้ที่นี่คือถ้าการกระทำเกิดขึ้นเมื่อเราตั้งค่าผิด นั่นsetBlaเป็นข้อยกเว้นเมื่อคุณผ่านสตริงสามารถทดสอบแยกกัน แต่ถ้าเราต้องการทดสอบว่าทั้งสองค่าที่อนุญาต (12 & 14) ไม่ทำงานด้วยกัน (ด้วยเหตุผลใดก็ตาม) กว่ากรณีทดสอบหนึ่งกรณี

หากคุณต้องการชุดทดสอบ "ดี" คุณสามารถเพิ่ม@covers Stuff::executeคำอธิบายประกอบเพื่อให้แน่ใจว่าคุณสร้างรหัสครอบคลุมสำหรับวิธีนี้เท่านั้นและสิ่งอื่น ๆ ที่เป็นเพียงการตั้งค่าจะต้องทำการทดสอบแยกต่างหาก (อีกครั้งหาก คุณต้องการสิ่งนั้น).

ประเด็นก็คือ:บางทีคุณอาจต้องสร้างโลกรอบข้างก่อน แต่คุณควรจะสามารถเขียนกรณีทดสอบที่มีความหมายซึ่งโดยปกติจะมีเพียงหนึ่งหรือสองฟังก์ชันจริงเท่านั้น (ผู้ตั้งค่าไม่นับที่นี่) ส่วนที่เหลือสามารถเยาะเย้ยไปหรือทดสอบก่อนแล้วพึ่งพา (ดู@depends)


* หมายเหตุ: คำถามถูกโยกย้ายจาก SO และเริ่มแรกเกี่ยวกับ PHP / PHPUnit นั่นเป็นสาเหตุว่าทำไมโค้ดตัวอย่างและการอ้างอิงมาจาก php world ฉันคิดว่านี่ใช้ได้กับภาษาอื่นด้วยเนื่องจาก phpunit ไม่แตกต่างจาก xUnit อื่นมากนัก กรอบการทดสอบ


มีรายละเอียดและให้ข้อมูลมาก ... คุณพูดว่า "คุณต้องการทดสอบพฤติกรรมไม่ใช่เพียงวิธีเดียวที่ไม่มีบริบท" แน่นอนว่าวิธีการที่เหมาะสมไม่สามารถทดสอบได้ในบริบทที่เฉพาะเจาะจงเพราะมันขึ้นอยู่กับหรือพึ่งพาวิธีอื่น ดังนั้นเงื่อนไขการทดสอบที่มีประโยชน์จะเป็นเพียงบริบทเท่านั้นหากผู้ติดตามอยู่ในระหว่างการทดสอบด้วยหรือไม่ หรือฉันกำลังตีความสิ่งที่คุณหมายถึง>
zcourts

@ robinsonc494 ฉันจะแก้ไขในตัวอย่างที่อาจจะอธิบายเล็ก ๆ น้อย ๆ ที่ดีกว่าที่ฉันจะมีนี้
edorian

ขอบคุณสำหรับการแก้ไขและตัวอย่างมันช่วยได้อย่างแน่นอน ฉันคิดว่าความสับสนของฉัน (ถ้าคุณเรียกได้ว่าเป็นอย่างนั้น) ก็คือแม้ว่าฉันจะอ่านเกี่ยวกับการทดสอบสำหรับ "พฤติกรรม" แต่อย่างใดฉันก็แค่คิดมาเอง (อาจจะ?) คิดถึงกรณีทดสอบที่เน้นการใช้งาน
zcourts

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

3
ฉันคิดว่าตัวอย่างที่ดีที่สุดที่ฉันเห็นของ TDD ที่ช่วยให้คลิกในการเขียนกรณีทดสอบคือเกมโบว์ลิ่ง Kata โดย Uncle Bob Martin slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Amy Anuszewski

2

การทดสอบและการทดสอบหน่วยไม่ใช่สิ่งเดียวกัน การทดสอบหน่วยเป็นส่วนย่อยที่สำคัญและน่าสนใจของการทดสอบโดยรวม ฉันขออ้างว่าจุดเน้นของการทดสอบหน่วยทำให้เราคิดถึงการทดสอบประเภทนี้ในลักษณะที่ขัดแย้งกับคำพูดด้านบน

ก่อนอื่นถ้าเราทำตาม TDD หรือ DTH (เช่นพัฒนาและทดสอบอย่างใกล้ชิด) เรากำลังใช้แบบทดสอบที่เราเขียนเพื่อมุ่งเน้นการออกแบบที่ถูกต้อง ด้วยการคิดถึงเรื่องมุมและการเขียนการทดสอบดังนั้นเราจึงหลีกเลี่ยงข้อผิดพลาดที่เกิดขึ้นในตอนแรกดังนั้นในความเป็นจริงเราเขียนการทดสอบที่เราคาดว่าจะผ่าน (ตกลงได้ตั้งแต่เริ่ม TDD พวกเขาล้มเหลว แต่นั่นเป็นเพียงสิ่งประดิษฐ์ รหัสนั้นเสร็จแล้วเราคาดหวังให้พวกเขาผ่านและส่วนใหญ่ทำเพราะคุณคิดเกี่ยวกับรหัส

ประการที่สองการทดสอบหน่วยเข้ามาเป็นของตัวเองจริงๆเมื่อเราปรับโครงสร้าง เราเปลี่ยนการใช้งานของเรา แต่คาดหวังว่าคำตอบจะยังคงเหมือนเดิม - การทดสอบหน่วยคือการป้องกันการละเมิดสัญญาส่วนต่อประสาน ดังนั้นอีกครั้งเราคาดหวังว่าการทดสอบผ่าน

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

ในการตอบคำถามที่ชัดเจนของคุณ: การทดสอบหน่วยสำหรับส่วนต่อประสานสาธารณะมีค่า

แก้ไขในความคิดเห็นตอบกลับ:

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


ดังนั้นเพื่อให้แน่ใจว่าฉันเข้าใจสิ่งที่คุณกำลังพูด: เมื่อทำการทดสอบให้เน้นที่การทดสอบส่วนต่อประสานสาธารณะใช่ไหม สมมติว่าถูกต้อง ... ไม่มีความเป็นไปได้ที่ใหญ่กว่าในการทิ้งบั๊กในวิธีการส่วนตัว / อินเตอร์เฟสที่คุณไม่ได้ทำการทดสอบหน่วยสำหรับบั๊กที่ยุ่งยากบางอย่างในอินเทอร์เฟซ "ส่วนตัว" ที่ยังไม่ผ่านการทดสอบอาจนำไปสู่ ควรจะล้มเหลวจริงๆ ฉันคิดผิดอย่างนั้นเหรอ?
zcourts

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

2

การทดสอบหน่วยควรเป็นส่วนหนึ่งของกลยุทธ์การทดสอบขนาดใหญ่ ฉันทำตามหลักการเหล่านี้ในการเลือกประเภทการทดสอบที่จะเขียนและเมื่อ:

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

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

  • รอจนกว่า API ที่คุณกำลังทดสอบมีความเสถียรในการเขียนการทดสอบทั้งสองประเภท คุณต้องการหลีกเลี่ยงการ refactor ทั้งการใช้งานและการทดสอบของคุณ

Rob Ashton มีบทความที่ดีในหัวข้อนี้ซึ่งฉันได้เขียนไว้อย่างชัดเจนเพื่ออธิบายหลักการข้างต้น


+1 - ฉันไม่เห็นด้วยกับคะแนนทั้งหมดของคุณ (หรือคะแนนของบทความ) แต่ที่ฉันเห็นด้วยก็คือการทดสอบหน่วยส่วนใหญ่จะไร้ประโยชน์หากทำแบบสุ่ม (ตาบอด) (แนวทาง TDD) อย่างไรก็ตามสิ่งเหล่านี้มีค่าอย่างยิ่งหากคุณฉลาดในการตัดสินใจเลือกสิ่งที่คุ้มค่ากับการใช้เวลาในการทดสอบหน่วย ฉันเห็นด้วยอย่างยิ่งว่าคุณจะได้รับประโยชน์จากการเขียนการทดสอบระดับที่สูงขึ้นโดยเฉพาะอย่างยิ่งในการทดสอบระดับระบบย่อยโดยเฉพาะ ปัญหาเกี่ยวกับการทดสอบอัตโนมัติแบบ end-to-end คือการที่พวกเขาจะทำได้ยากหากไม่ได้ทำจริงทั้งหมดสำหรับระบบหากมีขนาด / ความซับซ้อนใด ๆ
Dunk

0

ฉันมักจะทำตามแนวทางที่แตกต่างในการทดสอบหน่วยที่ดูเหมือนจะทำงานได้ดี แทนที่จะคิดถึงการทดสอบหน่วยในฐานะ "การทดสอบพฤติกรรมบางอย่าง" ฉันคิดว่ามันเป็น "ข้อกำหนดที่ต้องใช้รหัสของฉัน" ด้วยวิธีนี้คุณสามารถประกาศว่าวัตถุควรทำงานในลักษณะใดและโดยที่คุณคิดว่าที่อื่นในโปรแกรมของคุณคุณสามารถมั่นใจได้ว่ามันค่อนข้างปราศจากข้อบกพร่อง

หากคุณเขียน API สาธารณะนี่เป็นสิ่งที่มีค่าอย่างยิ่ง อย่างไรก็ตามคุณจะต้องใช้การทดสอบการผสานรวมแบบครบวงจรด้วยเช่นกันเพราะการเข้าใกล้การทดสอบหน่วย 100% นั้นไม่คุ้มค่าและจะพลาดสิ่งที่ส่วนใหญ่จะพิจารณาว่า "ไม่สามารถทดสอบได้" โดยวิธีการทดสอบหน่วย (การเยาะเย้ย ฯลฯ )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.