mirror of
https://github.com/fofolee/uTools-Manuals.git
synced 2025-06-08 23:14:06 +08:00
539 lines
154 KiB
HTML
539 lines
154 KiB
HTML
<div class="body" role="main"><div class="section" id="unittest-mock-getting-started"><h1><span class="yiyi-st" id="yiyi-10">26.6. <a class="reference internal" href="unittest.mock.html#module-unittest.mock" title="unittest.mock: Mock object library."><code class="xref py py-mod docutils literal"><span class="pre">unittest.mock</span></code></a> - 开始</span></h1><div class="versionadded"><p><span class="yiyi-st" id="yiyi-11"><span class="versionmodified">版本3.3中的新功能。</span></span></p></div><div class="section" id="using-mock"><h2><span class="yiyi-st" id="yiyi-12">26.6.1. </span><span class="yiyi-st" id="yiyi-13">Using Mock</span></h2><div class="section" id="mock-patching-methods"><h3><span class="yiyi-st" id="yiyi-14">26.6.1.1. </span><span class="yiyi-st" id="yiyi-15">Mock Patching Methods</span></h3><p><span class="yiyi-st" id="yiyi-16"><a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>对象的常见用法包括:</span></p><ul class="simple"><li><span class="yiyi-st" id="yiyi-17">修补方法</span></li><li><span class="yiyi-st" id="yiyi-18">记录方法调用对象</span></li></ul><p><span class="yiyi-st" id="yiyi-19">您可能想要替换对象上的方法,以检查它是否被系统的另一部分的正确参数调用:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">real</span> <span class="o">=</span> <span class="n">SomeClass</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">method</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'method'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">method</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="n">key</span><span class="o">=</span><span class="s1">'value'</span><span class="p">)</span>
|
||
<span class="go"><MagicMock name='method()' id='...'></span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-20">一旦我们的模拟被使用(在这个例子中<code class="docutils literal"><span class="pre">real.method</span></code>),它有方法和属性,允许你对如何使用它的断言。</span></p><div class="admonition note"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-21">注意</span></p><p class="last"><span class="yiyi-st" id="yiyi-22">在大多数这些示例中,<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>和<a class="reference internal" href="unittest.mock.html#unittest.mock.MagicMock" title="unittest.mock.MagicMock"><code class="xref py py-class docutils literal"><span class="pre">MagicMock</span></code></a>类是可互换的。</span><span class="yiyi-st" id="yiyi-23">由于<code class="docutils literal"><span class="pre">MagicMock</span></code>是更强大的类,因此它默认使用一个明智的类。</span></p></div><p><span class="yiyi-st" id="yiyi-24">一旦调用模拟,它的<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.called" title="unittest.mock.Mock.called"><code class="xref py py-attr docutils literal"><span class="pre">called</span></code></a>属性设置为<code class="docutils literal"><span class="pre">True</span></code>。</span><span class="yiyi-st" id="yiyi-25">更重要的是,我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.assert_called_with" title="unittest.mock.Mock.assert_called_with"><code class="xref py py-meth docutils literal"><span class="pre">assert_called_with()</span></code></a>或<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.assert_called_once_with" title="unittest.mock.Mock.assert_called_once_with"><code class="xref py py-meth docutils literal"><span class="pre">assert_called_once_with()</span></code></a>方法来检查它是否使用正确的参数调用。</span></p><p><span class="yiyi-st" id="yiyi-26">此示例测试调用<code class="docutils literal"><span class="pre">ProductionClass().method</span></code>会调用<code class="docutils literal"><span class="pre">something</span></code>方法:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">ProductionClass</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">something</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">something</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">real</span> <span class="o">=</span> <span class="n">ProductionClass</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">something</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">method</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">something</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
</code></pre></div><div class="section" id="mock-for-method-calls-on-an-object"><h3><span class="yiyi-st" id="yiyi-27">26.6.1.2. </span><span class="yiyi-st" id="yiyi-28">Mock for Method Calls on an Object</span></h3><p><span class="yiyi-st" id="yiyi-29">在上一个示例中,我们直接在一个对象上修补了一个方法,以检查它是否被正确调用。</span><span class="yiyi-st" id="yiyi-30">另一个常见的用例是将对象传递给方法(或被测系统的某些部分),然后检查它是否以正确的方式使用。</span></p><p><span class="yiyi-st" id="yiyi-31">下面的简单<code class="docutils literal"><span class="pre">ProductionClass</span></code>具有<code class="docutils literal"><span class="pre">closer</span></code>方法。</span><span class="yiyi-st" id="yiyi-32">如果它被一个对象调用,它会调用<code class="docutils literal"><span class="pre">close</span></code>。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">ProductionClass</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">closer</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">something</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">something</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-33">所以要测试它,我们需要传递一个具有<code class="docutils literal"><span class="pre">close</span></code>方法的对象,并检查它是否被正确调用。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">real</span> <span class="o">=</span> <span class="n">ProductionClass</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">real</span><span class="o">.</span><span class="n">closer</span><span class="p">(</span><span class="n">mock</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">close</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-34">我们不需要做任何工作来在我们的模拟上提供“关闭”方法。</span><span class="yiyi-st" id="yiyi-35">访问close会创建它。</span><span class="yiyi-st" id="yiyi-36">所以,如果'close'还没有被调用,那么在测试中访问它会创建它,但<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.assert_called_with" title="unittest.mock.Mock.assert_called_with"><code class="xref py py-meth docutils literal"><span class="pre">assert_called_with()</span></code></a>会引发一个失败异常。</span></p></div><div class="section" id="mocking-classes"><h3><span class="yiyi-st" id="yiyi-37">26.6.1.3. </span><span class="yiyi-st" id="yiyi-38">Mocking Classes</span></h3><p><span class="yiyi-st" id="yiyi-39">一个常见的用例是模拟被测试代码实例化的类。</span><span class="yiyi-st" id="yiyi-40">当你修补一个类,那么该类被替换为一个模拟。</span><span class="yiyi-st" id="yiyi-41">实例由<em>创建,调用类</em>。</span><span class="yiyi-st" id="yiyi-42">这意味着你通过查看嘲笑类的返回值来访问“mock实例”。</span></p><p><span class="yiyi-st" id="yiyi-43">在下面的例子中,我们有一个函数<code class="docutils literal"><span class="pre">some_function</span></code>实例化<code class="docutils literal"><span class="pre">Foo</span></code>并调用它的方法。</span><span class="yiyi-st" id="yiyi-44">调用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>用模拟替换类<code class="docutils literal"><span class="pre">Foo</span></code>。</span><span class="yiyi-st" id="yiyi-45"><code class="docutils literal"><span class="pre">Foo</span></code>实例是调用模拟的结果,因此通过修改模拟<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.return_value" title="unittest.mock.Mock.return_value"><code class="xref py py-attr docutils literal"><span class="pre">return_value</span></code></a>来配置。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">def</span> <span class="nf">some_function</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="n">instance</span> <span class="o">=</span> <span class="n">module</span><span class="o">.</span><span class="n">Foo</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">instance</span><span class="o">.</span><span class="n">method</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'module.Foo'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">instance</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">return_value</span>
|
||
<span class="gp">... </span> <span class="n">instance</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="s1">'the result'</span>
|
||
<span class="gp">... </span> <span class="n">result</span> <span class="o">=</span> <span class="n">some_function</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">result</span> <span class="o">==</span> <span class="s1">'the result'</span>
|
||
</code></pre></div><div class="section" id="naming-your-mocks"><h3><span class="yiyi-st" id="yiyi-46">26.6.1.4. </span><span class="yiyi-st" id="yiyi-47">Naming your mocks</span></h3><p><span class="yiyi-st" id="yiyi-48">它可以是有用的给你的嘲笑一个名字。</span><span class="yiyi-st" id="yiyi-49">该名称显示在模拟的repr中,并且当模拟出现在测试失败消息中时可以是有帮助的。</span><span class="yiyi-st" id="yiyi-50">该名称还传播到模拟的属性或方法:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'foo'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span>
|
||
<span class="go"><MagicMock name='foo' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">method</span>
|
||
<span class="go"><MagicMock name='foo.method' id='...'></span>
|
||
</code></pre></div><div class="section" id="tracking-all-calls"><h3><span class="yiyi-st" id="yiyi-51">26.6.1.5. </span><span class="yiyi-st" id="yiyi-52">Tracking all Calls</span></h3><p><span class="yiyi-st" id="yiyi-53">通常你想跟踪一个方法的多个调用。</span><span class="yiyi-st" id="yiyi-54"><a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.mock_calls" title="unittest.mock.Mock.mock_calls"><code class="xref py py-attr docutils literal"><span class="pre">mock_calls</span></code></a>属性记录所有对模拟的子属性的调用 - 以及它们的子节点。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">method</span><span class="p">()</span>
|
||
<span class="go"><MagicMock name='mock.method()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">attribute</span><span class="o">.</span><span class="n">method</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">x</span><span class="o">=</span><span class="mi">53</span><span class="p">)</span>
|
||
<span class="go"><MagicMock name='mock.attribute.method()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">mock_calls</span>
|
||
<span class="go">[call.method(), call.attribute.method(10, x=53)]</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-55">如果对<code class="docutils literal"><span class="pre">mock_calls</span></code>作出断言,并且调用了任何意外的方法,则断言将失败。</span><span class="yiyi-st" id="yiyi-56">这是有用的,因为除了断言您预期的呼叫已经发生,您还检查他们是按正确的顺序,没有额外的电话:</span></p><p><span class="yiyi-st" id="yiyi-57">您可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.call" title="unittest.mock.call"><code class="xref py py-data docutils literal"><span class="pre">call</span></code></a>对象来构造与<code class="docutils literal"><span class="pre">mock_calls</span></code>进行比较的列表:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">expected</span> <span class="o">=</span> <span class="p">[</span><span class="n">call</span><span class="o">.</span><span class="n">method</span><span class="p">(),</span> <span class="n">call</span><span class="o">.</span><span class="n">attribute</span><span class="o">.</span><span class="n">method</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">x</span><span class="o">=</span><span class="mi">53</span><span class="p">)]</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">mock_calls</span> <span class="o">==</span> <span class="n">expected</span>
|
||
<span class="go">True</span>
|
||
</code></pre></div><div class="section" id="setting-return-values-and-attributes"><h3><span class="yiyi-st" id="yiyi-58">26.6.1.6. </span><span class="yiyi-st" id="yiyi-59">Setting Return Values and Attributes</span></h3><p><span class="yiyi-st" id="yiyi-60">在模拟对象上设置返回值是非常容易的:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="mi">3</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="go">3</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-61">当然你可以做同样的方法在模拟:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">method</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="mi">3</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">method</span><span class="p">()</span>
|
||
<span class="go">3</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-62">返回值也可以在构造函数中设置:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="go">3</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-63">如果你需要一个属性设置你的模拟,只要这样做:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">3</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">x</span>
|
||
<span class="go">3</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-64">有时你想模拟一个更复杂的情况,例如<code class="docutils literal"><span class="pre">mock.connection.cursor()。execute(“SELECT</span> <span class="pre">1”)</span> 。</code></span><span class="yiyi-st" id="yiyi-65">如果我们想要这个调用返回一个列表,那么我们必须配置嵌套调用的结果。</span></p><p><span class="yiyi-st" id="yiyi-66">我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.call" title="unittest.mock.call"><code class="xref py py-data docutils literal"><span class="pre">call</span></code></a>在“链接调用”中构造一组调用,像这样容易断言:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">cursor</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">cursor</span><span class="o">.</span><span class="n">return_value</span>
|
||
<span class="gp">>>> </span><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'foo'</span><span class="p">]</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"SELECT 1"</span><span class="p">)</span>
|
||
<span class="go">['foo']</span>
|
||
<span class="gp">>>> </span><span class="n">expected</span> <span class="o">=</span> <span class="n">call</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">cursor</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s2">"SELECT 1"</span><span class="p">)</span><span class="o">.</span><span class="n">call_list</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">mock_calls</span>
|
||
<span class="go">[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">mock_calls</span> <span class="o">==</span> <span class="n">expected</span>
|
||
<span class="go">True</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-67">它是对<code class="docutils literal"><span class="pre">.call_list()</span></code>的调用,它将我们的调用对象转换为表示链接调用的调用列表。</span></p></div><div class="section" id="raising-exceptions-with-mocks"><h3><span class="yiyi-st" id="yiyi-68">26.6.1.7. </span><span class="yiyi-st" id="yiyi-69">Raising exceptions with mocks</span></h3><p><span class="yiyi-st" id="yiyi-70">一个有用的属性是<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.side_effect" title="unittest.mock.Mock.side_effect"><code class="xref py py-attr docutils literal"><span class="pre">side_effect</span></code></a>。</span><span class="yiyi-st" id="yiyi-71">如果将此设置为异常类或实例,则在调用模拟时将引发异常。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="ne">Exception</span><span class="p">(</span><span class="s1">'Boom!'</span><span class="p">))</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="c">...</span>
|
||
<span class="gr">Exception</span>: <span class="n">Boom!</span>
|
||
</code></pre></div><div class="section" id="side-effect-functions-and-iterables"><h3><span class="yiyi-st" id="yiyi-72">26.6.1.8. </span><span class="yiyi-st" id="yiyi-73">Side effect functions and iterables</span></h3><p><span class="yiyi-st" id="yiyi-74"><code class="docutils literal"><span class="pre">side_effect</span></code>也可以设置为一个函数或一个迭代。</span><span class="yiyi-st" id="yiyi-75"><code class="docutils literal"><span class="pre">side_effect</span></code>作为可迭代的用例是,你的mock将被调用几次,你想让每次调用返回一个不同的值。</span><span class="yiyi-st" id="yiyi-76">当将<code class="docutils literal"><span class="pre">side_effect</span></code>设置为可迭代时,每次对mock的调用都会返回可迭代的下一个值:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="p">[</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">])</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="go">4</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="go">5</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="go">6</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-77">对于更高级的用例,例如根据调用模拟而动态地改变返回值,<code class="docutils literal"><span class="pre">side_effect</span></code>可以是一个函数。</span><span class="yiyi-st" id="yiyi-78">该函数将使用与模拟相同的参数进行调用。</span><span class="yiyi-st" id="yiyi-79">无论函数返回什么是调用返回:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">vals</span> <span class="o">=</span> <span class="p">{(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span> <span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">):</span> <span class="mi">2</span><span class="p">}</span>
|
||
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">side_effect</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">vals</span><span class="p">[</span><span class="n">args</span><span class="p">]</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">side_effect</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
|
||
<span class="go">1</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
<span class="go">2</span>
|
||
</code></pre></div><div class="section" id="creating-a-mock-from-an-existing-object"><h3><span class="yiyi-st" id="yiyi-80">26.6.1.9. </span><span class="yiyi-st" id="yiyi-81">Creating a Mock from an Existing Object</span></h3><p><span class="yiyi-st" id="yiyi-82">过度使用模拟的一个问题是它将你的测试与你的mock的实现相结合,而不是你的实际代码。</span><span class="yiyi-st" id="yiyi-83">假设你有一个实现<code class="docutils literal"><span class="pre">some_method</span></code>的类。</span><span class="yiyi-st" id="yiyi-84">在另一个类的测试中,您提供此对象的模拟,<em>也</em>提供<code class="docutils literal"><span class="pre">some_method</span></code>。</span><span class="yiyi-st" id="yiyi-85">如果以后你重构第一个类,所以它不再有<code class="docutils literal"><span class="pre">some_method</span></code> - 那么你的测试将继续传递,即使你的代码现在破了!</span></p><p><span class="yiyi-st" id="yiyi-86"><a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>允许您使用<em>spec</em>关键字参数提供一个对象作为模拟的规范。</span><span class="yiyi-st" id="yiyi-87">访问模拟上不存在于规范对象上的方法/属性将立即引发属性错误。</span><span class="yiyi-st" id="yiyi-88">如果更改规范的实现,那么使用该类的测试将立即开始失败,而无需在这些测试中实例化类。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">spec</span><span class="o">=</span><span class="n">SomeClass</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">old_method</span><span class="p">()</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="c">...</span>
|
||
<span class="gr">AttributeError</span>: <span class="n">object has no attribute 'old_method'</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-89">使用规范还使得能够更智能地匹配对模拟的调用,而不管某些参数是作为位置参数还是命名参数传递的:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">def</span> <span class="nf">f</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">spec</span><span class="o">=</span><span class="n">f</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
<span class="go"><Mock name='mock()' id='140161580456576'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">a</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">c</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-90">如果您希望此更智能的匹配也可以使用模拟中的方法调用,您可以使用<a class="reference internal" href="unittest.mock.html#auto-speccing"><span>auto-speccing</span></a>。</span></p><p><span class="yiyi-st" id="yiyi-91">如果你想要一个更强大的规范形式阻止任意属性的设置以及获取它们,那么你可以使用<em>spec_set</em>而不是<em>spec</em>。</span></p></div></div><div class="section" id="patch-decorators"><h2><span class="yiyi-st" id="yiyi-92">26.6.2. </span><span class="yiyi-st" id="yiyi-93">Patch Decorators</span></h2><div class="admonition note"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-94">注意</span></p><p class="last"><span class="yiyi-st" id="yiyi-95">使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>这对您在命名空间中的对象进行修补很重要,因为它们被查找。</span><span class="yiyi-st" id="yiyi-96">这通常很简单,但为了快速指南,请阅读<a class="reference internal" href="unittest.mock.html#where-to-patch"><span>where to patch</span></a>。</span></p></div><p><span class="yiyi-st" id="yiyi-97">测试中常见的需求是修补类属性或模块属性,例如修补内置或修补模块中的类以测试它是否被实例化。</span><span class="yiyi-st" id="yiyi-98">模块和类实际上是全局的,因此在测试之后,对它们的修补必须被撤消,或者补丁将持续到其他测试中并且导致难以诊断问题。</span></p><p><span class="yiyi-st" id="yiyi-99">mock为此提供了三个方便的装饰器:<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>,<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.object" title="unittest.mock.patch.object"><code class="xref py py-func docutils literal"><span class="pre">patch.object()</span></code></a>和<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.dict" title="unittest.mock.patch.dict"><code class="xref py py-func docutils literal"><span class="pre">patch.dict()</span></code></a>。</span><span class="yiyi-st" id="yiyi-100"><code class="docutils literal"><span class="pre">patch</span></code>使用单个字符串<code class="docutils literal"><span class="pre">package.module.Class.attribute</span></code>指定要修补的属性。</span><span class="yiyi-st" id="yiyi-101">它也可以选择一个值,你想要的属性(或类或任何)被替换。</span><span class="yiyi-st" id="yiyi-102">'patch.object'接受一个对象和你想要打补丁的属性的名字,加上可选的补丁值。</span></p><p><span class="yiyi-st" id="yiyi-103"><code class="docutils literal"><span class="pre">patch.object</span></code>:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">original</span> <span class="o">=</span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span>
|
||
<span class="gp">>>> </span><span class="nd">@patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">SomeClass</span><span class="p">,</span> <span class="s1">'attribute'</span><span class="p">,</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span><span class="p">)</span>
|
||
<span class="gp">... </span><span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span> <span class="o">==</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">test</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span> <span class="o">==</span> <span class="n">original</span>
|
||
</code></pre><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="nd">@patch</span><span class="p">(</span><span class="s1">'package.module.attribute'</span><span class="p">,</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span><span class="p">)</span>
|
||
<span class="gp">... </span><span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="kn">from</span> <span class="nn">package.module</span> <span class="k">import</span> <span class="n">attribute</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">attribute</span> <span class="ow">is</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">test</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-104">如果您正在修补模块(包括<a class="reference internal" href="builtins.html#module-builtins" title="builtins: The module that provides the built-in namespace."><code class="xref py py-mod docutils literal"><span class="pre">builtins</span></code></a>),请使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>而不是<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.object" title="unittest.mock.patch.object"><code class="xref py py-func docutils literal"><span class="pre">patch.object()</span></code></a></span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="n">sentinel</span><span class="o">.</span><span class="n">file_handle</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'builtins.open'</span><span class="p">,</span> <span class="n">mock</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">handle</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'filename'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="s1">'filename'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">handle</span> <span class="o">==</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">file_handle</span><span class="p">,</span> <span class="s2">"incorrect file handle returned"</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-105">如果需要,模块名称可以是“虚线”,格式为<code class="docutils literal"><span class="pre">package.module</span></code>:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="nd">@patch</span><span class="p">(</span><span class="s1">'package.module.ClassName.attribute'</span><span class="p">,</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span><span class="p">)</span>
|
||
<span class="gp">... </span><span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
|
||
<span class="gp">... </span> <span class="kn">from</span> <span class="nn">package.module</span> <span class="k">import</span> <span class="n">ClassName</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">ClassName</span><span class="o">.</span><span class="n">attribute</span> <span class="o">==</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">test</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-106">一个好的模式是实际装饰测试方法本身:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="nd">@patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">SomeClass</span><span class="p">,</span> <span class="s1">'attribute'</span><span class="p">,</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span><span class="p">,</span> <span class="n">sentinel</span><span class="o">.</span><span class="n">attribute</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">original</span> <span class="o">=</span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_something'</span><span class="p">)</span><span class="o">.</span><span class="n">test_something</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">attribute</span> <span class="o">==</span> <span class="n">original</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-107">如果你想使用Mock进行补丁,你可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>只有一个参数(或<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.object" title="unittest.mock.patch.object"><code class="xref py py-func docutils literal"><span class="pre">patch.object()</span></code></a>有两个参数)。</span><span class="yiyi-st" id="yiyi-108">模拟将为您创建并传递到测试函数/方法:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="nd">@patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">SomeClass</span><span class="p">,</span> <span class="s1">'static_method'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">mock_method</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">SomeClass</span><span class="o">.</span><span class="n">static_method</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="n">mock_method</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_something'</span><span class="p">)</span><span class="o">.</span><span class="n">test_something</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-109">您可以使用此模式堆叠多个修补程序装饰:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="nd">@patch</span><span class="p">(</span><span class="s1">'package.module.ClassName1'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="nd">@patch</span><span class="p">(</span><span class="s1">'package.module.ClassName2'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_something</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">MockClass2</span><span class="p">,</span> <span class="n">MockClass1</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">package</span><span class="o">.</span><span class="n">module</span><span class="o">.</span><span class="n">ClassName1</span><span class="p">,</span> <span class="n">MockClass1</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">package</span><span class="o">.</span><span class="n">module</span><span class="o">.</span><span class="n">ClassName2</span><span class="p">,</span> <span class="n">MockClass2</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_something'</span><span class="p">)</span><span class="o">.</span><span class="n">test_something</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-110">当你嵌套补丁修饰符时,mock会以它们应用的相同顺序传递到修饰函数中(应用修饰符的正常<em>python</em>顺序)。</span><span class="yiyi-st" id="yiyi-111">这意味着从下到上,因此在上面的示例中,首先传递<code class="docutils literal"><span class="pre">test_module.ClassName2</span></code>的模拟。</span></p><p><span class="yiyi-st" id="yiyi-112">还有<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.dict" title="unittest.mock.patch.dict"><code class="xref py py-func docutils literal"><span class="pre">patch.dict()</span></code></a>用于在范围内设置字典中的值,并在测试结束时将字典恢复到其原始状态:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">foo</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'key'</span><span class="p">:</span> <span class="s1">'value'</span><span class="p">}</span>
|
||
<span class="gp">>>> </span><span class="n">original</span> <span class="o">=</span> <span class="n">foo</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">dict</span><span class="p">(</span><span class="n">foo</span><span class="p">,</span> <span class="p">{</span><span class="s1">'newkey'</span><span class="p">:</span> <span class="s1">'newvalue'</span><span class="p">},</span> <span class="n">clear</span><span class="o">=</span><span class="kc">True</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">foo</span> <span class="o">==</span> <span class="p">{</span><span class="s1">'newkey'</span><span class="p">:</span> <span class="s1">'newvalue'</span><span class="p">}</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">foo</span> <span class="o">==</span> <span class="n">original</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-113"><code class="docutils literal"><span class="pre">patch</span></code>,<code class="docutils literal"><span class="pre">patch.object</span></code>和<code class="docutils literal"><span class="pre">patch.dict</span></code>都可以用作上下文管理器。</span></p><p><span class="yiyi-st" id="yiyi-114">在使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>为您创建模拟的地方,可以使用with语句的“as”形式获得对模拟的引用:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">ProductionClass</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">ProductionClass</span><span class="p">,</span> <span class="s1">'method'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_method</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">mock_method</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="kc">None</span>
|
||
<span class="gp">... </span> <span class="n">real</span> <span class="o">=</span> <span class="n">ProductionClass</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="n">real</span><span class="o">.</span><span class="n">method</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock_method</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-115">作为替代<code class="docutils literal"><span class="pre">patch</span></code>,<code class="docutils literal"><span class="pre">patch.object</span></code>和<code class="docutils literal"><span class="pre">patch.dict</span></code>可以用作类装饰器。</span><span class="yiyi-st" id="yiyi-116">当以这种方式使用时,它与将装饰器单独应用于其名称以“测试”开始的每个方法相同。</span></p></div><div class="section" id="further-examples"><h2><span class="yiyi-st" id="yiyi-117">26.6.3. </span><span class="yiyi-st" id="yiyi-118">Further Examples</span></h2><p><span class="yiyi-st" id="yiyi-119">下面是一些稍微更高级的场景的更多示例。</span></p><div class="section" id="mocking-chained-calls"><h3><span class="yiyi-st" id="yiyi-120">26.6.3.1. </span><span class="yiyi-st" id="yiyi-121">Mocking chained calls</span></h3><p><span class="yiyi-st" id="yiyi-122">如果您理解<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.return_value" title="unittest.mock.Mock.return_value"><code class="xref py py-attr docutils literal"><span class="pre">return_value</span></code></a>属性,则模拟链接调用实际上是直接的。</span><span class="yiyi-st" id="yiyi-123">当第一次调用模拟或在调用之前获取<code class="docutils literal"><span class="pre">return_value</span></code>时,会创建一个新的<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>。</span></p><p><span class="yiyi-st" id="yiyi-124">这意味着您可以查看从调用模拟对象返回的对象是否已通过查询<code class="docutils literal"><span class="pre">return_value</span></code> mock使用:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span><span class="o">.</span><span class="n">foo</span><span class="p">(</span><span class="n">a</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
|
||
<span class="go"><Mock name='mock().foo()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">return_value</span><span class="o">.</span><span class="n">foo</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">a</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-125">从这里它是一个简单的步骤配置,然后关于链接调用断言。</span><span class="yiyi-st" id="yiyi-126">当然,另一种选择是,首先以更可测试的方式编写代码...</span></p><p><span class="yiyi-st" id="yiyi-127">所以,假设我们有一些看起来有点像这样的代码:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Something</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">backend</span> <span class="o">=</span> <span class="n">BackendProvider</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">method</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">backend</span><span class="o">.</span><span class="n">get_endpoint</span><span class="p">(</span><span class="s1">'foobar'</span><span class="p">)</span><span class="o">.</span><span class="n">create_call</span><span class="p">(</span><span class="s1">'spam'</span><span class="p">,</span> <span class="s1">'eggs'</span><span class="p">)</span><span class="o">.</span><span class="n">start_call</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="c1"># more code</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-128">假设<code class="docutils literal"><span class="pre">BackendProvider</span></code>已经通过测试,我们如何测试<code class="docutils literal"><span class="pre">method()</span></code>?</span><span class="yiyi-st" id="yiyi-129">具体来说,我们要测试代码部分<code class="docutils literal"><span class="pre">#</span> <span class="pre">更多</span> <span class="pre">代码</span></code>以正确的方式使用响应对象。</span></p><p><span class="yiyi-st" id="yiyi-130">由于这个调用链是从一个实例属性进行的,我们可以在<code class="docutils literal"><span class="pre">Something</span></code>实例上修补<code class="docutils literal"><span class="pre">backend</span></code>属性。</span><span class="yiyi-st" id="yiyi-131">在这种特殊情况下,我们只对最终调用<code class="docutils literal"><span class="pre">start_call</span></code>的返回值感兴趣,所以我们没有太多配置。</span><span class="yiyi-st" id="yiyi-132">让我们假设它返回的对象是“类文件”,因此我们将确保我们的响应对象使用内置<a class="reference internal" href="functions.html#open" title="open"><code class="xref py py-func docutils literal"><span class="pre">open()</span></code></a>作为其<code class="docutils literal"><span class="pre">spec</span></code>。</span></p><p><span class="yiyi-st" id="yiyi-133">为此,我们创建一个mock实例作为我们的模拟后端,并为它创建一个模拟响应对象。</span><span class="yiyi-st" id="yiyi-134">要将响应设置为最终<code class="docutils literal"><span class="pre">start_call</span></code>的返回值,我们可以这样做:</span></p><pre><code class="language-python"><span></span><span class="n">mock_backend</span><span class="o">.</span><span class="n">get_endpoint</span><span class="o">.</span><span class="n">return_value</span><span class="o">.</span><span class="n">create_call</span><span class="o">.</span><span class="n">return_value</span><span class="o">.</span><span class="n">start_call</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">mock_response</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-135">我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.configure_mock" title="unittest.mock.Mock.configure_mock"><code class="xref py py-meth docutils literal"><span class="pre">configure_mock()</span></code></a>方法以更好的方式直接设置我们的返回值:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">something</span> <span class="o">=</span> <span class="n">Something</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock_response</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">spec</span><span class="o">=</span><span class="nb">open</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock_backend</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">config</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'get_endpoint.return_value.create_call.return_value.start_call.return_value'</span><span class="p">:</span> <span class="n">mock_response</span><span class="p">}</span>
|
||
<span class="gp">>>> </span><span class="n">mock_backend</span><span class="o">.</span><span class="n">configure_mock</span><span class="p">(</span><span class="o">**</span><span class="n">config</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-136">有了这些,我们猴子修补“模拟后端”到位,并可以使真正的电话:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">something</span><span class="o">.</span><span class="n">backend</span> <span class="o">=</span> <span class="n">mock_backend</span>
|
||
<span class="gp">>>> </span><span class="n">something</span><span class="o">.</span><span class="n">method</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-137">使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.mock_calls" title="unittest.mock.Mock.mock_calls"><code class="xref py py-attr docutils literal"><span class="pre">mock_calls</span></code></a>我们可以使用单个断言检查链接调用。</span><span class="yiyi-st" id="yiyi-138">链接调用是一行代码中的几个调用,因此在<code class="docutils literal"><span class="pre">mock_calls</span></code>中将有几个条目。</span><span class="yiyi-st" id="yiyi-139">我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.call.call_list" title="unittest.mock.call.call_list"><code class="xref py py-meth docutils literal"><span class="pre">call.call_list()</span></code></a>为我们创建这个调用列表:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">chained</span> <span class="o">=</span> <span class="n">call</span><span class="o">.</span><span class="n">get_endpoint</span><span class="p">(</span><span class="s1">'foobar'</span><span class="p">)</span><span class="o">.</span><span class="n">create_call</span><span class="p">(</span><span class="s1">'spam'</span><span class="p">,</span> <span class="s1">'eggs'</span><span class="p">)</span><span class="o">.</span><span class="n">start_call</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">call_list</span> <span class="o">=</span> <span class="n">chained</span><span class="o">.</span><span class="n">call_list</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">mock_backend</span><span class="o">.</span><span class="n">mock_calls</span> <span class="o">==</span> <span class="n">call_list</span>
|
||
</code></pre></div><div class="section" id="partial-mocking"><h3><span class="yiyi-st" id="yiyi-140">26.6.3.2. </span><span class="yiyi-st" id="yiyi-141">Partial mocking</span></h3><p><span class="yiyi-st" id="yiyi-142">在一些测试中,我想对<a class="reference internal" href="datetime.html#datetime.date.today" title="datetime.date.today"><code class="xref py py-meth docutils literal"><span class="pre">datetime.date.today()</span></code></a>进行一次调用,以返回一个已知的日期,但我不想阻止被测试的代码创建新的日期对象。</span><span class="yiyi-st" id="yiyi-143">不幸的是,<a class="reference internal" href="datetime.html#datetime.date" title="datetime.date"><code class="xref py py-class docutils literal"><span class="pre">datetime.date</span></code></a>是用C写的,所以我不能只是monkey-patch出静态的<code class="xref py py-meth docutils literal"><span class="pre">date.today()</span></code>方法。</span></p><p><span class="yiyi-st" id="yiyi-144">我发现了一个简单的方法来做到这一点,涉及有效地包装日期类与一个嘲笑,但传递到构造函数到真实类(并返回真实的实例)的调用。</span></p><p><span class="yiyi-st" id="yiyi-145">此处使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch</span> <span class="pre">decorator</span></code></a>来模拟测试中的模块中的<code class="docutils literal"><span class="pre">date</span></code>类。</span><span class="yiyi-st" id="yiyi-146">然后,将模拟日期类上的<code class="xref py py-attr docutils literal"><span class="pre">side_effect</span></code>属性设置为返回实际日期的lambda函数。</span><span class="yiyi-st" id="yiyi-147">当模拟日期类被调用时,实际日期将由<code class="docutils literal"><span class="pre">side_effect</span></code>构造并返回。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">datetime</span> <span class="k">import</span> <span class="n">date</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.date'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_date</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">mock_date</span><span class="o">.</span><span class="n">today</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">date</span><span class="p">(</span><span class="mi">2010</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">mock_date</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="k">lambda</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">:</span> <span class="n">date</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kw</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">date</span><span class="o">.</span><span class="n">today</span><span class="p">()</span> <span class="o">==</span> <span class="n">date</span><span class="p">(</span><span class="mi">2010</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">date</span><span class="p">(</span><span class="mi">2009</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span> <span class="o">==</span> <span class="n">date</span><span class="p">(</span><span class="mi">2009</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-148">请注意,我们不会全局修补<a class="reference internal" href="datetime.html#datetime.date" title="datetime.date"><code class="xref py py-class docutils literal"><span class="pre">datetime.date</span></code></a>,我们在<em>使用</em>的模块中修补<code class="docutils literal"><span class="pre">date</span></code>。</span><span class="yiyi-st" id="yiyi-149">请参阅<a class="reference internal" href="unittest.mock.html#where-to-patch"><span>where to patch</span></a>。</span></p><p><span class="yiyi-st" id="yiyi-150">当调用<code class="docutils literal"><span class="pre">date.today()</span></code>时,返回一个已知日期,但对<code class="docutils literal"><span class="pre">date(...)</span></code>构造函数的调用仍会返回正常日期。</span><span class="yiyi-st" id="yiyi-151">没有这个,你可以发现自己必须使用与被测代码完全相同的算法来计算预期结果,这是一个经典的测试反模式。</span></p><p><span class="yiyi-st" id="yiyi-152">对日期构造函数的调用记录在<code class="docutils literal"><span class="pre">mock_date</span></code>属性(<code class="docutils literal"><span class="pre">call_count</span></code>和朋友)中,这对您的测试也很有用。</span></p><p><span class="yiyi-st" id="yiyi-153">在<a class="reference external" href="https://williambert.online/2011/07/how-to-unit-testing-in-django-with-mocking-and-patching/">此博客条目</a>中讨论了处理模拟日期或其他内置类的替代方法。</span></p></div><div class="section" id="mocking-a-generator-method"><h3><span class="yiyi-st" id="yiyi-154">26.6.3.3. </span><span class="yiyi-st" id="yiyi-155">Mocking a Generator Method</span></h3><p><span class="yiyi-st" id="yiyi-156">Python生成器是一个函数或方法,使用<a class="reference internal" href="../reference/simple_stmts.html#yield"><code class="xref std std-keyword docutils literal"><span class="pre">yield</span></code></a>语句在<a class="footnote-reference" href="#id3" id="id2">[1]</a>上迭代时返回一系列值。</span></p><p><span class="yiyi-st" id="yiyi-157">调用生成器方法/函数返回生成器对象。</span><span class="yiyi-st" id="yiyi-158">它是生成器对象,然后迭代。</span><span class="yiyi-st" id="yiyi-159">迭代的协议方法是<a class="reference internal" href="stdtypes.html#container.__iter__" title="container.__iter__"><code class="xref py py-meth docutils literal"><span class="pre">__iter__()</span></code></a>,所以我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.MagicMock" title="unittest.mock.MagicMock"><code class="xref py py-class docutils literal"><span class="pre">MagicMock</span></code></a>来模拟这个。</span></p><p><span class="yiyi-st" id="yiyi-160">这里有一个示例类,其中“iter”方法实现为生成器:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">iter</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]:</span>
|
||
<span class="gp">... </span> <span class="k">yield</span> <span class="n">i</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">foo</span> <span class="o">=</span> <span class="n">Foo</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="nb">list</span><span class="p">(</span><span class="n">foo</span><span class="o">.</span><span class="n">iter</span><span class="p">())</span>
|
||
<span class="go">[1, 2, 3]</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-161">我们如何模拟这个类,特别是它的“iter”方法?</span></p><p><span class="yiyi-st" id="yiyi-162">要配置从迭代(隐含在<a class="reference internal" href="stdtypes.html#list" title="list"><code class="xref py py-class docutils literal"><span class="pre">list</span></code></a>的调用中)返回的值,我们需要配置由调用<code class="docutils literal"><span class="pre">foo.iter()</span></code>返回的对象。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock_foo</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock_foo</span><span class="o">.</span><span class="n">iter</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="nb">iter</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">])</span>
|
||
<span class="gp">>>> </span><span class="nb">list</span><span class="p">(</span><span class="n">mock_foo</span><span class="o">.</span><span class="n">iter</span><span class="p">())</span>
|
||
<span class="go">[1, 2, 3]</span>
|
||
</code></pre><table class="docutils footnote" frame="void" id="id3" rules="none"><tbody valign="top"><tr><td class="label"><span class="yiyi-st" id="yiyi-163"><a class="fn-backref" href="#id2">[1]</a></span></td><td><span class="yiyi-st" id="yiyi-164">还有生成器表达式和生成器的更多<a class="reference external" href="http://www.dabeaz.com/coroutines/index.html">高级用法</a>,但我们在这里不关心它们。</span><span class="yiyi-st" id="yiyi-165">对生成器非常好的介绍以及它们的强大功能是:<a class="reference external" href="http://www.dabeaz.com/generators/">生成器对系统程序员的技巧</a>。</span></td></tr></tbody></table></div><div class="section" id="applying-the-same-patch-to-every-test-method"><h3><span class="yiyi-st" id="yiyi-166">26.6.3.4. </span><span class="yiyi-st" id="yiyi-167">Applying the same patch to every test method</span></h3><p><span class="yiyi-st" id="yiyi-168">如果你想要多个测试方法的地方的几个补丁,明显的方法是应用补丁修饰符到每个方法。</span><span class="yiyi-st" id="yiyi-169">这可以感觉不必要的重复。</span><span class="yiyi-st" id="yiyi-170">对于Python 2.6或更高版本,你可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>(在所有的各种形式)作为类装饰器。</span><span class="yiyi-st" id="yiyi-171">这将修补程序应用于类上的所有测试方法。</span><span class="yiyi-st" id="yiyi-172">测试方法由名称以<code class="docutils literal"><span class="pre">test</span></code>开头的方法标识:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="nd">@patch</span><span class="p">(</span><span class="s1">'mymodule.SomeClass'</span><span class="p">)</span>
|
||
<span class="gp">... </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_one</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">MockSomeClass</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">mymodule</span><span class="o">.</span><span class="n">SomeClass</span><span class="p">,</span> <span class="n">MockSomeClass</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_two</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">MockSomeClass</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">mymodule</span><span class="o">.</span><span class="n">SomeClass</span><span class="p">,</span> <span class="n">MockSomeClass</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">not_a_test</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="s1">'something'</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_one'</span><span class="p">)</span><span class="o">.</span><span class="n">test_one</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_two'</span><span class="p">)</span><span class="o">.</span><span class="n">test_two</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_two'</span><span class="p">)</span><span class="o">.</span><span class="n">not_a_test</span><span class="p">()</span>
|
||
<span class="go">'something'</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-173">管理修补程序的另一种方法是使用<a class="reference internal" href="unittest.mock.html#start-and-stop"><span>patch methods: start and stop</span></a>。</span><span class="yiyi-st" id="yiyi-174">这些允许您将修补移动到<code class="docutils literal"><span class="pre">setUp</span></code>和<code class="docutils literal"><span class="pre">tearDown</span></code>方法中。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span> <span class="o">=</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.foo'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">mock_foo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">mymodule</span><span class="o">.</span><span class="n">foo</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">mock_foo</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">tearDown</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">patcher</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_foo'</span><span class="p">)</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-175">如果您使用此技术,您必须通过调用<code class="docutils literal"><span class="pre">stop</span></code>确保修补是“撤消”。</span><span class="yiyi-st" id="yiyi-176">这可能比你想象的更加微弱,因为如果在setUp中引发异常,那么tearDown不会被调用。</span><span class="yiyi-st" id="yiyi-177"><a class="reference internal" href="unittest.html#unittest.TestCase.addCleanup" title="unittest.TestCase.addCleanup"><code class="xref py py-meth docutils literal"><span class="pre">unittest.TestCase.addCleanup()</span></code></a>让这更容易:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">patcher</span> <span class="o">=</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.foo'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">addCleanup</span><span class="p">(</span><span class="n">patcher</span><span class="o">.</span><span class="n">stop</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">mock_foo</span> <span class="o">=</span> <span class="n">patcher</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">assertIs</span><span class="p">(</span><span class="n">mymodule</span><span class="o">.</span><span class="n">foo</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">mock_foo</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_foo'</span><span class="p">)</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||
</code></pre></div><div class="section" id="mocking-unbound-methods"><h3><span class="yiyi-st" id="yiyi-178">26.6.3.5. </span><span class="yiyi-st" id="yiyi-179">Mocking Unbound Methods</span></h3><p><span class="yiyi-st" id="yiyi-180">今天写测试时,我需要修补<em>未绑定的方法</em>(在类上修补方法,而不是实例)。</span><span class="yiyi-st" id="yiyi-181">我需要self作为第一个参数传递,因为我想断言关于哪些对象调用这个特定的方法。</span><span class="yiyi-st" id="yiyi-182">问题是,你不能用一个嘲笑补丁,因为如果你用一个模拟替换一个未绑定的方法,它不会成为一个绑定的方法,当从实例获取,所以它不会自我传递。</span><span class="yiyi-st" id="yiyi-183">解决方法是使用实函数修补未绑定的方法。</span><span class="yiyi-st" id="yiyi-184"><a class="reference internal" href="unittest.mock.html#unittest.mock.patch" title="unittest.mock.patch"><code class="xref py py-func docutils literal"><span class="pre">patch()</span></code></a>装饰器使得使用模拟来修改方法变得如此简单,以至于必须创建一个真实的函数变成一个讨厌的东西。</span></p><p><span class="yiyi-st" id="yiyi-185">如果你将<code class="docutils literal"><span class="pre">autospec=True</span></code>传递给patch,那么它会使用<em>真实</em>函数对象进行修补。</span><span class="yiyi-st" id="yiyi-186">此函数对象具有与其正在替换的声明相同的声明,但委派给在引擎盖下的模拟。</span><span class="yiyi-st" id="yiyi-187">你仍然得到你的模拟自动创建完全相同的方式,以前。</span><span class="yiyi-st" id="yiyi-188">它的意思是,如果你使用它来修补一个类的未绑定的方法,mocked函数将变成一个绑定的方法,如果它是从实例获取。</span><span class="yiyi-st" id="yiyi-189">它会将<code class="docutils literal"><span class="pre">self</span></code>作为第一个参数传入,这正是我想要的:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">pass</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">object</span><span class="p">(</span><span class="n">Foo</span><span class="p">,</span> <span class="s1">'foo'</span><span class="p">,</span> <span class="n">autospec</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_foo</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">mock_foo</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="s1">'foo'</span>
|
||
<span class="gp">... </span> <span class="n">foo</span> <span class="o">=</span> <span class="n">Foo</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="n">foo</span><span class="o">.</span><span class="n">foo</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="go">'foo'</span>
|
||
<span class="gp">>>> </span><span class="n">mock_foo</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">(</span><span class="n">foo</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-190">如果我们不使用<code class="docutils literal"><span class="pre">autospec=True</span></code>,那么未绑定的方法将使用Mock实例进行修补,而不是使用<code class="docutils literal"><span class="pre">self</span></code>来调用。</span></p></div><div class="section" id="checking-multiple-calls-with-mock"><h3><span class="yiyi-st" id="yiyi-191">26.6.3.6. </span><span class="yiyi-st" id="yiyi-192">Checking multiple calls with mock</span></h3><p><span class="yiyi-st" id="yiyi-193">mock有一个很好的API来断言你的模拟对象是如何使用的。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="kc">None</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="p">(</span><span class="s1">'baz'</span><span class="p">,</span> <span class="n">spam</span><span class="o">=</span><span class="s1">'eggs'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="s1">'baz'</span><span class="p">,</span> <span class="n">spam</span><span class="o">=</span><span class="s1">'eggs'</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-194">如果你的模拟只被调用一次,你可以使用<code class="xref py py-meth docutils literal"><span class="pre">assert_called_once_with()</span></code>方法,也断言<code class="xref py py-attr docutils literal"><span class="pre">call_count</span></code>是一个。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">(</span><span class="s1">'baz'</span><span class="p">,</span> <span class="n">spam</span><span class="o">=</span><span class="s1">'eggs'</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">foo_bar</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">(</span><span class="s1">'baz'</span><span class="p">,</span> <span class="n">spam</span><span class="o">=</span><span class="s1">'eggs'</span><span class="p">)</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>: <span class="n">Expected to be called once. Called 2 times.</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-195"><code class="docutils literal"><span class="pre">assert_called_with</span></code>和<code class="docutils literal"><span class="pre">assert_called_once_with</span></code>都会对<em>最近的</em>调用进行断言。</span><span class="yiyi-st" id="yiyi-196">如果你的模拟要被多次调用,并且你想对<em>所有</em>进行断言,那么你可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.call_args_list" title="unittest.mock.Mock.call_args_list"><code class="xref py py-attr docutils literal"><span class="pre">call_args_list</span></code></a>:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">call_args_list</span>
|
||
<span class="go">[call(1, 2, 3), call(4, 5, 6), call()]</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-197"><a class="reference internal" href="unittest.mock.html#unittest.mock.call" title="unittest.mock.call"><code class="xref py py-data docutils literal"><span class="pre">call</span></code></a>助手可以轻松地对这些调用进行断言。</span><span class="yiyi-st" id="yiyi-198">您可以构建预期调用的列表,并将其与<code class="docutils literal"><span class="pre">call_args_list</span></code>进行比较。</span><span class="yiyi-st" id="yiyi-199">这看起来非常类似于<code class="docutils literal"><span class="pre">call_args_list</span></code>的repr:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">expected</span> <span class="o">=</span> <span class="p">[</span><span class="n">call</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">call</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">),</span> <span class="n">call</span><span class="p">()]</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">call_args_list</span> <span class="o">==</span> <span class="n">expected</span>
|
||
<span class="go">True</span>
|
||
</code></pre></div><div class="section" id="coping-with-mutable-arguments"><h3><span class="yiyi-st" id="yiyi-200">26.6.3.7. </span><span class="yiyi-st" id="yiyi-201">Coping with mutable arguments</span></h3><p><span class="yiyi-st" id="yiyi-202">另一种情况是罕见的,但可以咬你,是当你的模拟被调用与可变参数。</span><span class="yiyi-st" id="yiyi-203"><code class="docutils literal"><span class="pre">call_args</span></code>和<code class="docutils literal"><span class="pre">call_args_list</span></code>将<em>引用存储到参数</em>。</span><span class="yiyi-st" id="yiyi-204">如果参数被测试中的代码突变,那么你不能再对模拟调用时的值进行断言。</span></p><p><span class="yiyi-st" id="yiyi-205">下面是一些显示问题的示例代码。</span><span class="yiyi-st" id="yiyi-206">想象一下'mymodule'中定义的以下函数:</span></p><pre><code class="language-python"><span></span><span class="k">def</span> <span class="nf">frob</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
|
||
<span class="k">pass</span>
|
||
|
||
<span class="k">def</span> <span class="nf">grob</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
|
||
<span class="s2">"First frob and then clear val"</span>
|
||
<span class="n">frob</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
|
||
<span class="n">val</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-207">当我们尝试测试<code class="docutils literal"><span class="pre">grob</span></code>使用正确的参数调用<code class="docutils literal"><span class="pre">frob</span></code>时会发生什么:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.frob'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_frob</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">val</span> <span class="o">=</span> <span class="p">{</span><span class="mi">6</span><span class="p">}</span>
|
||
<span class="gp">... </span> <span class="n">mymodule</span><span class="o">.</span><span class="n">grob</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">val</span>
|
||
<span class="go">set()</span>
|
||
<span class="gp">>>> </span><span class="n">mock_frob</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">({</span><span class="mi">6</span><span class="p">})</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>: <span class="n">Expected: (({6},), {})</span>
|
||
<span class="go">Called with: ((set(),), {})</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-208">一种可能性是模拟复制你传递的参数。</span><span class="yiyi-st" id="yiyi-209">这可能会导致问题,如果你做依赖对象标识相等的断言。</span></p><p><span class="yiyi-st" id="yiyi-210">这里有一个使用<code class="xref py py-attr docutils literal"><span class="pre">side_effect</span></code>功能的解决方案。</span><span class="yiyi-st" id="yiyi-211">如果为模拟提供<code class="docutils literal"><span class="pre">side_effect</span></code>函数,那么<code class="docutils literal"><span class="pre">side_effect</span></code>将使用与模拟相同的参数调用。</span><span class="yiyi-st" id="yiyi-212">这使我们有机会复制参数并存储它们以供以后的断言。</span><span class="yiyi-st" id="yiyi-213">在这个例子中,我使用<em>另一个</em> mock来存储参数,以便我可以使用mock方法进行断言。</span><span class="yiyi-st" id="yiyi-214">再次一个帮助函数设置了这个为我。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">copy</span> <span class="k">import</span> <span class="n">deepcopy</span>
|
||
<span class="gp">>>> </span><span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="k">import</span> <span class="n">Mock</span><span class="p">,</span> <span class="n">patch</span><span class="p">,</span> <span class="n">DEFAULT</span>
|
||
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">copy_call_args</span><span class="p">(</span><span class="n">mock</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">new_mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">side_effect</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">args</span> <span class="o">=</span> <span class="n">deepcopy</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">kwargs</span> <span class="o">=</span> <span class="n">deepcopy</span><span class="p">(</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">new_mock</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">DEFAULT</span>
|
||
<span class="gp">... </span> <span class="n">mock</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">side_effect</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">new_mock</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.frob'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_frob</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">new_mock</span> <span class="o">=</span> <span class="n">copy_call_args</span><span class="p">(</span><span class="n">mock_frob</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">val</span> <span class="o">=</span> <span class="p">{</span><span class="mi">6</span><span class="p">}</span>
|
||
<span class="gp">... </span> <span class="n">mymodule</span><span class="o">.</span><span class="n">grob</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">new_mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">({</span><span class="mi">6</span><span class="p">})</span>
|
||
<span class="gp">>>> </span><span class="n">new_mock</span><span class="o">.</span><span class="n">call_args</span>
|
||
<span class="go">call({6})</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-215"><code class="docutils literal"><span class="pre">copy_call_args</span></code>使用将被调用的模拟来调用。</span><span class="yiyi-st" id="yiyi-216">它返回一个新的模拟,我们做断言。</span><span class="yiyi-st" id="yiyi-217"><code class="docutils literal"><span class="pre">side_effect</span></code>函数创建args的副本,并使用副本调用<code class="docutils literal"><span class="pre">new_mock</span></code>。</span></p><div class="admonition note"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-218">注意</span></p><p><span class="yiyi-st" id="yiyi-219">如果你的模拟只会被使用一次,有一个更简单的方法检查参数在它们被称为的点。</span><span class="yiyi-st" id="yiyi-220">您可以简单地在<code class="docutils literal"><span class="pre">side_effect</span></code>函数内进行检查。</span></p><div class="last highlight-python3"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="k">def</span> <span class="nf">side_effect</span><span class="p">(</span><span class="n">arg</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">arg</span> <span class="o">==</span> <span class="p">{</span><span class="mi">6</span><span class="p">}</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">side_effect</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">({</span><span class="mi">6</span><span class="p">})</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="nb">set</span><span class="p">())</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>
|
||
</pre></div></div></div><p><span class="yiyi-st" id="yiyi-221">另一种方法是创建<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>或<a class="reference internal" href="unittest.mock.html#unittest.mock.MagicMock" title="unittest.mock.MagicMock"><code class="xref py py-class docutils literal"><span class="pre">MagicMock</span></code></a>的子类(使用<a class="reference internal" href="copy.html#copy.deepcopy" title="copy.deepcopy"><code class="xref py py-func docutils literal"><span class="pre">copy.deepcopy()</span></code></a>)复制参数。</span><span class="yiyi-st" id="yiyi-222">这里有一个示例实现:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="kn">from</span> <span class="nn">copy</span> <span class="k">import</span> <span class="n">deepcopy</span>
|
||
<span class="gp">>>> </span><span class="k">class</span> <span class="nc">CopyingMock</span><span class="p">(</span><span class="n">MagicMock</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">args</span> <span class="o">=</span> <span class="n">deepcopy</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">kwargs</span> <span class="o">=</span> <span class="n">deepcopy</span><span class="p">(</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">CopyingMock</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">__call__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">c</span> <span class="o">=</span> <span class="n">CopyingMock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">arg</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">c</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">arg</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">c</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="nb">set</span><span class="p">())</span>
|
||
<span class="gp">>>> </span><span class="n">c</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">arg</span><span class="p">)</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>: <span class="n">Expected call: mock({1})</span>
|
||
<span class="go">Actual call: mock(set())</span>
|
||
<span class="gp">>>> </span><span class="n">c</span><span class="o">.</span><span class="n">foo</span>
|
||
<span class="go"><CopyingMock name='mock.foo' id='...'></span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-223">当您将<code class="docutils literal"><span class="pre">Mock</span></code>或<code class="docutils literal"><span class="pre">MagicMock</span></code>作为子类时,所有动态创建的属性和<code class="docutils literal"><span class="pre">return_value</span></code>将自动使用您的子类。</span><span class="yiyi-st" id="yiyi-224">这意味着<code class="docutils literal"><span class="pre">CopyingMock</span></code>的所有孩子也将具有类型<code class="docutils literal"><span class="pre">CopyingMock</span></code>。</span></p></div><div class="section" id="nesting-patches"><h3><span class="yiyi-st" id="yiyi-225">26.6.3.8. </span><span class="yiyi-st" id="yiyi-226">Nesting Patches</span></h3><p><span class="yiyi-st" id="yiyi-227">使用补丁作为上下文管理器是很好的,但如果你做了多个补丁,你可以结束嵌套语句进一步向右缩进:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.Foo'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_foo</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.Bar'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_bar</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.Spam'</span><span class="p">)</span> <span class="k">as</span> <span class="n">mock_spam</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span> <span class="ow">is</span> <span class="n">mock_foo</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Bar</span> <span class="ow">is</span> <span class="n">mock_bar</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Spam</span> <span class="ow">is</span> <span class="n">mock_spam</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">original</span> <span class="o">=</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_foo'</span><span class="p">)</span><span class="o">.</span><span class="n">test_foo</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span> <span class="ow">is</span> <span class="n">original</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-228">使用unittest <code class="docutils literal"><span class="pre">cleanup</span></code>函数和<a class="reference internal" href="unittest.mock.html#start-and-stop"><span>patch methods: start and stop</span></a>,我们可以实现相同的效果,没有嵌套的缩进。</span><span class="yiyi-st" id="yiyi-229">一个简单的帮助方法,<code class="docutils literal"><span class="pre">create_patch</span></code>,将补丁放在原处,并返回为我们创建的模拟:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyTest</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">create_patch</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">patcher</span> <span class="o">=</span> <span class="n">patch</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">thing</span> <span class="o">=</span> <span class="n">patcher</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">addCleanup</span><span class="p">(</span><span class="n">patcher</span><span class="o">.</span><span class="n">stop</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">thing</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">test_foo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">mock_foo</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">create_patch</span><span class="p">(</span><span class="s1">'mymodule.Foo'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">mock_bar</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">create_patch</span><span class="p">(</span><span class="s1">'mymodule.Bar'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">mock_spam</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">create_patch</span><span class="p">(</span><span class="s1">'mymodule.Spam'</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span> <span class="ow">is</span> <span class="n">mock_foo</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Bar</span> <span class="ow">is</span> <span class="n">mock_bar</span>
|
||
<span class="gp">... </span> <span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Spam</span> <span class="ow">is</span> <span class="n">mock_spam</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">original</span> <span class="o">=</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span>
|
||
<span class="gp">>>> </span><span class="n">MyTest</span><span class="p">(</span><span class="s1">'test_foo'</span><span class="p">)</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="n">mymodule</span><span class="o">.</span><span class="n">Foo</span> <span class="ow">is</span> <span class="n">original</span>
|
||
</code></pre></div><div class="section" id="mocking-a-dictionary-with-magicmock"><h3><span class="yiyi-st" id="yiyi-230">26.6.3.9. </span><span class="yiyi-st" id="yiyi-231">Mocking a dictionary with MagicMock</span></h3><p><span class="yiyi-st" id="yiyi-232">你可能想要模拟一个字典或其他容器对象,记录对它的所有访问,同时它仍然表现得像字典。</span></p><p><span class="yiyi-st" id="yiyi-233">我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.MagicMock" title="unittest.mock.MagicMock"><code class="xref py py-class docutils literal"><span class="pre">MagicMock</span></code></a>来做到这一点,它将像字典一样运行,并使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.side_effect" title="unittest.mock.Mock.side_effect"><code class="xref py py-data docutils literal"><span class="pre">side_effect</span></code></a>将字典访问委托给我们控制的真正的底层字典。</span></p><p><span class="yiyi-st" id="yiyi-234">当我们的<code class="docutils literal"><span class="pre">MagicMock</span></code>的<a class="reference internal" href="../reference/datamodel.html#object.__getitem__" title="object.__getitem__"><code class="xref py py-meth docutils literal"><span class="pre">__getitem__()</span></code></a>和<a class="reference internal" href="../reference/datamodel.html#object.__setitem__" title="object.__setitem__"><code class="xref py py-meth docutils literal"><span class="pre">__setitem__()</span></code></a>方法被调用(正常的字典访问),<code class="docutils literal"><span class="pre">side_effect</span></code>用键(在<code class="docutils literal"><span class="pre">__setitem__</span></code>的值的情况下)调用。</span><span class="yiyi-st" id="yiyi-235">我们还可以控制返回的内容。</span></p><p><span class="yiyi-st" id="yiyi-236">在使用<code class="docutils literal"><span class="pre">MagicMock</span></code>之后,我们可以使用像<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.call_args_list" title="unittest.mock.Mock.call_args_list"><code class="xref py py-data docutils literal"><span class="pre">call_args_list</span></code></a>这样的属性来断言字典是如何使用的:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">my_dict</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'a'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="s1">'b'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="s1">'c'</span><span class="p">:</span> <span class="mi">3</span><span class="p">}</span>
|
||
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">getitem</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">my_dict</span><span class="p">[</span><span class="n">name</span><span class="p">]</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">setitem</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">val</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="n">my_dict</span><span class="p">[</span><span class="n">name</span><span class="p">]</span> <span class="o">=</span> <span class="n">val</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__getitem__</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">getitem</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__setitem__</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">setitem</span>
|
||
</code></pre><div class="admonition note"><p class="first admonition-title"><span class="yiyi-st" id="yiyi-237">注意</span></p><p><span class="yiyi-st" id="yiyi-238">使用<code class="docutils literal"><span class="pre">MagicMock</span></code>的替代方法是使用<code class="docutils literal"><span class="pre">Mock</span></code>和<em>仅提供您特别想要的魔法方法:</em></span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__getitem__</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">getitem</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__setitem__</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">setitem</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-239"><em>第三选项是使用<code class="docutils literal"><span class="pre">MagicMock</span></code>,但将<code class="docutils literal"><span class="pre">dict</span></code>作为<em>spec</em>(或<em>spec_set )参数,以便<code class="docutils literal"><span class="pre">MagicMock</span></code>创建只有字典魔法方法可用:</em></em></span></p><div class="last highlight-python3"><div class="highlight"><pre><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">(</span><span class="n">spec_set</span><span class="o">=</span><span class="nb">dict</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__getitem__</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">getitem</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__setitem__</span><span class="o">.</span><span class="n">side_effect</span> <span class="o">=</span> <span class="n">setitem</span>
|
||
</pre></div></div></div><p><span class="yiyi-st" id="yiyi-240">使用这些副作用函数,<code class="docutils literal"><span class="pre">mock</span></code>将像正常的字典,但记录访问。</span><span class="yiyi-st" id="yiyi-241">如果您尝试访问不存在的键,它甚至会引发<a class="reference internal" href="exceptions.html#KeyError" title="KeyError"><code class="xref py py-exc docutils literal"><span class="pre">KeyError</span></code></a>。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'a'</span><span class="p">]</span>
|
||
<span class="go">1</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'c'</span><span class="p">]</span>
|
||
<span class="go">3</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'d'</span><span class="p">]</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">KeyError</span>: <span class="n">'d'</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'b'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'fish'</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'d'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'eggs'</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'b'</span><span class="p">]</span>
|
||
<span class="go">'fish'</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">[</span><span class="s1">'d'</span><span class="p">]</span>
|
||
<span class="go">'eggs'</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-242">在它被使用后,你可以使用正常的模拟方法和属性来断言访问:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__getitem__</span><span class="o">.</span><span class="n">call_args_list</span>
|
||
<span class="go">[call('a'), call('c'), call('d'), call('b'), call('d')]</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">__setitem__</span><span class="o">.</span><span class="n">call_args_list</span>
|
||
<span class="go">[call('b', 'fish'), call('d', 'eggs')]</span>
|
||
<span class="gp">>>> </span><span class="n">my_dict</span>
|
||
<span class="go">{'a': 1, 'c': 3, 'b': 'fish', 'd': 'eggs'}</span>
|
||
</code></pre></div><div class="section" id="mock-subclasses-and-their-attributes"><h3><span class="yiyi-st" id="yiyi-243">26.6.3.10. </span><span class="yiyi-st" id="yiyi-244">Mock subclasses and their attributes</span></h3><p><span class="yiyi-st" id="yiyi-245">您可能想要将<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>子类化的原因有很多种。</span><span class="yiyi-st" id="yiyi-246">一个原因可能是添加助手方法。</span><span class="yiyi-st" id="yiyi-247">这是一个愚蠢的例子:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">MyMock</span><span class="p">(</span><span class="n">MagicMock</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">has_been_called</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">called</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span> <span class="o">=</span> <span class="n">MyMock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span>
|
||
<span class="go"><MyMock id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">has_been_called</span><span class="p">()</span>
|
||
<span class="go">False</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">has_been_called</span><span class="p">()</span>
|
||
<span class="go">True</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-248"><code class="docutils literal"><span class="pre">Mock</span></code>实例的标准行为是属性和返回值嘲笑的类型与其访问的模拟类型相同。</span><span class="yiyi-st" id="yiyi-249">This ensures that <code class="docutils literal"><span class="pre">Mock</span></code> attributes are <code class="docutils literal"><span class="pre">Mocks</span></code> and <code class="docutils literal"><span class="pre">MagicMock</span></code> attributes are <code class="docutils literal"><span class="pre">MagicMocks</span></code> <a class="footnote-reference" href="#id5" id="id4">[2]</a>. </span><span class="yiyi-st" id="yiyi-250">因此,如果你是子类化添加助手方法,那么他们也将可用于您的子类的实例的属性和返回值模拟。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span>
|
||
<span class="go"><MyMock name='mock.foo' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span><span class="o">.</span><span class="n">has_been_called</span><span class="p">()</span>
|
||
<span class="go">False</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span><span class="p">()</span>
|
||
<span class="go"><MyMock name='mock.foo()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span><span class="o">.</span><span class="n">has_been_called</span><span class="p">()</span>
|
||
<span class="go">True</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-251">有时这是不方便的。</span><span class="yiyi-st" id="yiyi-252">例如,<a class="reference external" href="https://code.google.com/p/mock/issues/detail?id=105">一个用户</a>是对模拟进行子类化以创建<a class="reference external" href="https://twistedmatrix.com/documents/11.0.0/api/twisted.python.components.html">扭曲适配器</a>。</span><span class="yiyi-st" id="yiyi-253">将此应用于属性实际上导致错误。</span></p><p><span class="yiyi-st" id="yiyi-254"><code class="docutils literal"><span class="pre">Mock</span></code>(在其所有类型中)使用一个名为<code class="docutils literal"><span class="pre">_get_child_mock</span></code>的方法为属性和返回值创建这些“子模型”。</span><span class="yiyi-st" id="yiyi-255">您可以通过覆盖此方法来阻止您的子类用于属性。</span><span class="yiyi-st" id="yiyi-256">声明是,它需要任意的关键字参数(<code class="docutils literal"><span class="pre">**kwargs</span></code>),然后传递到模拟构造函数:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Subclass</span><span class="p">(</span><span class="n">MagicMock</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">_get_child_mock</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="n">MagicMock</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span> <span class="o">=</span> <span class="n">Subclass</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span>
|
||
<span class="go"><MagicMock name='mock.foo' id='...'></span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">mymock</span><span class="p">,</span> <span class="n">Subclass</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">mymock</span><span class="o">.</span><span class="n">foo</span><span class="p">,</span> <span class="n">Subclass</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="ow">not</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">mymock</span><span class="p">(),</span> <span class="n">Subclass</span><span class="p">)</span>
|
||
</code></pre><table class="docutils footnote" frame="void" id="id5" rules="none"><tbody valign="top"><tr><td class="label"><span class="yiyi-st" id="yiyi-257"><a class="fn-backref" href="#id4">[2]</a></span></td><td><span class="yiyi-st" id="yiyi-258">这个规则的一个例外是不可调用的模拟。</span><span class="yiyi-st" id="yiyi-259">属性使用可调用变体,因为否则不可调用的mock不能有可调用的方法。</span></td></tr></tbody></table></div><div class="section" id="mocking-imports-with-patch-dict"><h3><span class="yiyi-st" id="yiyi-260">26.6.3.11. </span><span class="yiyi-st" id="yiyi-261">Mocking imports with patch.dict</span></h3><p><span class="yiyi-st" id="yiyi-262">一种情况是嘲笑可能很困难,你有一个函数内部的本地导入。</span><span class="yiyi-st" id="yiyi-263">这些更难以模拟,因为他们没有使用我们可以修补的模块命名空间中的对象。</span></p><p><span class="yiyi-st" id="yiyi-264">一般来说,应避免地方进口。</span><span class="yiyi-st" id="yiyi-265">它们有时是为了防止循环依赖,通常<em></em>是解决问题(重构代码)或者通过延迟导入来防止“前期成本”的更好的方法。</span><span class="yiyi-st" id="yiyi-266">这也可以以比无条件的本地导入(将模块存储为类或模块属性,并且仅在首次使用时导入)更好的方式来解决。</span></p><p><span class="yiyi-st" id="yiyi-267">除此之外,有一种方法使用<code class="docutils literal"><span class="pre">mock</span></code>来影响导入的结果。</span><span class="yiyi-st" id="yiyi-268">导入从<a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal"><span class="pre">sys.modules</span></code></a>字典中提取<em>对象</em>。</span><span class="yiyi-st" id="yiyi-269">请注意,它获取一个<em>对象</em>,这不需要是一个模块。</span><span class="yiyi-st" id="yiyi-270">首次导入模块会导致将模块对象放在<cite>sys.modules</cite>中,因此通常在导入某个模块时,您会得到一个模块。</span><span class="yiyi-st" id="yiyi-271">然而,这不是必须的。</span></p><p><span class="yiyi-st" id="yiyi-272">这意味着您可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.patch.dict" title="unittest.mock.patch.dict"><code class="xref py py-func docutils literal"><span class="pre">patch.dict()</span></code></a>到<em>暂时</em>在<a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal"><span class="pre">sys.modules</span></code></a>中放置模拟。</span><span class="yiyi-st" id="yiyi-273">在此补丁处于活动状态时的任何导入将获取模拟。</span><span class="yiyi-st" id="yiyi-274">当补丁完成时(装饰函数退出,with语句体完成或调用<code class="docutils literal"><span class="pre">patcher.stop()</span></code>),那么以前的任何内容都将被安全地恢复。</span></p><p><span class="yiyi-st" id="yiyi-275">下面是一个模拟“fooble”模块的例子。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">dict</span><span class="p">(</span><span class="s1">'sys.modules'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'fooble'</span><span class="p">:</span> <span class="n">mock</span><span class="p">}):</span>
|
||
<span class="gp">... </span> <span class="kn">import</span> <span class="nn">fooble</span>
|
||
<span class="gp">... </span> <span class="n">fooble</span><span class="o">.</span><span class="n">blob</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="go"><Mock name='mock.blob()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="k">assert</span> <span class="s1">'fooble'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">blob</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-276">正如你可以看到<code class="docutils literal"><span class="pre">import</span> <span class="pre">fooble</span></code>成功,但是在退出时,在<a class="reference internal" href="sys.html#sys.modules" title="sys.modules"><code class="xref py py-data docutils literal"><span class="pre">sys.modules</span></code></a></span></p><p><span class="yiyi-st" id="yiyi-277">这也适用于<code class="docutils literal"><span class="pre">从</span> <span class="pre">模块</span> <span class="pre">导入</span> <span class="pre">名称</span></code></span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">dict</span><span class="p">(</span><span class="s1">'sys.modules'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'fooble'</span><span class="p">:</span> <span class="n">mock</span><span class="p">}):</span>
|
||
<span class="gp">... </span> <span class="kn">from</span> <span class="nn">fooble</span> <span class="k">import</span> <span class="n">blob</span>
|
||
<span class="gp">... </span> <span class="n">blob</span><span class="o">.</span><span class="n">blip</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="go"><Mock name='mock.blob.blip()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">blob</span><span class="o">.</span><span class="n">blip</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">()</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-278">有了更多的工作,你也可以模拟包导入:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">modules</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'package'</span><span class="p">:</span> <span class="n">mock</span><span class="p">,</span> <span class="s1">'package.module'</span><span class="p">:</span> <span class="n">mock</span><span class="o">.</span><span class="n">module</span><span class="p">}</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="o">.</span><span class="n">dict</span><span class="p">(</span><span class="s1">'sys.modules'</span><span class="p">,</span> <span class="n">modules</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="kn">from</span> <span class="nn">package.module</span> <span class="k">import</span> <span class="n">fooble</span>
|
||
<span class="gp">... </span> <span class="n">fooble</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="go"><Mock name='mock.module.fooble()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">module</span><span class="o">.</span><span class="n">fooble</span><span class="o">.</span><span class="n">assert_called_once_with</span><span class="p">()</span>
|
||
</code></pre></div><div class="section" id="tracking-order-of-calls-and-less-verbose-call-assertions"><h3><span class="yiyi-st" id="yiyi-279">26.6.3.12. </span><span class="yiyi-st" id="yiyi-280">Tracking order of calls and less verbose call assertions</span></h3><p><span class="yiyi-st" id="yiyi-281"><a class="reference internal" href="unittest.mock.html#unittest.mock.Mock" title="unittest.mock.Mock"><code class="xref py py-class docutils literal"><span class="pre">Mock</span></code></a>类允许您通过<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.method_calls" title="unittest.mock.Mock.method_calls"><code class="xref py py-attr docutils literal"><span class="pre">method_calls</span></code></a>属性跟踪模拟对象上的方法调用的<em>顺序</em>。</span><span class="yiyi-st" id="yiyi-282">这不允许您跟踪单独的模拟对象之间的调用顺序,但是我们可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.mock_calls" title="unittest.mock.Mock.mock_calls"><code class="xref py py-attr docutils literal"><span class="pre">mock_calls</span></code></a>来实现相同的效果。</span></p><p><span class="yiyi-st" id="yiyi-283">因为mock跟踪在<code class="docutils literal"><span class="pre">mock_calls</span></code>中对子模型的调用,并且访问模拟的任意属性创建了一个子模型,我们可以从父模型创建单独的模拟。</span><span class="yiyi-st" id="yiyi-284">然后,将对所有子模拟的调用都按顺序记录在父级的<code class="docutils literal"><span class="pre">mock_calls</span></code>中:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">manager</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">mock_foo</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">foo</span>
|
||
<span class="gp">>>> </span><span class="n">mock_bar</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="n">bar</span>
|
||
</code></pre><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">mock_foo</span><span class="o">.</span><span class="n">something</span><span class="p">()</span>
|
||
<span class="go"><Mock name='mock.foo.something()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">mock_bar</span><span class="o">.</span><span class="n">other</span><span class="o">.</span><span class="n">thing</span><span class="p">()</span>
|
||
<span class="go"><Mock name='mock.bar.other.thing()' id='...'></span>
|
||
</code></pre><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">manager</span><span class="o">.</span><span class="n">mock_calls</span>
|
||
<span class="go">[call.foo.something(), call.bar.other.thing()]</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-285">然后,我们可以通过与管理器模拟上的<code class="docutils literal"><span class="pre">mock_calls</span></code>属性进行比较来断言调用,包括顺序:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">expected_calls</span> <span class="o">=</span> <span class="p">[</span><span class="n">call</span><span class="o">.</span><span class="n">foo</span><span class="o">.</span><span class="n">something</span><span class="p">(),</span> <span class="n">call</span><span class="o">.</span><span class="n">bar</span><span class="o">.</span><span class="n">other</span><span class="o">.</span><span class="n">thing</span><span class="p">()]</span>
|
||
<span class="gp">>>> </span><span class="n">manager</span><span class="o">.</span><span class="n">mock_calls</span> <span class="o">==</span> <span class="n">expected_calls</span>
|
||
<span class="go">True</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-286">如果<code class="docutils literal"><span class="pre">patch</span></code>正在创建,并放置到位,您可以使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.attach_mock" title="unittest.mock.Mock.attach_mock"><code class="xref py py-meth docutils literal"><span class="pre">attach_mock()</span></code></a>方法将它们附加到管理器模拟。</span><span class="yiyi-st" id="yiyi-287">附加呼叫后将记录在管理器的<code class="docutils literal"><span class="pre">mock_calls</span></code>中。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">manager</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.Class1'</span><span class="p">)</span> <span class="k">as</span> <span class="n">MockClass1</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">with</span> <span class="n">patch</span><span class="p">(</span><span class="s1">'mymodule.Class2'</span><span class="p">)</span> <span class="k">as</span> <span class="n">MockClass2</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="n">manager</span><span class="o">.</span><span class="n">attach_mock</span><span class="p">(</span><span class="n">MockClass1</span><span class="p">,</span> <span class="s1">'MockClass1'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">manager</span><span class="o">.</span><span class="n">attach_mock</span><span class="p">(</span><span class="n">MockClass2</span><span class="p">,</span> <span class="s1">'MockClass2'</span><span class="p">)</span>
|
||
<span class="gp">... </span> <span class="n">MockClass1</span><span class="p">()</span><span class="o">.</span><span class="n">foo</span><span class="p">()</span>
|
||
<span class="gp">... </span> <span class="n">MockClass2</span><span class="p">()</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span>
|
||
<span class="gp">...</span>
|
||
<span class="go"><MagicMock name='mock.MockClass1().foo()' id='...'></span>
|
||
<span class="go"><MagicMock name='mock.MockClass2().bar()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">manager</span><span class="o">.</span><span class="n">mock_calls</span>
|
||
<span class="go">[call.MockClass1(),</span>
|
||
<span class="go"> call.MockClass1().foo(),</span>
|
||
<span class="go"> call.MockClass2(),</span>
|
||
<span class="go"> call.MockClass2().bar()]</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-288">如果已经进行了许多调用,但是你只对它们的特定序列感兴趣,那么替代方法是使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.assert_has_calls" title="unittest.mock.Mock.assert_has_calls"><code class="xref py py-meth docutils literal"><span class="pre">assert_has_calls()</span></code></a>方法。</span><span class="yiyi-st" id="yiyi-289">这需要一个调用列表(用<a class="reference internal" href="unittest.mock.html#unittest.mock.call" title="unittest.mock.call"><code class="xref py py-data docutils literal"><span class="pre">call</span></code></a>对象构造)。</span><span class="yiyi-st" id="yiyi-290">如果该调用序列在<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.mock_calls" title="unittest.mock.Mock.mock_calls"><code class="xref py py-attr docutils literal"><span class="pre">mock_calls</span></code></a>中,则assert成功。</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">m</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">m</span><span class="p">()</span><span class="o">.</span><span class="n">foo</span><span class="p">()</span><span class="o">.</span><span class="n">bar</span><span class="p">()</span><span class="o">.</span><span class="n">baz</span><span class="p">()</span>
|
||
<span class="go"><MagicMock name='mock().foo().bar().baz()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">m</span><span class="o">.</span><span class="n">one</span><span class="p">()</span><span class="o">.</span><span class="n">two</span><span class="p">()</span><span class="o">.</span><span class="n">three</span><span class="p">()</span>
|
||
<span class="go"><MagicMock name='mock.one().two().three()' id='...'></span>
|
||
<span class="gp">>>> </span><span class="n">calls</span> <span class="o">=</span> <span class="n">call</span><span class="o">.</span><span class="n">one</span><span class="p">()</span><span class="o">.</span><span class="n">two</span><span class="p">()</span><span class="o">.</span><span class="n">three</span><span class="p">()</span><span class="o">.</span><span class="n">call_list</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">m</span><span class="o">.</span><span class="n">assert_has_calls</span><span class="p">(</span><span class="n">calls</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-291">即使链接调用<code class="docutils literal"><span class="pre">m.one().two().three()</span></code>不是对模拟器的唯一调用,该断言仍然成功。</span></p><p><span class="yiyi-st" id="yiyi-292">有时,一个模拟可能有几个调用,并且你只是对关于<em>一些</em>的调用感兴趣。</span><span class="yiyi-st" id="yiyi-293">你甚至可能不关心订单。</span><span class="yiyi-st" id="yiyi-294">在这种情况下,您可以将<code class="docutils literal"><span class="pre">any_order=True</span></code>传递给<code class="docutils literal"><span class="pre">assert_has_calls</span></code>:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">m</span> <span class="o">=</span> <span class="n">MagicMock</span><span class="p">()</span>
|
||
<span class="gp">>>> </span><span class="n">m</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">m</span><span class="o">.</span><span class="n">two</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="n">m</span><span class="o">.</span><span class="n">seven</span><span class="p">(</span><span class="mi">7</span><span class="p">),</span> <span class="n">m</span><span class="o">.</span><span class="n">fifty</span><span class="p">(</span><span class="s1">'50'</span><span class="p">)</span>
|
||
<span class="go">(...)</span>
|
||
<span class="gp">>>> </span><span class="n">calls</span> <span class="o">=</span> <span class="p">[</span><span class="n">call</span><span class="o">.</span><span class="n">fifty</span><span class="p">(</span><span class="s1">'50'</span><span class="p">),</span> <span class="n">call</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">call</span><span class="o">.</span><span class="n">seven</span><span class="p">(</span><span class="mi">7</span><span class="p">)]</span>
|
||
<span class="gp">>>> </span><span class="n">m</span><span class="o">.</span><span class="n">assert_has_calls</span><span class="p">(</span><span class="n">calls</span><span class="p">,</span> <span class="n">any_order</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||
</code></pre></div><div class="section" id="more-complex-argument-matching"><h3><span class="yiyi-st" id="yiyi-295">26.6.3.13. </span><span class="yiyi-st" id="yiyi-296">More complex argument matching</span></h3><p><span class="yiyi-st" id="yiyi-297">使用与<a class="reference internal" href="unittest.mock.html#unittest.mock.ANY" title="unittest.mock.ANY"><code class="xref py py-data docutils literal"><span class="pre">ANY</span></code></a>相同的基本概念,我们可以实现匹配器,以对用作mock参数的对象执行更复杂的断言。</span></p><p><span class="yiyi-st" id="yiyi-298">假设我们期望一些对象被传递给一个mock,默认情况下基于对象标识(这是用户定义的类的Python默认值)比较等于。</span><span class="yiyi-st" id="yiyi-299">要使用<a class="reference internal" href="unittest.mock.html#unittest.mock.Mock.assert_called_with" title="unittest.mock.Mock.assert_called_with"><code class="xref py py-meth docutils literal"><span class="pre">assert_called_with()</span></code></a>,我们需要传入完全相同的对象。</span><span class="yiyi-st" id="yiyi-300">如果我们只对这个对象的一些属性感兴趣,那么我们可以创建一个匹配器来检查这些属性。</span></p><p><span class="yiyi-st" id="yiyi-301">您可以在此示例中看到如何对标准调用<code class="docutils literal"><span class="pre">assert_called_with</span></code>是不够的:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Foo</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">a</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">b</span> <span class="o">=</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span>
|
||
<span class="gp">...</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span> <span class="o">=</span> <span class="n">Mock</span><span class="p">(</span><span class="n">return_value</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="p">(</span><span class="n">Foo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">Foo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>: <span class="n">Expected: call(<__main__.Foo object at 0x...>)</span>
|
||
<span class="go">Actual call: call(<__main__.Foo object at 0x...>)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-302">我们的<code class="docutils literal"><span class="pre">Foo</span></code>类的比较函数可能如下所示:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">def</span> <span class="nf">compare</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">if</span> <span class="ow">not</span> <span class="nb">type</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">==</span> <span class="nb">type</span><span class="p">(</span><span class="n">other</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="kc">False</span>
|
||
<span class="gp">... </span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">a</span> <span class="o">!=</span> <span class="n">other</span><span class="o">.</span><span class="n">a</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="kc">False</span>
|
||
<span class="gp">... </span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">b</span> <span class="o">!=</span> <span class="n">other</span><span class="o">.</span><span class="n">b</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="kc">False</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="kc">True</span>
|
||
<span class="gp">...</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-303">和一个匹配器对象,可以使用这样的比较功能,它的平等操作看起来像这样:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="k">class</span> <span class="nc">Matcher</span><span class="p">:</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">compare</span><span class="p">,</span> <span class="n">some_obj</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">compare</span> <span class="o">=</span> <span class="n">compare</span>
|
||
<span class="gp">... </span> <span class="bp">self</span><span class="o">.</span><span class="n">some_obj</span> <span class="o">=</span> <span class="n">some_obj</span>
|
||
<span class="gp">... </span> <span class="k">def</span> <span class="nf">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
|
||
<span class="gp">... </span> <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">compare</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">some_obj</span><span class="p">,</span> <span class="n">other</span><span class="p">)</span>
|
||
<span class="gp">...</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-304">把所有这些:</span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">match_foo</span> <span class="o">=</span> <span class="n">Matcher</span><span class="p">(</span><span class="n">compare</span><span class="p">,</span> <span class="n">Foo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">match_foo</span><span class="p">)</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-305">使用我们的比较函数和要比较的<code class="docutils literal"><span class="pre">Foo</span></code>对象来实例化<code class="docutils literal"><span class="pre">Matcher</span></code>。</span><span class="yiyi-st" id="yiyi-306">在<code class="docutils literal"><span class="pre">assert_called_with</span></code>中,将调用<code class="docutils literal"><span class="pre">Matcher</span></code>等式方法,该方法将mock调用的对象与我们创建的匹配器进行比较。</span><span class="yiyi-st" id="yiyi-307">如果它们匹配,则<code class="docutils literal"><span class="pre">assert_called_with</span></code>通过,并且如果它们不会出现<a class="reference internal" href="exceptions.html#AssertionError" title="AssertionError"><code class="xref py py-exc docutils literal"><span class="pre">AssertionError</span></code></a></span></p><pre><code class="language-python"><span></span><span class="gp">>>> </span><span class="n">match_wrong</span> <span class="o">=</span> <span class="n">Matcher</span><span class="p">(</span><span class="n">compare</span><span class="p">,</span> <span class="n">Foo</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
|
||
<span class="gp">>>> </span><span class="n">mock</span><span class="o">.</span><span class="n">assert_called_with</span><span class="p">(</span><span class="n">match_wrong</span><span class="p">)</span>
|
||
<span class="gt">Traceback (most recent call last):</span>
|
||
<span class="o">...</span>
|
||
<span class="gr">AssertionError</span>: <span class="n">Expected: ((<Matcher object at 0x...>,), {})</span>
|
||
<span class="go">Called with: ((<Foo object at 0x...>,), {})</span>
|
||
</code></pre><p><span class="yiyi-st" id="yiyi-308">通过一些调整,您可以直接使用比较功能引发<a class="reference internal" href="exceptions.html#AssertionError" title="AssertionError"><code class="xref py py-exc docutils literal"><span class="pre">AssertionError</span></code></a>,并提供更有用的失败消息。</span></p><p><span class="yiyi-st" id="yiyi-309">从版本1.5开始,Python测试库<a class="reference external" href="https://pyhamcrest.readthedocs.org/">PyHamcrest</a>提供类似的功能,这在其等价匹配器(<a class="reference external" href="https://pyhamcrest.readthedocs.org/en/release-1.8/integration/#module-hamcrest.library.integration.match_equality">hamcrest.library.integration.match_equality</a> 。</span></p></div></div></div></div> |