Python หน่วยทดสอบกับฐานและคลาสย่อย


149

ขณะนี้ฉันมีการทดสอบสองสามบทซึ่งใช้ชุดการทดสอบทั่วไป นี่คือตัวอย่าง:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

ผลลัพธ์ของข้างต้นคือ:

Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

มีวิธีการเขียนข้างต้นเพื่อที่testCommonจะไม่เรียกแรกหรือไม่

แก้ไข: แทนที่จะทำการทดสอบ 5 ครั้งด้านบนฉันต้องการให้ทดสอบเพียง 4 การทดสอบ 2 จาก SubTest1 และอีก 2 จาก SubTest2 ดูเหมือนว่า Python unittest ใช้ BaseTest ดั้งเดิมด้วยตัวเองและฉันต้องการกลไกเพื่อป้องกันไม่ให้เกิดขึ้น


ฉันเห็นว่าไม่มีใครพูดถึงมัน แต่คุณมีตัวเลือกในการเปลี่ยนส่วนหลักและเรียกใช้ชุดทดสอบที่มีคลาสย่อยทั้งหมดของ BaseTest หรือไม่
kon Psych

คำตอบ:


154

ใช้การสืบทอดหลายคลาสดังนั้นคลาสของคุณที่มีการทดสอบทั่วไปจะไม่ได้รับมรดกจาก TestCase

import unittest

