สร้าง CLI Python จัดการไฟล์ด้วย Context Manager และ Test ง่ายๆ
สวัสดีครับ
วันนี้ผมจะพาเพื่อนๆ มาลองสร้างเครื่องมือ CLI (Command Line Interface) ง่ายๆ ด้วย Python กันนะครับ ที่จะช่วยจัดการไฟล์ และที่สำคัญ เราจะใช้ context-manager เข้ามาช่วยให้โค้ดของเราปลอดภัยขึ้น แล้วก็มีการเขียน Test ครอบคลุมด้วยครับ
เริ่มต้นด้วย CLI ง่ายๆ
เราจะเริ่มจากการสร้างฟังก์ชันง่ายๆ สำหรับอ่านไฟล์นะครับ
import argparse
def read_file_content(filepath):
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
print(f"Content from {filepath}:\
{content}")
return content
except FileNotFoundError:
print(f"Error: File not found at {filepath}")
return None
except Exception as e:
print(f"An error occurred: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="A simple file reader CLI tool.")
parser.add_argument("filepath", type=str, help="Path to the file to read.")
args = parser.parse_args()
read_file_content(args.filepath)
if __name__ == "__main__":
main()
ตรงนี้ครับ เราใช้ argparse เพื่อให้โปรแกรมของเราสามารถรับค่าจาก command line ได้ง่ายๆ นะครับ และฟังก์ชัน read_file_content ก็จะทำการเปิดไฟล์ อ่าน แล้วก็แสดงผลออกมาให้เราเห็น
เพิ่ม Logging เข้าไป
เพื่อให้โปรแกรมของเราดูโปรขึ้น แล้วก็มีข้อมูลเวลาทำงาน เรามาเพิ่ม logging เข้าไปหน่อยนะครับ จะได้รู้ว่าเกิดอะไรขึ้นบ้าง
import argparse
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def read_file_content(filepath):
try:
logging.info(f"Attempting to read file: {filepath}")
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
logging.info(f"Successfully read file: {filepath}")
# print(f"Content from {filepath}:\
{content}") # คอมเมนต์ออกไป ไม่ให้ interfere กับ test
return content
except FileNotFoundError:
logging.error(f"Error: File not found at {filepath}")
# print(f"Error: File not found at {filepath}")
return None
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
# print(f"An error occurred: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="A simple file reader CLI tool.")
parser.add_argument("filepath", type=str, help="Path to the file to read.")
args = parser.parse_args()
read_file_content(args.filepath)
if __name__ == "__main__":
main()
แล้วก็ตรงนี้นะครับ เราเพิ่ม logging.basicConfig เข้ามาเพื่อให้ Logger ของเราทำงานได้ แล้วก็ใช้ logging.info กับ logging.error ในจุดที่เหมาะสมครับ
ลองสร้าง Context Manager เอง (Optional)
จริงๆ แล้ว open() ใน Python ก็เป็น context-manager อยู่แล้วนะครับ แต่ถ้าเพื่อนๆ อยากลองสร้างเอง เพื่อควบคุมทรัพยากรอื่นๆ ที่ไม่เกี่ยวกับไฟล์ ผมจะแสดงตัวอย่างง่ายๆ ให้ดูครับ
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class ConnectionManager:
def __init__(self, resource_name):
self.resource_name = resource_name
def __enter__(self):
logging.info(f"Connecting to {self.resource_name}...")
# สมมติว่านี่คือการเชื่อมต่อกับฐานข้อมูลหรือเครือข่าย
return self.resource_name # ส่งคืนตัว resource กลับไปใช้งาน
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
logging.error(f"Error with {self.resource_name}: {exc_val}")
logging.info(f"Disconnecting from {self.resource_name}...")
# ทำความสะอาด หรือปิดการเชื่อมต่อที่นี่
return False # ถ้าเป็น True จะเป็นการ suppress error
# การใช้งาน
if __name__ == "__main__":
with ConnectionManager("MyDatabase") as db_conn:
logging.info(f"Using connection: {db_conn}")
# จำลองการเกิดข้อผิดพลาด
# raise ValueError("Something went wrong during DB operation")
with ConnectionManager("MyAPI") as api_conn:
logging.info(f"Working with {api_conn}")
ตรงนี้ครับ จะเห็นว่า context-manager ช่วยให้เรามั่นใจได้ว่า __exit__ จะถูกเรียกเสมอ ไม่ว่าจะเกิด Error หรือไม่ก็ตามนะครับ ทำให้การจัดการทรัพยากรของเราเป็นระเบียบและปลอดภัยครับ
การเขียน Test สำหรับ CLI ของเรา
แล้วก็ อย่าลืมเขียน Test ด้วยนะครับ เพื่อให้มั่นใจว่าโปรแกรมของเราทำงานได้ถูกต้องเสมอ เราสามารถใช้ unittest ใน Python ได้เลยครับ เราจะจำลองการเรียก CLI แล้วก็ตรวจสอบผลลัพธ์นะครับ
สร้างไฟล์ test_cli_tool.py ขึ้นมาแบบนี้นะครับ:
import unittest
import os
from unittest.mock import patch, MagicMock
from io import StringIO
# เพื่อความง่ายในการทดสอบ ผมจะเอา read_file_content และ main มาไว้ในนี้เลยนะครับ
import argparse
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def read_file_content(filepath):
try:
logging.info(f"Attempting to read file: {filepath}")
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
logging.info(f"Successfully read file: {filepath}")
return content
except FileNotFoundError:
logging.error(f"Error: File not found at {filepath}")
return None
except Exception as e:
logging.error(f"An unexpected error occurred: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="A simple file reader CLI tool.")
parser.add_argument("filepath", type=str, help="Path to the file to read.")
args = parser.parse_args()
read_file_content(args.filepath)
class TestCLITool(unittest.TestCase):
def setUp(self):
# สร้างไฟล์ทดสอบชั่วคราว
self.test_filename = "temp_test_file.txt"
with open(self.test_filename, "w") as f:
f.write("Hello, World!\
This is a test.")
def tearDown(self):
# ลบไฟล์ทดสอบเมื่อจบการทดสอบ
if os.path.exists(self.test_filename):
os.remove(self.test_filename)
def test_read_existing_file(self):
content = read_file_content(self.test_filename)
self.assertIsNotNone(content)
self.assertIn("Hello, World!", content)
def test_read_non_existent_file(self):
content = read_file_content("non_existent_file.txt")
self.assertIsNone(content)
@patch('sys.argv', ['cli_tool.py', 'non_existent_file.txt'])
@patch('sys.stdout', new_callable=StringIO)
def test_main_with_non_existent_file_output(self, mock_stdout):
# การรัน main() จะเรียก argparse ซึ่งจะ print error ไปที่ stdout
# เราต้องจับ stdout เพื่อตรวจสอบข้อความ
main() # main() จะเรียก read_file_content ซึ่งจะ print error ด้วย
self.assertIn("Error: File not found", mock_stdout.getvalue())
เพื่อรัน Test นะครับ ให้เปิด Terminal แล้วไปที่ Folder ที่มีไฟล์ test_cli_tool.py แล้วพิมพ์ python -m unittest test_cli_tool.py แบบนี้นะครับ
แบบนี้นะครับ เพื่อนๆ ก็สามารถสร้างเครื่องมือ CLI ของตัวเองได้ง่ายๆ ด้วย Python แล้วก็ยังมั่นใจได้ด้วยว่าโค้ดของเราทำงานได้ถูกต้อง มีการจัดการทรัพยากรดี และมีการบันทึก Log เวลาทำงานด้วยครับ ลองเอาไปปรับใช้ดูได้เลยครับ
cii3.net