本文檔說明如何在 AIMA-Python 專案的測試中模擬 matplotlib 圖表。
在運行測試時,我們不希望彈出圖表視窗或生成實際的圖像檔案。conftest.py 提供了多種 fixtures 來模擬 matplotlib 的繪圖功能。
用途: 自動模擬所有測試中的 plt.show(),防止彈出視窗。
特點:
- 設定為
autouse=True,無需顯式調用 - 自動應用到所有測試
- 使用
Agg非互動式後端
範例:
def test_simple_plot():
plt.plot([1, 2, 3])
plt.show() # 不會彈出視窗用途: 提供一個模擬的 Figure 物件,用於測試繪圖邏輯。
使用方法:
def test_with_mock_figure(mock_figure):
with patch('matplotlib.pyplot.figure', return_value=mock_figure):
fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(111)
ax.plot([1, 2, 3])
# 驗證方法被調用
assert mock_figure.add_subplot.called用途: 提供完整的 matplotlib.pyplot 模擬物件。
使用方法:
def test_with_mock_plt(mock_plt):
with patch.dict('sys.modules', {'matplotlib.pyplot': mock_plt}):
mock_plt.figure()
mock_plt.plot([1, 2, 3])
assert mock_plt.plot.called用途: 捕獲並記錄所有繪圖調用,用於驗證繪圖行為。
使用方法:
def test_capture_calls(capture_plot_calls):
with capture_plot_calls:
plt.figure()
plt.plot([1, 2, 3])
plt.show()
# 驗證調用
assert capture_plot_calls.figure_called
assert capture_plot_calls.plot_called
assert capture_plot_calls.show_called
# 檢查調用歷史
print(capture_plot_calls.calls)可用屬性:
plot_called: 是否調用了plot()show_called: 是否調用了show()figure_called: 是否調用了figure()imshow_called: 是否調用了imshow()scatter_called: 是否調用了scatter()savefig_called: 是否調用了savefig()calls: 所有調用的列表[(函數名, args, kwargs), ...]
用途: 完全禁用所有顯示和繪圖操作。
使用方法:
def test_no_display(no_display):
plt.plot([1, 2, 3])
plt.show()
plt.savefig('test.png')
# 所有操作都被靜默處理假設您有一個函數需要繪製圖表:
# 在 notebook.py 中
def plot_NQueens(solution):
n = len(solution)
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111)
ax.set_title('{} Queens'.format(n))
plt.show()測試這個函數:
def test_plot_NQueens(no_display):
solution = [0, 4, 7, 5, 2, 6, 1, 3]
plot_NQueens(solution) # 不會彈出視窗
# 函數執行完成,沒有錯誤如果您需要驗證特定的繪圖方法被調用:
def test_verify_plotting_behavior(capture_plot_calls):
with capture_plot_calls:
# 執行繪圖代碼
plt.figure()
plt.plot([1, 2, 3], [4, 5, 6])
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
# 驗證
assert capture_plot_calls.figure_called
assert capture_plot_calls.plot_called
assert capture_plot_calls.show_called
assert len(capture_plot_calls.calls) >= 3def test_heatmap(capture_plot_calls):
with capture_plot_calls:
grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
heatmap(grid) # 您的熱圖函數
assert capture_plot_calls.imshow_calleddef test_multiple_figures():
# 自動模擬 show()
for i in range(5):
fig = plt.figure()
plt.plot([1, 2, 3])
plt.close(fig)
# 無視窗彈出-
預設使用自動模擬: 大多數測試不需要顯式使用 fixture,因為
mock_matplotlib_show會自動處理。 -
需要驗證時使用
capture_plot_calls: 當您需要驗證特定的繪圖方法被調用時。 -
清理資源: 使用
plt.close()關閉不再需要的 figure 物件。 -
隔離測試: 每個測試應該獨立,不依賴其他測試的繪圖狀態。
在 conftest.py 中設定:
matplotlib.use('Agg')這將 matplotlib 設定為非互動式後端,不會創建任何視窗。
現有的 pytest.ini 已經配置了忽略警告:
[pytest]
filterwarnings =
ignore::DeprecationWarning
ignore::UserWarning
ignore::RuntimeWarning運行所有測試:
pytest tests/運行特定測試檔案:
pytest tests/test_mock_figures.py運行特定測試:
pytest tests/test_mock_figures.py::test_capture_plot_calls啟用詳細輸出:
pytest tests/ -v解決方案:
- 確保
conftest.py在tests/目錄中 - 檢查是否有其他地方設定了互動式後端
- 在測試檔案開頭添加:
import matplotlib matplotlib.use('Agg')
解決方案:
在 conftest.py 的相應 fixture 中添加該屬性的模擬。
解決方案:
使用 capture_plot_calls fixture 並確保在其 context manager 內執行繪圖代碼。
完整的範例請參考 tests/test_mock_figures.py。
def test_custom_mock():
with patch('matplotlib.pyplot.plot') as mock_plot:
mock_plot.return_value = [Mock()]
plt.plot([1, 2, 3])
# 驗證參數
mock_plot.assert_called_once()
args, kwargs = mock_plot.call_args
assert len(args[0]) == 3def test_module_specific():
with patch('notebook.plt.show'):
# 只模擬 notebook.py 中的 plt.show
from notebook import plot_NQueens
plot_NQueens([1, 3, 5, 7, 2, 0, 6, 4])如果需要添加新的繪圖函數模擬,請更新 conftest.py 中相應的 fixture。