class CommonTests(object):
    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(unittest.TestCase, CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(unittest.TestCase, CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

1
นั่นคือทางออกที่งดงามที่สุด
ธีรี่ร์แลม

27
วิธีนี้ใช้ได้กับเมธอด setUp และ tearDown เท่านั้นหากคุณกลับลำดับของคลาสพื้นฐาน เนื่องจากเมธอดถูกกำหนดใน unittestTestCase และพวกเขาไม่เรียก super () ดังนั้นเมธอด setUp และ tearDown ใด ๆ ใน CommonTests จะต้องเป็นอันดับแรกใน MRO มิเช่นนั้นจะไม่ถูกเรียกเลย
Ian Clelland

32
เพียงเพื่อชี้แจงคำพูดของ Ian Clelland เพื่อให้ชัดเจนสำหรับคนอย่างฉัน: ถ้าคุณเพิ่มsetUpและtearDownวิธีการในการCommonTestsเรียนและคุณต้องการให้พวกเขาถูกเรียกสำหรับการทดสอบแต่ละครั้งในคลาสที่ได้รับคุณต้องกลับคำสั่งของคลาสพื้นฐาน ดังนั้นมันจะเป็น: class SubTest1(CommonTests, unittest.TestCase).
Dennis Golomazov

6
ฉันไม่ใช่แฟนของแนวทางนี้ นี้กำหนดสัญญาในรหัสที่เรียนจะต้องได้รับมรดกจากทั้งสองและunittest.TestCase CommonTestsฉันคิดว่าsetUpClassวิธีการด้านล่างนี้ดีที่สุดและมีแนวโน้มที่จะเกิดข้อผิดพลาดน้อยลง ไม่ว่าจะเป็นหรือห่อคลาส BaseTest ในคลาสคอนเทนเนอร์ซึ่งเป็นแฮ็คที่มากขึ้น แต่หลีกเลี่ยงข้อความข้ามในการทดสอบการพิมพ์
David Sanders

10
ปัญหาของอันนี้ก็คือ pylint มีความเหมาะสมเพราะCommonTestsมันเป็นวิธีการเรียกใช้ที่ไม่มีอยู่ในคลาสนั้น
MadScientist

146

อย่าใช้มรดกหลายก็จะกัดคุณในภายหลัง

แต่คุณสามารถย้ายคลาสพื้นฐานของคุณไปยังโมดูลแยกหรือห่อด้วยคลาสเปล่า:

class BaseTestCases:

    class BaseTest(unittest.TestCase):

        def testCommon(self):
            print('Calling BaseTest:testCommon')
            value = 5
            self.assertEqual(value, 5)


class SubTest1(BaseTestCases.BaseTest):

    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

ผลลัพธ์:

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

6
นี่คือสิ่งที่ฉันชอบ มันเป็นวิธีที่แฮ็คน้อยที่สุดและไม่ยุ่งเกี่ยวกับวิธีการเอาชนะไม่เปลี่ยนแปลง MRO และอนุญาตให้ฉันกำหนด setUp, setUpClass และอื่น ๆ ในชั้นฐาน
Hannes

6
ฉันไม่เข้าใจ (เวทมนตร์มาจากไหน) แต่มันก็เป็นทางออกที่ดีที่สุดสำหรับฉัน :) มาจาก Java ฉันเกลียดการรับมรดกหลายรายการ ...
Edouard Berthe

4
@Edouardb unittest รันเฉพาะคลาสระดับโมดูลที่สืบทอดจาก TestCase แต่ BaseTest ไม่ใช่ระดับโมดูล
JoshB

ในฐานะที่เป็นทางเลือกที่คล้ายกันมากคุณสามารถกำหนด ABC ภายในฟังก์ชั่นไม่ต้องรอที่ส่งคืน ABC เมื่อเรียกว่า
Anakhand

34

คุณสามารถแก้ปัญหานี้ด้วยคำสั่งเดียว:

del(BaseTest)

ดังนั้นรหัสจะเป็นดังนี้:

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

del(BaseTest)

if __name__ == '__main__':
    unittest.main()

3
BaseTest เป็นสมาชิกของโมดูลในขณะที่กำลังถูกกำหนดดังนั้นจึงสามารถใช้เป็นคลาสพื้นฐานของการทดสอบย่อยได้ ก่อนที่คำจำกัดความจะเสร็จสมบูรณ์ del () จะลบออกในฐานะสมาชิกดังนั้นเฟรมเวิร์กที่ไม่สำคัญที่สุดจะไม่พบเมื่อทำการค้นหาคลาสย่อยของ TestCase ในโมดูล
mhsmith

3
นี่คือคำตอบที่ยอดเยี่ยม! ฉันชอบมากกว่า @MatthewMarshall เพราะในโซลูชันของเขาคุณจะได้รับข้อผิดพลาดทางไวยากรณ์จาก pylint เนื่องจากself.assert*วิธีการไม่มีอยู่ในวัตถุมาตรฐาน
SimplyKnownAsG

1
ไม่ทำงานหากมีการอ้างอิง BaseTest ที่อื่นในคลาสฐานหรือคลาสย่อยเช่นเมื่อเรียก super () ในการแทนที่เมธอด: super( BaseTest, cls ).setUpClass( )
Hannes

1
@Hannes อย่างน้อยในหลาม 3 BaseTestสามารถอ้างอิงผ่านsuper(self.__class__, self)หรือเพียงแค่super()ใน subclasses แม้เห็นได้ชัดว่าไม่ว่าคุณจะมีการก่อสร้างเป็นมรดก อาจมีทางเลือก "ไม่ระบุชื่อ" เช่นนี้เมื่อคลาสพื้นฐานจำเป็นต้องอ้างอิงตัวเอง (ไม่ใช่ว่าฉันมีความคิดใด ๆ เมื่อคลาสจำเป็นต้องอ้างอิงตัวเอง)
สไตน์

29

คำตอบของ Matthew Marshall นั้นยอดเยี่ยม แต่คุณต้องสืบทอดจากสองคลาสในแต่ละกรณีทดสอบซึ่งอาจเกิดข้อผิดพลาดได้ง่าย ฉันใช้สิ่งนี้แทน (python> = 2.7):

class BaseTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        if cls is BaseTest:
            raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
        super(BaseTest, cls).setUpClass()

3
นั่นมันเรียบร้อย มีวิธีที่จะหลีกเลี่ยงการใช้ข้ามหรือไม่ สำหรับฉันแล้วการข้ามจะไม่สามารถทำได้และใช้เพื่อระบุปัญหาในแผนการทดสอบปัจจุบัน (ด้วยรหัสหรือการทดสอบ)?
Zach Young

@ZacharyYoung ฉันไม่รู้บางทีคำตอบอื่น ๆ อาจช่วยได้
Dennis Golomazov

@ ZacharyYoung ฉันพยายามแก้ไขปัญหานี้แล้วดูคำตอบของฉัน
simonzack

ยังไม่ชัดเจนในทันทีสิ่งที่เกิดข้อผิดพลาดได้ง่ายเกี่ยวกับการสืบทอดจากสองคลาส
jwg

@jwg เห็นความคิดเห็นต่อคำตอบที่ยอมรับ :) คุณต้องรับคลาสการทดสอบแต่ละคลาสของคุณจากคลาสพื้นฐานสองคลาส คุณต้องรักษาลำดับที่ถูกต้องของพวกเขา หากคุณต้องการเพิ่มคลาสทดสอบพื้นฐานอีกคลาสหนึ่งคุณจะต้องสืบทอดจากคลาสนั้นด้วย ไม่มีอะไรผิดปกติกับ mixins แต่ในกรณีนี้พวกเขาสามารถถูกแทนที่ด้วยข้ามง่าย
Dennis Golomazov

7

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

import unittest

class CommonTests(unittest.TestCase):
      def common_assertion(self, foo, bar, baz):
          # whatever common code
          self.assertEqual(foo(bar), baz)

