1
0
wiki/Technology/ProgrammingLanguage/Python/进阶/测试和调试.html

335 lines
486 KiB
HTML
Raw Normal View History

2024-09-06 17:53:58 +08:00
<!DOCTYPE html>
<html lang="zh"><head><title>测试和调试</title><meta charset="utf-8"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com"/><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM Plex Mono&amp;family=Noto Serif Simplified Chinese:wght@400;700&amp;family=Source Sans Pro:ital,wght@0,400;0,600;1,400;1,600&amp;display=swap"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta property="og:title" content="测试和调试"/><meta property="og:description" content="Python 的测试和调试,主要涉及标准库的 pdb 和 unittest 和一些拓展概念,如 TDD 开发,高级测试技巧 mock 等。."/><meta property="og:image" content="https://wiki.7wate.com/static/og-image.png"/><meta property="og:width" content="1200"/><meta property="og:height" content="675"/><link rel="icon" href="../../../../static/icon.png"/><meta name="description" content="Python 的测试和调试,主要涉及标准库的 pdb 和 unittest 和一些拓展概念,如 TDD 开发,高级测试技巧 mock 等。."/><meta name="generator" content="Quartz"/><link href="../../../../index.css" rel="stylesheet" type="text/css" spa-preserve/><link href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css" rel="stylesheet" type="text/css" spa-preserve/><script src="../../../../prescript.js" type="application/javascript" spa-preserve></script><script type="application/javascript" spa-preserve>const fetchData = fetch("../../../../static/contentIndex.json").then(data => data.json())</script></head><body data-slug="Technology/ProgrammingLanguage/Python/进阶/测试和调试"><div id="quartz-root" class="page"><div id="quartz-body"><div class="left sidebar"><h2 class="page-title"><a href="../../../..">🪴 X·Eden</a></h2><div class="spacer mobile-only"></div><div class="search"><button class="search-button" id="search-button"><p>搜索</p><svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title>Search</title><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"></path><circle cx="8" cy="8" r="7"></circle></g></svg></button><div id="search-container"><div id="search-space"><input autocomplete="off" id="search-bar" name="search" type="text" aria-label="搜索些什么" placeholder="搜索些什么"/><div id="search-layout" data-preview="true"></div></div></div></div><button class="darkmode" id="darkmode"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="dayIcon" x="0px" y="0px" viewBox="0 0 35 35" style="enable-background:new 0 0 35 35" xml:space="preserve" aria-label="暗色模式"><title>暗色模式</title><path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7
<h3 id="理解调试">理解调试<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#理解调试" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>调试是软件开发过程中不可或缺的一部分。它涉及到识别和修复代码中的错误或缺陷,以确保程序按预期运行。有效的调试策略可以大幅减少开发时间,并提高代码质量。</p>
<h3 id="基本调试技巧">基本调试技巧<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#基本调试技巧" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>最基础的调试方法是使用 <code>print</code> 语句输出变量的值或程序的执行状态。虽然简单,但这种方法在初步诊断问题时非常有效。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(a, b):</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> print</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;a: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">a</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">, b: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">b</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">) </span><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 输出参数值</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> b</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> add(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">5</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'3'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">print</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">f</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;结果: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">{</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">result</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">}</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">&quot;</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></figure>
<p>在这个例子中,<code>print</code> 语句帮助我们理解错误发生的原因:尝试将整数和字符串相加。</p>
<h2 id="python-调试器pdb">Python 调试器pdb<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#python-调试器pdb" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p>Python 的内置调试器 <code>pdb</code> 是一个非常强大的交互式调试工具,它允许开发者逐行执行代码,检查当前的变量状态,评估表达式,甚至在运行时修改代码。</p>
<h3 id="基本使用">基本使用<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#基本使用" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>使用 <code>pdb</code> 的最简单方式是在代码中插入断点。可以通过在代码中添加 <code>import pdb; pdb.set_trace()</code> 来实现这一点。当程序执行到这一行时,它将暂停执行并进入<strong>交互式调试模式。</strong></p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> pdb</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> my_function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(arg1, arg2):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 设置断点</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> pdb.set_trace()</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # breakpoint() </span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arg1 </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> arg2</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">result </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> my_function(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></figure>
<p>当程序运行到 <code>pdb.set_trace()</code>程序将暂停并出现一个交互式的调试环境。Python 3.8 版本以后可以使用 <code>breakpoint()</code> 替代 <code>import pdb;pdb.set_trace()</code></p>
<h3 id="调试命令">调试命令<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#调试命令" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p><code>pdb</code> 的调试环境中,有多种命令可用于调试程序:</p>
<div class="table-container"><table><thead><tr><th>命令</th><th>描述</th></tr></thead><tbody><tr><td><code>l (list)</code></td><td>显示当前位置周围的代码。</td></tr><tr><td><code>n (next)</code></td><td>执行程序的下一行代码。如果当前行调用了一个函数,不会进入该函数内部。</td></tr><tr><td><code>c (continue)</code></td><td>从当前位置继续执行程序,直到遇到下一个断点。</td></tr><tr><td><code>b (break)</code></td><td>在指定的行号处设置断点。例如 <code>b 120</code> 在第 120 行设置断点。</td></tr><tr><td><code>s (step)</code></td><td>执行下一行代码,如果当前行调用了一个函数,则进入该函数内部。</td></tr><tr><td><code>p (print)</code></td><td>打印一个表达式的值。例如 <code>p my_var</code> 打印变量 <code>my_var</code> 的值。</td></tr><tr><td><code>q (quit)</code></td><td>退出调试器。</td></tr><tr><td><code>r (return)</code></td><td>继续执行,直到当前函数返回。</td></tr><tr><td><code>a (args)</code></td><td>打印当前函数的参数列表。</td></tr><tr><td><code>j (jump)</code></td><td>将当前执行位置跳转到指定的行。例如 <code>j 50</code> 跳转到第 50 行。</td></tr><tr><td><code>h (help)</code></td><td>显示命令列表或查找特定命令的帮助信息。</td></tr><tr><td><code>!</code></td><td>执行任意 Python 代码。例如 <code>!x = 5</code> 会设置变量 <code>x</code> 的值为 5。</td></tr></tbody></table></div>
<h3 id="调试流程">调试流程<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#调试流程" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p><code>pdb</code> 中,通常的调试流程包括设置断点、运行程序、在断点处暂停、检查变量值或程序状态、逐步执行代码,并根据需要重复这个过程。</p>
<pre><code class="mermaid">sequenceDiagram
participant P as 程序员
participant I as IDE/调试器
participant C as 代码
P->>+I: 启动调试
I->>+C: 运行到断点
C-->>-I: 暂停执行,显示当前状态
loop 检查和调整
P->>+I: 检查变量和堆栈
I->>+C: 获取信息
C-->>-I: 返回信息
P->>I: 修改代码或断点
end
P->>+I: 继续执行/单步执行
I->>+C: 执行下一步
C-->>-I: 更新状态
P->>I: 停止调试/修复代码
I-->>-P: 结束调试会话
</code></pre>
<h3 id="高级使用">高级使用<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#高级使用" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p><code>pdb</code> 还提供了一些高级功能,比如:</p>
<ul>
<li>
<p><strong>条件断点</strong>:您可以设置一个条件断点,只有在特定条件满足时才会停止。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">pdb.set_trace()</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 在命令行中使用</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># b 54, x > 100</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># b 命令 54 行设置 x 大于 100 停止</span></span></code></pre></figure>
</li>
<li>
<p><strong>后期调试</strong>:如果程序崩溃,可以使用 <code>pdb.pm()</code> 来进行后期调试。</p>
</li>
</ul>
<h3 id="命令行调试">命令行调试<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#命令行调试" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>您也可以直接从命令行启动 <code>pdb</code>。这在调试脚本时非常有用。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="shell" data-theme="github-light github-dark"><code data-language="shell" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">python</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> pdb</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> my_script.py</span></span></code></pre></figure>
<p>在这种模式下,<code>pdb</code> 将在脚本开始执行之前启动,允许您提前设置断点。</p>
<h2 id="python-单元测试unittest">Python 单元测试unittest<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#python-单元测试unittest" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<p><code>unittest</code> 是 Python 内置的测试框架,灵感来源于 JUnit。它支持自动化测试共享测试设置setup和关闭代码teardown聚集多个测试用例以及与测试框架的集成等功能。</p>
<h3 id="基本概念">基本概念<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#基本概念" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<ul>
<li><strong>测试用例TestCase</strong>:测试用例是 <code>unittest</code> 中的基本单元。每一个测试用例是 <code>unittest.TestCase</code> 的子类,通常包含多个<strong><code>test</code> 开头的方法</strong>,这些方法是实际的测试脚本。</li>
<li><strong>测试套件TestSuite</strong>:测试套件是一系列的测试用例或测试套件。它用于聚合需要一起执行的测试用例。</li>
<li><strong>测试运行器TestRunner</strong>:测试运行器是用于执行和控制测试的组件。默认的运行器会将测试结果输出到标准输出。</li>
<li><strong>测试装置TestFixture</strong>:测试装置指的是执行一系列测试所需的准备工作及相关的清理操作。这通常包括 <code>setUp()</code><code>tearDown()</code> 方法。</li>
</ul>
<h3 id="基本的测试用例">基本的测试用例<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#基本的测试用例" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>使用 <code>unittest</code> 编写测试用例通常涉及创建一个继承自 <code>unittest.TestCase</code> 的类,并在其中定义一系列的测试方法。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> unittest</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> MyTest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">unittest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TestCase</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> setUp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 测试前的准备工作</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> pass</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> </span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 类内部 test 开头的方法视为一个测试用例</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> test_something</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 实际的测试内容</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.assertEqual(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> +</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> 1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> tearDown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 测试后的清理工作</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> pass</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">if</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> __name__</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> ==</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> '__main__'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> unittest.main()</span></span></code></pre></figure>
<p>在这个例子中,<code>MyTest</code> 类包含了一个简单的测试方法 <code>test_something</code>,它测试了 <code>1 + 1</code> 是否等于 <code>2</code></p>
<h3 id="断言方法">断言方法<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#断言方法" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p><code>unittest</code> 提供了一系列的断言方法来检查期望和实际结果。常用的断言方法包括:</p>
<div class="table-container"><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td><code>assertEqual(a, b)</code></td><td>检查 <code>a</code> 是否等于 <code>b</code></td></tr><tr><td><code>assertNotEqual(a, b)</code></td><td>检查 <code>a</code> 是否不等于 <code>b</code></td></tr><tr><td><code>assertTrue(x)</code></td><td>检查 <code>x</code> 是否为 <code>True</code></td></tr><tr><td><code>assertFalse(x)</code></td><td>检查 <code>x</code> 是否为 <code>False</code></td></tr><tr><td><code>assertIs(a, b)</code></td><td>检查 <code>a</code> 是否是 <code>b</code> <code>a is b</code></td></tr><tr><td><code>assertIsNot(a, b)</code></td><td>检查 <code>a</code> 是否不是 <code>b</code></td></tr><tr><td><code>assertIsNone(x)</code></td><td>检查 <code>x</code> 是否为 <code>None</code></td></tr><tr><td><code>assertIsNotNone(x)</code></td><td>检查 <code>x</code> 是否不是 <code>None</code></td></tr><tr><td><code>assertIn(a, b)</code></td><td>检查 <code>a</code> 是否在 <code>b</code></td></tr><tr><td><code>assertNotIn(a, b)</code></td><td>检查 <code>a</code> 是否不在 <code>b</code></td></tr><tr><td><code>assertIsInstance(a, b)</code></td><td>检查 <code>a</code> 是否是 <code>b</code> 类型的实例</td></tr><tr><td><code>assertNotIsInstance(a, b)</code></td><td>检查 <code>a</code> 是否不是 <code>b</code> 类型的实例</td></tr><tr><td><code>assertRaises(Error, func, *args, **kwargs)</code></td><td>检查调用 <code>func</code> 时是否抛出了 <code>Error</code> 异常</td></tr></tbody></table></div>
<h3 id="设置和清理">设置和清理<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#设置和清理" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>通过定义 <code>setUp</code><code>tearDown</code> 方法,可以在每个测试方法执行前后进行设置和清理工作。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> MyTest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">unittest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TestCase</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> setUp</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 测试前的设置</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.resource </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> allocate_resource()</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> tearDown</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> # 测试后的清理</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.resource.release()</span></span></code></pre></figure>
<h3 id="运行测试">运行测试<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#运行测试" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>通常有两种方式运行测试:</p>
<ul>
<li>直接运行测试文件:如果 <code>unittest.main()</code> 被调用,当 Python 文件被直接运行时,测试将被执行。</li>
<li>使用命令行:可以使用 Python 的 <code>-m unittest</code> 命令来发现和运行测试。</li>
</ul>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="shell" data-theme="github-light github-dark"><code data-language="shell" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">python</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -m</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> unittest</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> discover</span></span></code></pre></figure>
<h2 id="测试驱动开发tdd">测试驱动开发TDD<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#测试驱动开发tdd" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<h3 id="tdd-概念">TDD 概念<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#tdd-概念" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>测试驱动开发TDD是一种软件开发方法它要求开发人员先编写测试用例然后编写能够通过这些测试的代码。这种方法强调先有测试后有实现有助于创建更可靠、更易维护的代码。</p>
<h3 id="实践-tdd">实践 TDD<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#实践-tdd" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p><strong>在 TDD 实践中,开发过程分为三个主要步骤:编写失败的测试、编写通过测试的代码、重构代码。</strong></p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 假设我们有一个待实现的函数 add</span></span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 首先编写测试用例</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TestAddFunction</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">unittest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TestCase</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> test_add_numbers</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.assertEqual(add(</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">2</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">), </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 接着实现函数</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> add</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(a, b):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> a </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">+</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> b</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 最后运行测试,确认通过</span></span></code></pre></figure>
<p>通过重复这个循环,逐步构建并完善整个程序。</p>
<h2 id="高级测试技巧">高级测试技巧<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#高级测试技巧" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<h3 id="集成测试和系统测试">集成测试和系统测试<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#集成测试和系统测试" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>集成测试是指在单元测试的基础上,测试多个模块或组件协同工作的情况。系统测试则是测试整个应用程序的行为。</p>
<h3 id="mocking-和-patching">Mocking 和 Patching<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#mocking-和-patching" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>在测试过程中我们经常需要模拟Mock某些对象或函数的行为以便于在测试环境中替换掉它们。Python 的 <code>unittest.mock</code> 模块提供了强大的工具来进行 Mocking 和 Patching。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="python" data-theme="github-light github-dark"><code data-language="python" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">from</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> unittest.mock </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">import</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> MagicMock</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 假设我们要测试以下函数</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> fetch_data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(api_client):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> api_client.get_data()</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 我们可以这样模拟 API 客户端</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">class</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> TestFetchData</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">unittest</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">TestCase</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">):</span></span>
<span data-line><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> def</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> test_fetch_data</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">(self):</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> mock_client </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> MagicMock()</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> mock_client.get_data.return_value </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> 'mock data'</span></span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> self</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.assertEqual(fetch_data(mock_client), </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">'mock data'</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">)</span></span></code></pre></figure>
<p>在这个例子中,我们使用 <code>MagicMock</code> 来模拟 API 客户端的行为。</p>
<h2 id="测试框架和工具">测试框架和工具<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#测试框架和工具" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<h3 id="探索其他测试框架">探索其他测试框架<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#探索其他测试框架" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>除了 <code>unittest</code>Python 还有其他流行的测试框架,如 <code>pytest</code><code>nose</code>。它们提供了更简洁的语法和更丰富的功能。</p>
<h3 id="代码覆盖率工具">代码覆盖率工具<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#代码覆盖率工具" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>代码覆盖率是衡量测试完整性的重要指标。Python 的 <code>coverage.py</code> 工具可以帮助测量代码覆盖率,确保测试覆盖了所有重要的代码路径。</p>
<h2 id="持续集成-ci">持续集成 (CI)<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#持续集成-ci" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h2>
<h3 id="持续集成简介">持续集成简介<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#持续集成简介" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>持续集成CI是一种软件开发实践开发者经常将代码集成到共享仓库中。每次集成都通过自动化构建来验证以尽早发现集成错误。</p>
<h3 id="使用-ci-工具">使用 CI 工具<a role="anchor" aria-hidden="true" tabindex="-1" data-no-popover="true" href="#使用-ci-工具" class="internal"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg></a></h3>
<p>CI 工具如 Jenkins、Travis CI 和 GitHub Actions 可以帮助自动化测试流程,确保代码更改不会破坏现有功能。</p>
<figure data-rehype-pretty-code-figure><pre tabindex="0" data-language="yaml" data-theme="github-light github-dark"><code data-language="yaml" data-theme="github-light github-dark" style="display:grid;"><span data-line><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"># 示例GitHub Actions 配置文件</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">Python CI</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">push</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">]</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">jobs</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> build</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> runs-on</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">ubuntu-latest</span></span>
<span data-line> </span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> steps</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">actions/checkout@v2</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">Set up Python</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> uses</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">actions/setup-python@v2</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> with</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> python-version</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">3.8</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">Install dependencies</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span></span>
<span data-line><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> pip install -r requirements.txt</span></span>
<span data-line><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> - </span><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">name</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">Run tests</span></span>
<span data-line><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;"> run</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">|</span></span>
<span data-line><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> python -m unittest</span></span></code></pre></figure></article><hr/><div class="page-footer"></div></div><div class="right sidebar"><div class="graph"><h3>关系图谱</h3><div class="graph-outer"><div id="graph-container" data-cfg="{&quot;drag&quot;:true,&quot;zoom&quot;:true,&quot;depth&quot;:1,&quot;scale&quot;:1.1,&quot;repelForce&quot;:0.5,&quot;centerForce&quot;:0.3,&quot;linkDistance&quot;:30,&quot;fontSize&quot;:0.6,&quot;opacityScale&quot;:1,&quot;showTags&quot;:true,&quot;removeTags&quot;:[],&quot;focusOnHover&quot;:false}"></div><button id="global-graph-icon" aria-label="Global Graph"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 55 55" fill="currentColor" xml:space="preserve"><path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91
v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4
s-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2
S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4
s1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"></path></svg></button></div><div id="global-graph-outer"><div id="global-graph-container" data-cfg="{&quot;drag&quot;:true,&quot;zoom&quot;:true,&quot;depth&quot;:-1,&quot;scale&quot;:0.9,&quot;repelForce&quot;:0.5,&quot;centerForce&quot;:0.3,&quot;linkDistance&quot;:30,&quot;fontSize&quot;:0.6,&quot;opacityScale&quot;:1,&quot;showTags&quot;:true,&quot;removeTags&quot;:[],&quot;focusOnHover&quot;:true}"></div></div></div><div class="toc desktop-only"><button type="button" id="toc" class aria-controls="toc-content" aria-expanded="true"><h3>目录</h3><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold"><polyline points="6 9 12 15 18 9"></polyline></svg></button><div id="toc-content" class><ul class="overflow"><li class="depth-0"><a href="#调试基础" data-for="调试基础">调试基础</a></li><li class="depth-1"><a href="#理解调试" data-for="理解调试">理解调试</a></li><li class="depth-1"><a href="#基本调试技巧" data-for="基本调试技巧">基本调试技巧</a></li><li class="depth-0"><a href="#python-调试器pdb" data-for="python-调试器pdb">Python 调试器pdb</a></li><li class="depth-1"><a href="#基本使用" data-for="基本使用">基本使用</a></li><li class="depth-1"><a href="#调试命令" data-for="调试命令">调试命令</a></li><li class="depth-1"><a href="#调试流程" data-for="调试流程">调试流程</a></li><li class="depth-1"><a href="#高级使用" data-for="高级使用">高级使用</a></li><li class="depth-1"><a href="#命令行调试" data-for="命令行调试">命令行调试</a></li><li class="depth-0"><a href="#python-单元测试unittest" data-for="python-单元测试unittest">Python 单元测试unittest</a></li><li class="depth-1"><a href="#基本概念" data-for="基本概念">基本概念</a></li><li class="depth-1"><a href="#基本的测试用例" data-for="基本的测试用例">基本的测试用例</a></li><li class="depth-1"><a href="#断言方法" data-for="断言方法">断言方法</a></li><li class="depth-1"><a href="#设置和清理" data-for="设置和清理">设置和清理</a></li><li class="depth-1"><a href="#运行测试" data-for="运行测试">运行测试</a></li><li class="depth-0"><a href="#测试驱动开发tdd" data-for="测试驱动开发tdd">测试驱动开发TDD</a></li><li class="depth-1"><a href="#tdd-概念" data-for="tdd-概念">TDD 概念</a></li><li class="depth-1"><a href="#实践-tdd" data-for="实践-tdd">实践 TDD</a></li><li class="depth-0"><a href="#高级测试技巧" data-for="高级测试技巧">高级测试技巧</a></li><li class="depth-1"><a href="#集成测试和系统测试" data-for="集成测试和系统测试">集成测试和系统测试</a></li><li class="depth-1"><a href="#mocking-和-patching" data-for="mocking-和-patching">Mocking 和 Patching</a></li><li class="depth-0"><a href="#测试框架和工具" data-for="测试框架和工具">测试框架和工具</a></li><li class="depth-1"><a href="#探索其他测试框架" data-for="探索其他测试框架">探索其他测试框架</a></li><li class="depth-1"><a href="#代码覆盖率工具" data-for="代码覆盖率工具">代码覆盖率工具</a></li><li class="depth-0"><a href="#持续集成-ci" data-for="持续集成-ci">持续集成 (CI)</a></li><li class="depth-1"><a href="#持续集成简介" data-for="持续集成简介">持续集成简介</a></li><li class="depth-1"><a href="#使用-ci-工具" data-for="使用-ci-工具">使用 CI 工具</a></li></ul></div></div><div class="explorer mobile-only"><button type="button" id="explorer" data-behavior="collapse" data-collapsed="collapsed" data-savestate="true" data-tree="[{&quot;path&quot;:&quot;Personal&quot;,&quot;collapsed&quot;:true},{&quot;path&quot;:&quot;Personal/Blog&quot;,&quot;collapsed&quot;:true},{&quot;path&
</script><script type="module">
let mermaidImport = undefined
document.addEventListener('nav', async () => {
if (document.querySelector("code.mermaid")) {
mermaidImport ||= await import('https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.7.0/mermaid.esm.min.mjs')
const mermaid = mermaidImport.default
const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
mermaid.initialize({
startOnLoad: false,
securityLevel: 'loose',
theme: darkMode ? 'dark' : 'default'
})
await mermaid.run({
querySelector: '.mermaid'
})
}
});
</script><script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/contrib/copy-tex.min.js" type="application/javascript"></script><script src="../../../../postscript.js" type="module"></script></html>