【问题标题】:Test Methods Using pytest使用 pytest 的测试方法
【发布时间】:2022-12-18 11:55:06
【问题描述】:

我是新来的,正在尝试学习 python。我正在做一个语音识别项目,我需要为我以前使用过 pytest 的代码编写一些测试函数,其中包含返回某些内容的常规函数​​,但在这种情况下,我真的很困惑如何测试这些方法,我已经知道如何将它们导入测试文件,但我不知道如何测试它。我将非常感谢您能为我提供的任何类型的指导。谢谢!

`

#Imported modules
import sys
import wave, contextlib, math, time
import speech_recognition as sr
from moviepy.editor import AudioFileClip
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal

class Ui_MainWindow(object):
    """Main window GUI."""
    def __init__(self):
        """Initialisation function."""
        self.mp4_file_name = ""
        self.output_file = ""
        self.audio_file = "speech.wav"
    def setupUi(self, MainWindow):
        """Define visual components and positions."""
        # Main window
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(653, 836)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(50, 20, 161, 41))
        # Selected video file label
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.selected_video_label = QtWidgets.QLabel(self.centralwidget)
        self.selected_video_label.setGeometry(QtCore.QRect(230, 20, 371, 41))
        font = QtGui.QFont()
        font.setPointSize(8)
        self.selected_video_label.setFont(font)
        self.selected_video_label.setFrameShape(QtWidgets.QFrame.Box)
        self.selected_video_label.setText("")
        self.selected_video_label.setObjectName("selected_video_label")
        self.label_3 = QtWidgets.QLabel(self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(50, 90, 161, 41))
        # Transcribed text box
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.transcribed_text = QtWidgets.QTextBrowser(self.centralwidget)
        self.transcribed_text.setGeometry(QtCore.QRect(230, 320, 381, 431))
        self.transcribed_text.setObjectName("transcribed_text")
        self.label_5 = QtWidgets.QLabel(self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(230, 280, 161, 41))
        font = QtGui.QFont()
        font.setPointSize(14)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        self.transcribe_button = QtWidgets.QPushButton(self.centralwidget)
        self.transcribe_button.setEnabled(False)
        self.transcribe_button.setGeometry(QtCore.QRect(230, 150, 221, 81))
        # Transcribe button
        font = QtGui.QFont()
        font.setPointSize(14)
        self.transcribe_button.setFont(font)
        self.transcribe_button.setObjectName("transcribe_button")
        self.transcribe_button.clicked.connect(self.process_and_transcribe_audio)
        # progeress bar
        self.progress_bar = QtWidgets.QProgressBar(self.centralwidget)
        self.progress_bar.setGeometry(QtCore.QRect(230, 250, 381, 23))
        self.progress_bar.setProperty("value", 0)
        self.progress_bar.setObjectName("progress_bar")
        self.message_label = QtWidgets.QLabel(self.centralwidget)
        self.message_label.setGeometry(QtCore.QRect(0, 760, 651, 21))
        # Message label (for errors and warnings)
        font = QtGui.QFont()
        font.setPointSize(8)
        self.message_label.setFont(font)
        self.message_label.setFrameShape(QtWidgets.QFrame.Box)
        self.message_label.setText("")
        self.message_label.setObjectName("message_label")
        self.output_file_name = QtWidgets.QPlainTextEdit(self.centralwidget)
        self.output_file_name.setGeometry(QtCore.QRect(230, 90, 371, 41))
        # Output file name
        font = QtGui.QFont()
        font.setPointSize(14)
        self.output_file_name.setFont(font)
        self.output_file_name.setObjectName("output_file_name")
        # Menubar options
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 653, 21))
        self.menubar.setObjectName("menubar")
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        self.menuAbout = QtWidgets.QMenu(self.menubar)
        self.menuAbout.setObjectName("menuAbout")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.actionOpen_mp4_video_recording = QtWidgets.QAction(MainWindow)
        self.actionOpen_mp4_video_recording.setObjectName("actionOpen_mp4_video_recording")
        self.actionOpen_mp4_video_recording.triggered.connect(self.open_audio_file)
        self.actionAbout_vid2text = QtWidgets.QAction(MainWindow)
        self.actionAbout_vid2text.setObjectName("actionAbout_vid2text")
        self.actionAbout_vid2text.triggered.connect(self.show_about)
        self.actionNew = QtWidgets.QAction(MainWindow)
        self.actionNew.setObjectName("actionNew")
        self.actionNew.triggered.connect(self.new_project)
        self.menuFile.addAction(self.actionOpen_mp4_video_recording)
        self.menuFile.addAction(self.actionNew)
        self.menuAbout.addAction(self.actionAbout_vid2text)
        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuAbout.menuAction())
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
    def retranslateUi(self, MainWindow):
        """Translate UI method."""
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Selected video file:"))
        self.label_3.setText(_translate("MainWindow", "Output file name:"))
        self.label_5.setText(_translate("MainWindow", "Transcribed text:"))
        self.transcribe_button.setText(_translate("MainWindow", "Transcribe"))
        self.output_file_name.setPlaceholderText(_translate("MainWindow", "interview1.txt"))
        self.menuFile.setTitle(_translate("MainWindow", "File"))
        self.menuAbout.setTitle(_translate("MainWindow", "About"))
        self.actionOpen_mp4_video_recording.setText(_translate("MainWindow", "Open mp4 video recording"))
        self.actionAbout_vid2text.setText(_translate("MainWindow", "About video to speech"))
        self.actionNew.setText(_translate("MainWindow", "New"))
    def open_audio_file(self):
        """Open the audio (*.mp4) file."""
        file_name = QFileDialog.getOpenFileName()
        if file_name[0][-3:] == "mp4":
            self.transcribe_button.setEnabled(True)
            self.mp4_file_name = file_name[0]
            self.message_label.setText("")
            self.selected_video_label.setText(file_name[0])
        else:
            self.message_label.setText("Please select an *.mp4 file")
    def convert_mp4_to_wav(self):
        """Convert the mp4 video file into an audio file."""
        self.message_label.setText("Converting mp4 to audio (*.wav)...")
        self.convert_thread = convertVideoToAudioThread(self.mp4_file_name, self.audio_file)
        self.convert_thread.finished.connect(self.finished_converting)
        self.convert_thread.start()
    def get_audio_duration(self, audio_file):
        """Determine the length of the audio file."""
        with contextlib.closing(wave.open(audio_file,'r')) as f:
            frames = f.getnframes()
            rate = f.getframerate()
            duration = frames / float(rate)
            return duration
    def transcribe_audio(self, audio_file):
        """Transcribe the audio file."""
        total_duration = self.get_audio_duration(audio_file) / 10
        total_duration = math.ceil(total_duration)
        self.td = total_duration
        if len(self.output_file_name.toPlainText()) > 0:
            self.output_file = self.output_file_name.toPlainText()
        else:
            self.output_file = "my_speech_file.txt"
        # Use thread to process in the background and avoid freezing the GUI
        self.thread = transcriptionThread(total_duration, audio_file, self.output_file)
        self.thread.finished.connect(self.finished_transcribing)
        self.thread.change_value.connect(self.set_progress_value)
        self.thread.start()
    def finished_converting(self):
        """Reset message text when conversion is finished."""
        self.message_label.setText("Transcribing file...")
        self.transcribe_audio(self.audio_file)
    def finished_transcribing(self):
        """This run when transcription finished to tidy up UI."""
        self.progress_bar.setValue(100)
        self.transcribe_button.setEnabled(True)
        self.message_label.setText("")
        self.update_text_output()
    def set_progress_value(self, val):
        """Update progress bar value."""
        increment = int(math.floor(100*(float(val)/self.td)))
        self.progress_bar.setValue(increment)
    def process_and_transcribe_audio(self):
        """Process the audio into a textual transcription."""
        self.transcribe_button.setEnabled(False)
        self.message_label.setText("Converting mp4 to audio (*.wav)...")
        self.convert_mp4_to_wav()
    def update_text_output(self):
        """Update the text box with the transcribed file."""
        f = open(self.output_file, "r")
        self.transcribed_text.setText(f.read())
        f.close()
    def new_project(self):
        """Clear existing fields of data."""
        self.message_label.setText("")
        self.transcribed_text.setText("")
        self.selected_video_label.setText("")
        self.output_file_name.document().setPlainText("")
        self.progress_bar.setValue(0)
    def show_about(self):
        """Show about message box."""
        msg = QMessageBox()
        msg.setWindowTitle("About video to Speech")
        msg.setText("Nelson Petro - CSE 111")
        msg.setIcon(QMessageBox.Information)
        msg.exec_()
class convertVideoToAudioThread(QThread):
    """Thread to convert mp4 video file to wav file."""
    def __init__(self, mp4_file_name, audio_file):
        """Initialization function."""
        QThread.__init__(self)
        self.mp4_file_name = mp4_file_name
        self.audio_file = audio_file
    def __del__(self):
        """Destructor."""
        self.wait()
    def run(self):
        """Run video conversion task."""
        audio_clip = AudioFileClip(self.mp4_file_name)
        audio_clip.write_audiofile(self.audio_file)
class transcriptionThread(QThread):
    """Thread to transcribe file from audio to text."""
    change_value = pyqtSignal(int)
    def __init__(self, total_duration, audio_file, output_file):
        """Initialization function."""
        QThread.__init__(self)
        self.total_duration = total_duration
        self.audio_file = audio_file
        self.output_file = output_file
    def __del__(self):
        """Destructor."""
        self.wait()
    def run(self):
        """Run transcription, audio to text."""
        r = sr.Recognizer()
        for i in range(0, self.total_duration):
            try:
                with sr.AudioFile(self.audio_file) as source:
                    audio = r.record(source, offset=i*10, duration=10)
                    f = open(self.output_file, "a")
                    f.write(r.recognize_google(audio))
                    f.write(" ")
                self.change_value.emit(i)
            except:
                print("Unknown word detected...")
                continue
            f.close()
            
def main():
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()

我成功导入了这些方法,但我真的不知道如何测试它们。

【问题讨论】:

  • 实际上,您的代码看起来不太可测试。你想测试什么?单元测试并不总是一个好的解决方案。
  • 你好@Lenormju,谢谢你的回复,我正在上一门名为“用函数编程”的课程,我们被要求找到一个好的项目并编写一些测试函数。我能够为我以前的个人项目编写测试函数,为我的函数返回一些东西,但在这种情况下,我对如何测试这些函数是否正常工作感到困惑。

标签: python oop testing pytest


【解决方案1】:

测试需要上下文,你测试为了某物。
例如 :

  • 如果您的程序只是启动(冒烟测试),
  • 如果错误已修复(回归测试),
  • 如果满足功能要求(验收测试),
  • 如果它满足非功能性需求(性能、可访问性、...测试),
  • ...

单元测试(这是一个重载的术语,有许多不同的定义)对我来说更多的是关于如何编写测试(从其余单元中删除一个单元)而不是它试图检查的内容。
因此,您可以出于多种原因编写单元测试,只是它们只针对代码的一小部分(理想情况下只是一个类或函数/方法)。

例如,您可以测试翻译(伪代码):

def test_that_the_app_is_by_default_in_english():
    ... # start the app
    # `retranslateUi` gets implicitly called
    assert main_window.label_3.text == "Selected video file:"

def test_that_the_app_is_translated_in_spanish_when_the_user_locale_is_es_ES():
    ... # set the locale 
    ... # start the app
    # `retranslateUi` gets implicitly called
    assert main_window.label_3.text == "Archivo de vídeo seleccionado :"

或者,例如,您可以测试该应用程序是否拒绝非 mp4 文件:

def test_that_providing_a_raw_file_is_rejected():
    ... # start the app
    ... # mock `QFileDialog.getOpenFileName` to return "video.raw"
    ... # simulate user triggering the "Open mp4 video recording" action
    # `open_audio_file` gets called, calls the mock, and handles the input
    assert main_window.message_label.text == "Please select an *.mp4 file"

或者您可以测试如何处理损坏的文件。

或者您可以测试当用户单击相应的菜单项时是否会打开“关于窗口”。

或者 ...

有很多可能性。

但是你可能会想到一个模式:每次我们创建一个实例时,调用它的方法,然后检查它的字段(或者可能是它的方法的返回值)。
那是因为在面向对象编程中,self 是一个输入每个方法的输出参数。所以即使一个方法不返回任何东西(None),实际上它返回一个可能修改过的self,我们可以检查一下。 其他时候,self可能没有变化,但已经副作用: print 到终端,文件写入,http 请求,...这些都是系统外的变化,因此很难测试。单元测试嘲笑经常检查它们,要么忽略它们,要么实际检查它们,具体取决于测试目标。

您的问题不是特别适合 StackOverflow,它专门用于明确的技术问题,而您的问题是模糊的开放式问题。这并不是说它本身不是一个好问题,而是 StackOverflow 不是它应该发布的地方。但是因为我不知道还有什么地方,所以我还是部分回答了。

希望您了解为什么您的问题难以回答。


编辑

根据您的评论,我将提供此类测试的示例。

但首先,拥有一个 Minimal Reproducible Example 会比你的实际代码更好,它很长并且需要时间来理解,需要安装许多依赖项,......

这是一些更容易玩的东西:

import sys
import datetime
from PyQt5 import QtWidgets


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__(parent=None)  # because MainWindow

        self.setWindowTitle("Clock on demand")

        central_widget = QtWidgets.QWidget(self)
        self.setCentralWidget(central_widget)

        self.label = QtWidgets.QLabel("", central_widget)
        self.button = QtWidgets.QPushButton("What time is it ?", central_widget)
        self.button.clicked.connect(self.update_time)

        layout = QtWidgets.QVBoxLayout(central_widget)
        layout.addWidget(self.button)
        layout.addWidget(self.label)

        self.update_time()

    def update_time(self):
        self.label.setText(datetime.datetime.now().time().isoformat())


def main():
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

这是一个非常简单的应用程序:一个小窗口,两个相关的小部件(一个按钮和一个标签),它只做一件事。

现在假设我们要测试点击按钮更新时间。

def test__when_user_clicks_then_the_time_is_updated():
    app = QtWidgets.QApplication(sys.argv)
    main_window = MainWindow()
    # no need to call `show`, we don't need to **see** it, it just needs to exist
    # DO NOT call `app.exec` because it is **blocking** !! (and because the window isn't shown you can't even close it manually)

    time_before = main_window.label.text()
    time.sleep(1)

    # instead, you will have to simulate a click
    from PyQt5 import QtTest, QtCore
    QtTest.QTest.mouseClick(main_window.button, QtCore.Qt.LeftButton)

    time_after = main_window.label.text()

    assert time_before < time_after  # works on strings, except at midnight

这是一个基本测试:它读取时间,等待一秒钟,单击以更新时间,再次读取并比较。

如果您愿意,我可以这样重写它:

from PyQt5 import QtTest, QtCore


def init_app() -> MainWindow:
    return MainWindow()


def left_mouse_click_on_button(widget: QtWidgets.QWidget) -> None:
    QtTest.QTest.mouseClick(widget, QtCore.Qt.LeftButton)


def test__when_user_clicks_then_the_time_is_updated():
    # given
    app = QtWidgets.QApplication(sys.argv)  # required
    main_window = init_app()
    time_before = main_window.label.text()

    # when
    time.sleep(1)  # waiting a bit
    left_mouse_click_on_button(main_window.button)

    # then
    time_after = main_window.label.text()
    assert time_before < time_after  # time got updated

你知道测试是如何进行的吗?我设置应用程序进行测试(不可见),我发送刺激(时间流逝,然后点击),然后我检查结果。

这是测试的基础。 如果您还有其他问题,您没有在原文中说明。我犹豫是否要标记您的问题,因为它缺乏重点。 StackOverflow 旨在回答精确的问题。你的不是。

我的意思是如何启动应用程序并执行其他步骤... # 启动应用程序 ... # mock QFileDialog.getOpenFileName 返回“video.raw” ... # 模拟用户触发“打开 mp4 视频录制”操作

这些是更好的问题!你应该在网上搜索如何做,如果你没有找到满意的答案,你可以带着一个精确的问题回来。

【讨论】:

  • 你好 Lenormju!是的,这正是我要找的。我能够测试 Ui_Mainwindow 构造函数的默认值,如果你能告诉我如何编写这个函数 def test_that_providing_a_raw_file_is_rejected(): 那对我真的很有帮助,我是 OOP 的新手,我真的很难理解一些概念,但是有了一个完整的例子,我想我将能够编写另一个测试功能的代码,我真的很感谢你的帮助,我很抱歉我的问题,只是我不知道该怎么做。
  • 我的意思是如何启动应用程序并执行其他步骤... # 启动应用程序 ... # mock QFileDialog.getOpenFileName 返回“video.raw” ... # 模拟用户触发“打开 mp4 视频录制”操作 I今天已经尝试了几个小时并阅读了如何做到这一点,但我一直无法做到。
  • @NelsonPetro 更新了我的答案
猜你喜欢
  • 2020-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多