class BaseTest(CommonTests):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)

class SubTest2(CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

1
ภายใต้ข้อเสนอแนะของคุณ common_assertion () จะยังคงทำงานโดยอัตโนมัติเมื่อทดสอบคลาสย่อยหรือไม่
Stewart

@Startart ไม่มันจะไม่ การตั้งค่าเริ่มต้นคือใช้วิธีการที่ขึ้นต้นด้วย "test" เท่านั้น
CS

6

คำตอบของ Matthew คือคำตอบที่ฉันต้องการใช้ตั้งแต่ฉันยังอยู่ที่ 2.5 แต่เป็น 2.7 คุณสามารถใช้ตัวตกแต่ง @ unittest.skip () ในวิธีทดสอบใด ๆ ที่คุณต้องการข้าม

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

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

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func

6

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

เมื่อเทียบกับวิธีมิกซ์อินนั้น PyCharm ของ IDE จะไม่บ่นว่าวิธีทดสอบหน่วยจะหายไปจากคลาสฐาน

ถ้าคลาสพื้นฐานสืบทอดมาจากคลาสนี้คลาสนั้นจะต้องแทนที่setUpClassและtearDownClassวิธีการ

class BaseTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._test_methods = []
        if cls is BaseTest:
            for name in dir(cls):
                if name.startswith('test') and callable(getattr(cls, name)):
                    cls._test_methods.append((name, getattr(cls, name)))
                    setattr(cls, name, lambda self: None)

    @classmethod
    def tearDownClass(cls):
        if cls is BaseTest:
            for name, method in cls._test_methods:
                setattr(cls, name, method)
            cls._test_methods = []

5

คุณสามารถเพิ่ม__test_ = Falseในคลาส BaseTest แต่ถ้าคุณเพิ่มโปรดระวังว่าคุณต้องเพิ่ม__test__ = Trueในคลาสที่ได้รับเพื่อให้สามารถเรียกใช้การทดสอบ

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):
    __test__ = True

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    __test__ = True

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

วิธีนี้ไม่สามารถใช้กับการค้นพบ / ทดสอบการทดสอบของตัวเองได้ (ฉันเชื่อว่าต้องใช้นักวิ่งทดสอบสำรองเช่นจมูก)
medmunds

4

ตัวเลือกอื่นไม่ได้ทำงาน

unittest.main()

แทนที่จะเป็นเช่นนั้นคุณสามารถใช้

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)

ดังนั้นคุณจะทำการทดสอบในชั้นเรียนเท่านั้น TestClass


นี่เป็นวิธีแก้ปัญหาแฮ็กน้อยที่สุด แทนที่จะแก้ไขสิ่งที่unittest.main()รวบรวมไว้ในชุดเริ่มต้นคุณจะสร้างชุดโปรแกรมที่ชัดเจนและเรียกใช้การทดสอบ
zgoda

1

