Python redirect_stdout: จับ output ของโค้ดเก่าไว้ทดสอบ
บาง library เก่ายัง print() ผลลัพธ์แทนการคืนค่า ถ้าจะทดสอบหรือฝังใน notebook การแก้ทุก call site อาจเสี่ยงเกินไป contextlib.redirect_stdout ช่วยเบน stdout ชั่วคราวเข้า file-like object เหมือนต่อท่อจากลำโพงเข้าเครื่องบันทึกครับ
import io
import unittest
from contextlib import redirect_stdout
def legacy_report(items):
print(f"count={len(items)}")
for item in items:
print(f"- {item}")
def capture_report(items):
stream = io.StringIO()
with redirect_stdout(stream):
legacy_report(items)
return stream.getvalue()
class RedirectStdoutTests(unittest.TestCase):
def test_captures_complete_report(self):
self.assertEqual(capture_report(["mint", "amber"]), "count=2\n- mint\n- amber\n")
def test_empty_report(self):
self.assertEqual(capture_report([]), "count=0\n")
if __name__ == "__main__":
unittest.main()
ใช้ตรงไหนและไม่ควรใช้ตรงไหน
เหมาะกับ test, help text และ adapter รอบ legacy code แต่ไม่เหมาะเป็นระบบ logging เพราะมันเปลี่ยน sys.stdout แบบ global ระหว่าง context ทำให้ thread อื่นอาจเขียนผิดปลายทาง และจับ output จาก subprocess ที่เขียน file descriptor ตรง ๆ ไม่ได้ สำหรับ production ให้ refactor ฟังก์ชันคืนข้อมูลหรือรับ writer/logger ผ่าน dependency แทน
Recap
- จำกัด redirect ให้อยู่ใน
withที่สั้นที่สุด - ทดสอบ newline และ empty input แบบ exact
- อย่าใช้แทน structured logging
- หลีกเลี่ยงในโค้ดหลาย thread