โค้ดทดสอบหน่วย Python ซึ่งเรียกใช้ฟังก์ชันหลามระดับ OS/โมดูล

ฉันมีโมดูล/สคริปต์ python ซึ่งทำสิ่งเหล่านี้บางส่วน

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

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

  1. ฟังก์ชัน Wrapper มีไว้เพื่อการทดสอบเท่านั้น และทำให้โค้ดเสียหาย
  2. ฉันต้องจำไว้ว่าต้องใช้ฟังก์ชัน wrapper ในโค้ดทุกครั้ง ไม่ใช่แค่โทร os.path.exists หรือ raw_input

มีข้อเสนอแนะที่ยอดเยี่ยมบ้างไหม?


person Kannan Ekanath    schedule 19.02.2013    source แหล่งที่มา
comment
ฉันเพิ่มแท็กเพิ่มเติมเพื่อให้คำถามนี้ได้รับความสนใจมากขึ้น   -  person User    schedule 19.02.2013


คำตอบ (3)


คำตอบสั้นๆ คือ monkey patch การเรียกของระบบเหล่านี้

มีตัวอย่างที่ดีในคำตอบของ วิธีแสดง stdin ที่ถูกเปลี่ยนเส้นทางใน หลาม?

นี่คือตัวอย่างง่ายๆ สำหรับ raw_input() โดยใช้ lambda ที่จะทิ้งพรอมต์และส่งคืนสิ่งที่เราต้องการ

ระบบภายใต้การทดสอบ

$ cat ./name_getter.py
#!/usr/bin/env python

class NameGetter(object):

    def get_name(self):
        self.name = raw_input('What is your name? ')

    def greet(self):
        print 'Hello, ', self.name, '!'

    def run(self):
        self.get_name()
        self.greet()

if __name__ == '__main__':
    ng = NameGetter()
    ng.run()

$ echo Derek | ./name_getter.py 
What is your name? Hello,  Derek !

กรณีทดสอบ:

$ cat ./t_name_getter.py
#!/usr/bin/env python

import unittest
import name_getter

class TestNameGetter(unittest.TestCase):

    def test_get_alice(self):
        name_getter.raw_input = lambda _: 'Alice'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Alice')

    def test_get_bob(self):
        name_getter.raw_input = lambda _: 'Bob'
        ng = name_getter.NameGetter()
        ng.get_name()
        self.assertEquals(ng.name, 'Bob')

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

$ ./t_name_getter.py -v
test_get_alice (__main__.TestNameGetter) ... ok
test_get_bob (__main__.TestNameGetter) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
person Johnsyweb    schedule 19.02.2013

โซลูชันที่ 1: ฉันจะทำสิ่งนี้เพราะมันใช้งานได้:

def setUp(self):
    self._os_path_exists = os.path.exists
    os.path.exists = self.myTestExists # mock

def tearDown(self):
    os.path.exists = self._os_path_exists

มันไม่สวยเลย

โซลูชันที่ 2: การปรับโครงสร้างโค้ดของคุณใหม่ไม่ใช่ตัวเลือกอย่างที่คุณพูดใช่ไหม มันจะทำให้การเข้าใจและไร้สัญชาตญาณแย่ลง

person User    schedule 19.02.2013

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

ตัวอย่างเช่น หากต้องการรันการทดสอบหน่วยที่แทนที่ os.path.isfile และส่งคืน True เสมอ:

try:
    from unittest.mock import patch
except ImportError:
    from mock import patch

class SomeTest(TestCase):

    def test_blah():
        with patch("os.path.isfile", lambda x: True):
            self.assertTrue(some_function("input"))

วิธีนี้ช่วยให้คุณประหยัดโค้ดสำเร็จรูปได้มาก และยังสามารถอ่านได้ง่ายอีกด้วย

หากคุณต้องการบางสิ่งที่ซับซ้อนกว่านี้อีกเล็กน้อย เช่น การแทนที่ supbroccess.check_output คุณสามารถสร้างฟังก์ชันตัวช่วยง่ายๆ ได้:

def _my_monkeypatch_function(li):
     x,y = li[0], li[1]
     if x == "Reavers":
        return "Gorram"
     if x == "Inora":
        return "Shiny!"
     if x == y:
        return "The Ballad of Jayne"

def test_monkey():
     with patch("subprocess.check_output", _my_monkeypatch_function):
            assertEquals(subprocess.check_output(["Mudder","Mudder"]),
                                                 "The Ballad of Jayne")
person Community    schedule 30.12.2013