ฉันทำเหมือนกันกับ @Vladim P. ( https://stackoverflow.com/a/25695512/2451329 ) แต่ได้ปรับเปลี่ยนเล็กน้อย:

import unittest2


from some_module import func1, func2


def make_base_class(func):

    class Base(unittest2.TestCase):

        def test_common1(self):
            print("in test_common1")
            self.assertTrue(func())

        def test_common2(self):
            print("in test_common1")
            self.assertFalse(func(42))

    return Base



class A(make_base_class(func1)):
    pass


class B(make_base_class(func2)):

    def test_func2_with_no_arg_return_bar(self):
        self.assertEqual("bar", func2())

และเราไปที่นั่น


1

ใน Python 3.2 คุณสามารถเพิ่มฟังก์ชั่นtest_loaderเข้ากับโมดูลเพื่อควบคุมการทดสอบ (ถ้ามี) ที่กลไกการค้นพบพบได้

ตัวอย่างเช่นต่อไปนี้จะโหลดของผู้โพสต์ดั้งเดิมSubTest1และSubTest2กรณีทดสอบเท่านั้นโดยไม่สนใจBase:

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

มันควรจะเป็นไปได้ที่จะย้ำกว่าstandard_tests(กTestSuiteที่มีการทดสอบรถตักดินเริ่มต้นที่พบ) และคัดลอกทั้งหมด แต่Baseจะsuiteแทน แต่ธรรมชาติซ้อนกันของTestSuite.__iter__รถที่มากความซับซ้อนมากขึ้น


0

เพียงเปลี่ยนชื่อเมธอด testCommon เป็นอย่างอื่น Unittest (ปกติ) ข้ามสิ่งที่ไม่มี 'ทดสอบ' ในนั้น

ง่ายและรวดเร็ว

  import unittest

  class BaseTest(unittest.TestCase):

   def methodCommon(self):
       print 'Calling BaseTest:testCommon'
       value = 5
       self.assertEquals(value, 5)

  class SubTest1(BaseTest):

      def testSub1(self):
          print 'Calling SubTest1:testSub1'
          sub = 3
          self.assertEquals(sub, 3)


  class SubTest2(BaseTest):

      def testSub2(self):
          print 'Calling SubTest2:testSub2'
          sub = 4
          self.assertEquals(sub, 4)

  if __name__ == '__main__':
      unittest.main()`

2
นี่จะมีผลลัพธ์ของการไม่รันการทดสอบ methodCommon ใน SubTests อย่างใดอย่างหนึ่ง
Pepper Lebeck-Jobe

0

ดังนั้นนี่คือเธรดเก่า แต่ฉันเจอปัญหานี้วันนี้และคิดถึงแฮ็คของฉันเอง มันใช้มัณฑนากรที่ทำให้ค่าของฟังก์ชั่นไม่มีเมื่อ acessed ผ่านชั้นฐาน ไม่จำเป็นต้องกังวลเกี่ยวกับการตั้งค่าและ setupclass เพราะถ้า baseclass ไม่มีการทดสอบพวกเขาจะไม่ทำงาน

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

0

นี่คือวิธีการแก้ปัญหาที่ใช้เฉพาะคุณสมบัติ unittest ที่หลีกเลี่ยงการมีสถานะ "ข้าม" ในผลการทดสอบของคุณ:

class BaseTest(unittest.TestCase):

    def __init__(self, methodName='runTest'):
        if self.__class__ is BaseTest:
            # don't run these tests in the abstract base implementation
            methodName = 'runNoTestsInBaseClass'
        super().__init__(methodName)

    def runNoTestsInBaseClass(self):
        pass

    def testCommon(self):
        # everything else as in the original question

มันทำงานอย่างไร: ตามunittest.TestCaseเอกสารประกอบ "แต่ละกรณีของ TestCase จะเรียกใช้วิธีการฐานเดียว: วิธีที่ชื่อว่า methodName" "runTests" เริ่มต้นใช้งานวิธีการทดสอบ * ทั้งหมดในชั้นเรียนซึ่งเป็นวิธีที่อินสแตนซ์ของ TestCase ใช้งานได้ตามปกติ แต่เมื่อทำงานในคลาสพื้นฐานที่เป็นนามธรรมคุณสามารถลบล้างพฤติกรรมนั้นได้ด้วยวิธีการที่ไม่ทำอะไรเลย

ผลข้างเคียงคือการนับการทดสอบของคุณจะเพิ่มขึ้นทีละหนึ่ง: runNoTestsInBaseClass "การทดสอบ" จะถูกนับเป็นการทดสอบที่ประสบความสำเร็จเมื่อมันทำงานบน BaseClass

(สิ่งนี้ยังใช้งานได้ใน Python 2.7 หากคุณยังใช้งานอยู่ให้เปลี่ยนsuper()เป็นsuper(BaseTest, self))


-2

เปลี่ยนชื่อเมธอด BaseTest เป็น setUp:

class BaseTest(unittest.TestCase):
    def setUp(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

เอาท์พุท:

ทดสอบ 2 ครั้งใน 0.000 วินาที

การเรียก BaseTest: testCommon การเรียก
SubTest1: testSub1 การเรียก
BaseTest: testCommon การเรียก
SubTest2: testSub2

จากเอกสาร :

TestCase.setUp ()
วิธีการที่เรียกว่าเพื่อเตรียมการติดตั้งการทดสอบ สิ่งนี้ถูกเรียกทันทีก่อนเรียกวิธีการทดสอบ ข้อยกเว้นใด ๆ ที่เกิดขึ้นจากวิธีนี้จะถือเป็นข้อผิดพลาดมากกว่าความล้มเหลวในการทดสอบ การใช้งานเริ่มต้นไม่ทำอะไรเลย


นั่นจะใช้ได้ถ้าฉันมี n testCommon ฉันควรวางทั้งหมดไว้ข้างใต้setUpหรือไม่
ธีรี่ร์แลม

1
ใช่คุณควรใส่รหัสทั้งหมดที่ไม่ได้เป็นกรณีทดสอบจริงภายใต้การตั้งค่า
Brian R. Bondy

แต่ถ้าคลาสย่อยมีมากกว่าหนึ่งtest...วิธีsetUpจะถูกเรียกใช้ซ้ำแล้วซ้ำอีกครั้งหนึ่งต่อวิธีดังกล่าว ดังนั้นจึงไม่ใช่ความคิดที่ดีที่จะทำการทดสอบที่นั่น!
Alex Martelli

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