You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为3个无参数Python函数编写测试?我遇到了难题

如何为无参数函数编写测试用例

你的代码之所以难测试,核心问题是函数过度依赖全局变量(book、myText),且部分函数直接触发退出、文件操作等副作用,导致输入输出不透明。下面分两步解决:先重构代码降低耦合(推荐),再编写对应的测试用例;同时也提供不重构代码的临时测试方案。


第一步:重构主代码(让测试变简单)

先修改原程序,让函数摆脱全局变量,明确输入输出:

import sys
import PyPDF2
from tqdm import tqdm
import time
from gtts import gTTS
import os

def check_file(file_path):
    """验证文件是否为PDF,非PDF则抛出异常"""
    if not file_path.endswith(".pdf"):
        raise ValueError("File isn't a pdf!")

def read_text(file_path):
    """读取PDF文本并返回"""
    with open(file_path, "rb") as f:
        # 注意:PyPDF2新版本用PdfReader,旧版是PdfFileReader,根据你的版本调整
        pdf = PyPDF2.PdfReader(f)
        extracted_text = ""
        for page_num in tqdm(range(len(pdf.pages))):
            time.sleep(0.3)
            page = pdf.pages[page_num]
            extracted_text += page.extract_text()
    return extracted_text

def generate_audio(text, output_path="audio.mp3", lang="en"):
    """将文本转为音频并保存,返回输出路径"""
    tts = gTTS(text=text, lang=lang)
    print("Generating Speech...")
    tts.save(output_path)
    os.system(f"start {output_path}")
    print("Successfully Generated!!")
    return output_path

def main():
    book = "think.pdf"
    try:
        check_file(book)
        text = read_text(book)
        generate_audio(text)
    except ValueError as e:
        sys.exit(str(e))

if __name__ == "__main__":
    main()

重构说明:

  • 每个函数都添加了明确的参数,不再依赖全局变量
  • check_file用抛出异常替代sys.exit,方便测试捕获
  • read_text返回提取的文本,结果可直接验证
  • generate_audio接收文本参数,返回输出路径,逻辑边界清晰

第二步:编写测试用例(基于pytest)

测试check_file

验证合法/非法输入的分支逻辑:

import pytest
from project import check_file

def test_check_file_valid_pdf():
    # 合法PDF文件应正常执行,无异常
    check_file("test.pdf")

def test_check_file_invalid_file():
    # 非PDF文件应抛出指定异常
    with pytest.raises(ValueError, match="File isn't a pdf!"):
        check_file("test.txt")

测试read_text

用mock模拟PDF文件和PyPDF2的行为,避免依赖真实文件:

from unittest.mock import mock_open, patch, Mock
from project import read_text

def test_read_text_extracts_content():
    # 模拟PDF页面的文本内容
    mock_page = Mock()
    mock_page.extract_text.return_value = "Sample page content"
    # 模拟PDF对象包含1个页面
    mock_pdf = Mock()
    mock_pdf.pages = [mock_page]

    with patch("PyPDF2.PdfReader", return_value=mock_pdf):
        with patch("builtins.open", mock_open(read_data=b"mock pdf data")):
            result = read_text("test.pdf")
            assert result == "Sample page content"
            # 验证PyPDF2是否被正确调用
            PyPDF2.PdfReader.assert_called_once()

测试generate_audio

mock掉gTTS和系统命令,只验证逻辑是否正确触发了依赖:

from unittest.mock import patch
from project import generate_audio

def test_generate_audio_creates_file():
    with patch("gtts.gTTS") as mock_tts:
        # 模拟gTTS实例的save方法
        mock_tts_instance = mock_tts.return_value
        mock_tts_instance.save.return_value = None

        # 模拟系统命令调用
        with patch("os.system") as mock_system:
            output_path = generate_audio("Test speech", "output.mp3")
            # 验证gTTS初始化参数正确
            mock_tts.assert_called_once_with(text="Test speech", lang="en")
            # 验证save方法被调用
            mock_tts_instance.save.assert_called_once_with("output.mp3")
            # 验证系统命令被触发
            mock_system.assert_called_once_with("start output.mp3")
            # 验证返回值正确
            assert output_path == "output.mp3"

不重构原代码的临时测试方案(不推荐)

如果暂时不想修改主程序,可以用pytest的monkeypatch修改全局变量,mock外部依赖:

import pytest
from project import check_file, readText, generate_audio
from unittest.mock import mock_open, patch, Mock

# 测试check_file
def test_check_file_valid(monkeypatch):
    monkeypatch.setattr("project.book", "valid.pdf")
    # 不触发SystemExit即为通过
    check_file()

def test_check_file_invalid(monkeypatch):
    monkeypatch.setattr("project.book", "invalid.txt")
    with pytest.raises(SystemExit):
        check_file()

# 测试readText
def test_readText_sets_global(monkeypatch):
    monkeypatch.setattr("project.book", "test.pdf")
    mock_page = Mock()
    mock_page.extractText.return_value = "Test content"
    mock_pdf = Mock()
    mock_pdf.numPages = 1
    mock_pdf.getPage.return_value = mock_page

    with patch("PyPDF2.PdfFileReader", return_value=mock_pdf):
        with patch("builtins.open", mock_open(read_data=b"")):
            readText()
            # 检查全局变量myText是否被正确赋值
            from project import myText
            assert myText == "Test content"

# 测试generate_audio
def test_generate_audio_triggers_actions(monkeypatch):
    monkeypatch.setattr("project.myText", "Test audio text")
    with patch("gtts.gTTS") as mock_tts:
        mock_tts_instance = mock_tts.return_value
        mock_tts_instance.save.return_value = Mock()
        mock_tts_instance.save.return_value.time.return_value = None

        with patch("os.system") as mock_system:
            generate_audio()
            mock_tts.assert_called_once_with(text="Test audio text", lang="en")
            mock_tts_instance.save.assert_called_once_with("audio.mp3")
            mock_system.assert_called_once_with("start audio.mp3")

核心测试思路

  1. 边界分支覆盖:测试合法/非法输入、正常/异常场景
  2. Mock外部依赖:避免测试依赖真实文件、第三方服务或系统命令
  3. 验证行为而非结果:对于有副作用的函数,重点验证是否正确调用了依赖,而非真的生成文件或执行命令

内容的提问来源于stack exchange,提问作者Khaled

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 14:20:24