วิธีการทดสอบหน่วยวัตถุด้วยแบบสอบถามฐานข้อมูล


153

ฉันได้ยินมาว่าการทดสอบหน่วยเป็น "สุดยอด", "สุดยอดจริงๆ" และ "ทุกอย่างเป็นสิ่งที่ดี" แต่ 70% หรือมากกว่านั้นของไฟล์เกี่ยวข้องกับการเข้าถึงฐานข้อมูล (บางคนอ่านและเขียนบางคน) และฉันไม่แน่ใจว่าจะทำอย่างไร เพื่อเขียนการทดสอบหน่วยสำหรับไฟล์เหล่านี้

ฉันใช้ PHP และ Python แต่ฉันคิดว่าเป็นคำถามที่ใช้กับภาษาส่วนใหญ่ / ทุกภาษาที่ใช้การเข้าถึงฐานข้อมูล

คำตอบ:


82

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

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

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

ตอนนี้ในการทดสอบหน่วยของคุณคุณสร้างจำลองของ FooDataProvider ซึ่งช่วยให้คุณสามารถเรียกใช้เมธอด GetAllFoos ได้โดยไม่ต้องกดฐานข้อมูลจริงๆ

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

สถานการณ์จำลองทั่วไปโดยสังเขป แน่นอนคุณอาจจะยังต้องการทดสอบหน่วยการโทรฐานข้อมูลจริงของคุณด้วยซึ่งคุณจะต้องกดฐานข้อมูล


ฉันรู้ว่ามันเก่า แต่สิ่งที่เกี่ยวกับการสร้างตารางที่ซ้ำกันไปยังหนึ่งที่อยู่ในฐานข้อมูลแล้ว วิธีที่คุณสามารถยืนยันการโทร DB ทำงานได้หรือไม่
bretterer

1
ฉันใช้ PDO ของ PHP เป็นระดับต่ำสุดในการเข้าถึงฐานข้อมูลซึ่งฉันแยกอินเทอร์เฟซ จากนั้นฉันสร้างแอปพลิเคชันเลเยอร์ฐานข้อมูลขึ้นมา นี่คือเลเยอร์ที่เก็บเคียวรี SQL ดิบและข้อมูลอื่น ๆ ทั้งหมด แอปพลิเคชั่นที่เหลือจะโต้ตอบกับฐานข้อมูลระดับสูงกว่านี้ ฉันพบว่าสิ่งนี้ทำงานได้ค่อนข้างดีสำหรับการทดสอบหน่วย ฉันทดสอบหน้าแอปพลิเคชันของฉันในวิธีที่พวกเขาโต้ตอบกับฐานข้อมูลแอปพลิเคชัน ฉันทดสอบฐานข้อมูลแอปพลิเคชันของฉันในการโต้ตอบกับ PDO ฉันถือว่า PDO ทำงานโดยไม่มีข้อบกพร่อง รหัสที่มา: manx.codeplex.com
ถูกต้องตามกฎหมาย

1
@bretterer - การสร้างตารางที่ซ้ำกันเป็นสิ่งที่ดีสำหรับการทดสอบการรวม สำหรับการทดสอบหน่วยคุณมักจะใช้วัตถุจำลองซึ่งจะช่วยให้คุณทดสอบหน่วยของรหัสโดยไม่คำนึงถึงฐานข้อมูล
BornToCode

2
ค่าในการเยาะเย้ยเรียกฐานข้อมูลในการทดสอบหน่วยของคุณคืออะไร? ดูเหมือนจะไม่มีประโยชน์เพราะคุณสามารถเปลี่ยนการใช้งานเพื่อให้ได้ผลลัพธ์ที่แตกต่าง แต่การทดสอบหน่วยของคุณจะผ่าน (ไม่ถูกต้อง)
bmay2

2
@ bmay2 คุณไม่ผิด คำตอบดั้งเดิมของฉันเขียนเมื่อนานมาแล้ว (9 ปี!) เมื่อผู้คนจำนวนมากไม่ได้เขียนรหัสของพวกเขาในลักษณะที่สามารถทดสอบได้และเมื่อเครื่องมือทดสอบขาดอย่างรุนแรง ฉันจะไม่แนะนำวิธีนี้อีกต่อไป วันนี้ฉันเพิ่งจะตั้งค่าฐานข้อมูลทดสอบและเติมด้วยข้อมูลที่ฉันต้องการสำหรับการทดสอบและ / หรือออกแบบรหัสของฉันเพื่อให้ฉันสามารถทดสอบตรรกะได้มากที่สุดโดยไม่ต้องมีฐานข้อมูลเลย
Doug R

25

หากเป็นไปได้วัตถุของคุณควรมีความรู้ถาวร ตัวอย่างเช่นคุณควรมี "data access layer" ซึ่งคุณจะต้องร้องขอซึ่งจะส่งคืนวัตถุ ด้วยวิธีนี้คุณสามารถปล่อยส่วนนั้นออกจากการทดสอบหน่วยของคุณหรือทดสอบแยก

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

