如何为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")
核心测试思路
- 边界分支覆盖:测试合法/非法输入、正常/异常场景
- Mock外部依赖:避免测试依赖真实文件、第三方服务或系统命令
- 验证行为而非结果:对于有副作用的函数,重点验证是否正确调用了依赖,而非真的生成文件或执行命令
内容的提问来源于stack exchange,提问作者Khaled
相关产品推荐
相关产品推荐

