自动化 tkinter 文件对话以进行功能测试

Automate tkinter file dialogues for functional testing

我有一个 Python tkinter 脚本,我想对其进行 运行 一些功能测试。该脚本包括 tkinter.filedialog.askopenfilename()tkinter.filedialog.asksaveasfilename(),所以我想要测试的一部分 uploads/downloads 一个文件。我尝试使用 pyautogui 来尝试自动执行鼠标点击并发送按键以尝试自动执行这些操作,但这样做没有用(屏幕上没有任何可见的变化,也没有加载文件)。

使用 pyautogui 的功能测试尝试

class TestOrganizeAttendance(unittest.TestCase):
    def setUp(self):
        self.organizer = AttendanceOrganizer()
    ...

    def test_attendance_organizer_window_operation(self):
        ...
        #User clicks button and their computer's files appear
        self.organizer.upload_file_button.invoke()
        self.assertIn(
            "explorer.exe", [p.name() for p in psutil.process_iter()])

        #User selects a file to be uploaded
        filepath = os.path.abspath(
            os.path.join('.', 'tests', 'sample_attendance.csv'))
        pyautogui.PAUSE = 2.5
        pyautogui.hotkey('alt', 'd')
        pyautogui.typewrite(filepath)
        pyautogui.hotkey('enter')
        ....

脚本

class AttendanceOrganizer:
    def __init__(self):
        self.upload_file_button = tkinter.Button(
            self.root, text="Upload File", command=self.upload_file)
        self.download_file_button = tkinter.Button(
            self.root, text="Download File", command=self.download_file)
        ...

    def upload_file(self):
        self.details_var.set(
            value="Select a file to upload")
        filetypes = [('Comma Separated Values', '.csv')]
        filepath = tkinter.filedialog.askopenfilename(
            parent=self.root, filetypes=filetypes)
        if not (filepath and os.path.splitext(filepath)[-1] == '.csv'):
            return
        self.upload_var.set(value=filepath)
        self.details_var.set(
            value=f"File Uploaded\t{self.details_var.get()}")
        with open(filepath, encoding='utf-16') as file:
            self.values = list(csv.reader(file, delimiter='\t'))
            del self.values[0]
        self.organize_data_button.config(state='normal')

    def download_file(self):
        filetypes = [('Comma Separated Values', '.csv')]
        filepath = tkinter.filedialog.asksaveasfilename(
            parent=self.root, filetypes=filetypes)
        with open(f"{filepath}.csv", 'w', newline='') as file:
            fieldnames = ["Last", "First", "Joined", "Left"]
            writer = csv.DictWriter(
                file, fieldnames=fieldnames, delimiter='\t')
            writer.writeheader()
            for item in self.data:
                writer.writerow(self.data[item])

    ...

上述代码在functional_tests.py中的问题在于tkinter.filedialog.askopenfilenametkinter.filedialog.asksaveaskfilename都是阻塞函数,即函数必须在进一步代码执行之前完成。避免此问题的最简单方法是使用 threading 创建一个线程,该线程执行与主线程分开的所有 pyautogui 函数。

functional_tests.py 与 pyautogui

class TestOrganizeAttendance(unittest.TestCase):

    def setUp(self):
        ...

    def tearDown(self):
        ...

    def automate_file_dialogue(self, filepath):
        while "explorer.exe" not in [p.name() for p in psutil.process_iter()]:    #Wait for the file dialogue to open
            continue
        directory, filename = os.path.split(filepath)
        pyautogui.hotkey('alt', 'd')    #Keyboard shortcut to focus the address bar
        pyautogui.typewrite(directory)
        pyautogui.hotkey('enter')
        pyautogui.hotkey('tab')
        pyautogui.hotkey('tab')
        pyautogui.hotkey('tab')    #Repeatedly press TAB to remove focus from the address bar
        pyautogui.hotkey('alt', 'n')    #Keyboard shortcut to focus the file name
        pyautogui.hotkey(filename)
        pyautogui.hotkey('enter')

    def test_attendance_organizer_window_operation(self):
        ...
        filepath = os.path.abspath(
            os.path.join('tests', 'sample_attendance.csv'))
        upload_thread = threading.Thread(
            target=self.automate_file_dialogue, args=(filepath,))
        upload_thread.start()
        upload_thread.start()
        self.assertTrue(os.path.exists(filepath))
        self.organizer.upload_file_button.invoke()
        self.assertIn(
            "explorer.exe", [p.name() for p in psutil.process_iter()])
        upload_thread.join()
        ...
        filepath = os.path.abspath(
            os.path.join('tests', 'new_sample_attendance'))
        download_thread = threading.Thread(
            target=self.automate_file_dialogue, args=(filepath,))
        download_thread.start()
        self.assertFalse(os.path.exists(filepath))
        self.organizer.download_file_button.invoke()
        self.assertIn(
            "explorer.exe", [p.name() for p in psutil.process_iter()])
        download_thread.join()