ในโครงการ c # ของฉันฉันใช้ NHibernate กับชั้นข้อมูลที่แยกจากกันอย่างสมบูรณ์ วัตถุของฉันอยู่ในรูปแบบโดเมนหลักและเข้าถึงได้จากชั้นแอปพลิเคชันของฉัน แอปพลิเคชันเลเยอร์พูดถึงทั้งชั้นข้อมูลและเลเยอร์โมเดลโดเมน

บางครั้งชั้นแอปพลิเคชันก็บางครั้งเรียกว่า "ชั้นธุรกิจ"

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

ตัวเลือกอื่นคือใช้การเยาะเย้ย / สตับ


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

3
หากวัตถุของคุณอยู่คู่กับชั้นข้อมูลของคุณอย่างแน่นหนามันเป็นการยากที่จะทำการทดสอบหน่วยที่เหมาะสม ส่วนแรกของการทดสอบหน่วยคือ "หน่วย" ทุกหน่วยควรจะสามารถทดสอบแยก คำอธิบายที่ดี
Amitābha

11

วิธีที่ง่ายที่สุดในการทดสอบหน่วยวัตถุที่มีการเข้าถึงฐานข้อมูลคือการใช้ขอบเขตธุรกรรม

ตัวอย่างเช่น:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

สิ่งนี้จะเปลี่ยนสถานะของฐานข้อมูลกลับคืนโดยทั่วไปเป็นธุรกรรมย้อนกลับเพื่อให้คุณสามารถเรียกใช้การทดสอบได้บ่อยเท่าที่คุณต้องการโดยไม่มีผลข้างเคียงใด ๆ เราใช้วิธีนี้ในโครงการขนาดใหญ่ได้สำเร็จ งานสร้างของเราใช้เวลานานในการรัน (15 นาที) แต่ก็ไม่น่ากลัวสำหรับการทดสอบ 1,800 หน่วย นอกจากนี้หากเวลาในการสร้างเป็นเรื่องที่กังวลคุณสามารถเปลี่ยนกระบวนการสร้างให้มีหลายบิลด์หนึ่งสำหรับการสร้าง src อีกหนึ่งที่เริ่มขึ้นหลังจากนั้นที่จัดการทดสอบหน่วยการวิเคราะห์รหัสบรรจุภัณฑ์ ฯลฯ ...


1
+1 ประหยัดเวลาได้มากเมื่อหน่วยทดสอบเลเยอร์การเข้าถึงข้อมูล เพิ่งทราบว่า TS มักจะต้องการ MSDTC ซึ่งอาจไม่เป็นที่ต้องการ (ขึ้นอยู่กับว่าแอปของคุณจะต้องมี MSDTC) หรือไม่
StuartLC

คำถามเดิมเกี่ยวกับ PHP ตัวอย่างนี้ดูเหมือนจะเป็น C # สภาพแวดล้อมแตกต่างกันมาก
ทำให้ถูกกฎหมาย

2
ผู้เขียนคำถามระบุว่าเป็นคำถามทั่วไปที่ใช้กับทุกภาษาที่เกี่ยวข้องกับฐานข้อมูล
Vedran

9
และเพื่อนที่รักนี้เรียกว่าการทดสอบการรวม
AA

10

ฉันอาจให้ประสบการณ์ของเราแก่คุณเมื่อเราเริ่มดูการทดสอบหน่วยกระบวนการระดับกลางของเราที่มีการดำเนินงาน "ตรรกะทางธุรกิจ" ของ sql

ก่อนอื่นเราสร้างเลเยอร์นามธรรมที่อนุญาตให้เรา "เชื่อมต่อใน" การเชื่อมต่อฐานข้อมูลใด ๆ ที่สมเหตุสมผล (ในกรณีของเราเราเพียงรองรับการเชื่อมต่อ ODBC ประเภทเดียว)

เมื่ออยู่ในสถานที่เราก็สามารถทำสิ่งนี้ในรหัสของเรา (เราทำงานใน C ++ แต่ฉันแน่ใจว่าคุณได้รับความคิด):

GetDatabase (). ExecuteSQL ("INSERT INTO foo (blah, blah)")

ในเวลาทำงานปกติ GetDatabase () จะคืนค่าออบเจ็กต์ที่เลี้ยง sql ของเรา (รวมถึงการสืบค้น) ผ่าน ODBC ไปยังฐานข้อมูลโดยตรง

จากนั้นเราเริ่มดูฐานข้อมูลในหน่วยความจำ - วิธีที่ดีที่สุดคือ SQLite ( http://www.sqlite.org/index.html ) มันง่ายอย่างน่าทึ่งในการตั้งค่าและใช้งานและอนุญาตให้เรา subclass และ override GetDatabase () เพื่อส่งต่อ sql ไปยังฐานข้อมูลในหน่วยความจำที่สร้างและทำลายสำหรับการทดสอบทุกครั้งที่ดำเนินการ

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

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

เห็นได้ชัดว่าประสบการณ์ของเรามีศูนย์กลางที่สภาพแวดล้อมการพัฒนา C ++ อย่างไรก็ตามฉันแน่ใจว่าคุณอาจได้รับสิ่งที่คล้ายกันภายใต้ PHP / Python

หวังว่านี่จะช่วยได้


9

คุณควรจำลองการเข้าถึงฐานข้อมูลถ้าคุณต้องการทดสอบหน่วยการเรียนของคุณ ท้ายที่สุดคุณไม่ต้องการทดสอบฐานข้อมูลในการทดสอบหน่วย นั่นคือการทดสอบบูรณาการ

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


6

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


4

ตัวเลือกที่คุณมี:

  • เขียนสคริปต์ที่จะล้างฐานข้อมูลก่อนที่คุณจะเริ่มการทดสอบหน่วยจากนั้นเติม db ด้วยชุดข้อมูลที่กำหนดไว้ล่วงหน้าและรันการทดสอบ คุณสามารถทำได้ก่อนการทดสอบทุกครั้ง - มันจะช้า แต่มีข้อผิดพลาดน้อยลง
  • ฉีดฐานข้อมูล (ตัวอย่างใน pseudo-Java แต่ใช้ได้กับทุกภาษา OO)

    ฐานข้อมูลคลาส {
     แบบสอบถามผลลัพธ์สาธารณะ (แบบสอบถามสตริง) {... db จริงที่นี่ ... }
    }

    คลาส MockDatabase ขยายฐานข้อมูล { แบบสอบถามผลลัพธ์สาธารณะ (แบบสอบถามสตริง) { กลับ "ผลการเยาะเย้ย"; } }

    คลาส ObjectThatUsesDB { พับลิก ObjectThatUsesDB (ฐานข้อมูล db) { this.database = db; } }

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

  • อย่าใช้ DB เลยตลอดการใช้งานโค้ดส่วนใหญ่ สร้างวัตถุ "ฐานข้อมูล" ที่แทนที่จะส่งคืนผลลัพธ์จะคืนค่าวัตถุปกติ (เช่นจะส่งคืนUserแทนที่จะเป็น tuple {name: "marcin", password: "blah"}) เขียนการทดสอบทั้งหมดของคุณด้วย ad hoc ที่สร้างวัตถุจริงและเขียนหนึ่งการทดสอบขนาดใหญ่ที่ขึ้นอยู่กับฐานข้อมูลที่ทำให้แน่ใจว่าการแปลงนี้ ทำงานได้ดี

แน่นอนว่าวิธีการเหล่านี้ไม่ได้เกิดขึ้นพร้อมกันและคุณสามารถผสมผสานและจับคู่ได้ตามที่คุณต้องการ


3

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

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

หากคุณต้องการทดสอบการเข้าถึงฐานข้อมูลจริงคุณจะต้องจบการทดสอบการรวมมากขึ้นเพราะคุณจะต้องพึ่งพาเครือข่ายสแต็คและเซิร์ฟเวอร์ฐานข้อมูลของคุณ แต่คุณสามารถตรวจสอบว่ารหัส SQL ทำตามที่คุณขอ ทำ.

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

ขออภัยฉันไม่มีตัวอย่างรหัสเฉพาะสำหรับ PHP / Python แต่ถ้าคุณต้องการดูตัวอย่าง. NET ฉันมีโพสต์ที่อธิบายถึงเทคนิคที่ฉันใช้ทำการทดสอบเดียวกันนี้


2

ฉันมักจะพยายามเลิกทดสอบระหว่างการทดสอบวัตถุ (และ ORM ถ้ามี) และทดสอบ db ฉันทดสอบฝั่งวัตถุของสิ่งต่าง ๆ โดยเยาะเย้ยการเรียกใช้การเข้าถึงข้อมูลในขณะที่ฉันทดสอบด้าน db ของสิ่งต่าง ๆ โดยการทดสอบการโต้ตอบของวัตถุกับฐานข้อมูลซึ่งจากประสบการณ์ของฉันมักจะมีข้อ จำกัด

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


2

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

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

หวังว่าจะเป็นประโยชน์ถ้าไม่มีอะไรที่คุณจะต้องค้นหาในตอนนี้


2

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


2

คุณสามารถใช้กรอบการเยาะเย้ยเพื่อสรุปเครื่องยนต์ฐานข้อมูล ฉันไม่รู้ว่า PHP / Python มีบางอย่าง แต่สำหรับภาษาที่พิมพ์ (C #, Java เป็นต้น) มีตัวเลือกมากมาย

นอกจากนี้ยังขึ้นอยู่กับวิธีที่คุณออกแบบรหัสการเข้าถึงฐานข้อมูลเหล่านั้นเนื่องจากการออกแบบบางอย่างง่ายต่อการทดสอบหน่วยมากกว่าที่อื่น ๆ เช่นโพสต์ก่อนหน้านี้


2

การตั้งค่าข้อมูลทดสอบสำหรับการทดสอบหน่วยอาจเป็นเรื่องที่ท้าทาย

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

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