下面是一些针对更为高级应用场景的补充示例。
实际上一旦你理解了 属性那么使用 mock 模拟链式调用就会相当直观。 当一个 mock 首次被调用,或者当你在它被调用前获取其 时,将会创建一个新的 。
这意味着你可以通过检视 mock 来了解从调用被模拟对象返回的对象是如何被使用的:
从这里开始只需一个步骤即可配置并创建有关链式调用的断言。 当然还有另一种选择是首先以更易于测试的方式来编写你的代码...
因此,如果我们有这样一些代码:
假定 已经过良好测试,我们要如何测试 ? 特别地,我们希望测试代码段 是否以正确的方式使用了响应对象。
由于这个链式调用来自一个实例属性我们可以对 属性在 实例上进行猴子式修补。 在这个特定情况下我们只对最后调用 的返回值感兴趣所以我们不需要进行太多的配置。 让我们假定它返回的是“文件类”对象,因此我们将确保我们的响应对象使用内置的 作为其 。
为了做到这一点我们创建一个 mock 实例作为我们的 mock 后端并为它创建一个 mock 响应对象。 要将该响应对象设为最后的 的返回值我们可以这样做:
我们可以通过更好一些的方式做到这一点,即使用 方法直接为我们设置返回值:
有了这些我们就能准备好给“mock 后端”打上猴子补丁并可以执行真正的调用:
使用 我们可以通过一个断言来检查链式调用。 一个链式调用就是在一行代码中连续执行多个调用,所以在 中将会有多个条目。 我们可以使用 来为我们创建这个调用列表:
在一些测试中,我想把对 的调用模拟为返回一个已知日期的调用,但又不想阻止测试中的代码创建新的日期对象。 然而 是用 C 语言编写的,因此我不能简单地给静态的 方法打上猴子补丁。
我找到了实现这一点的简单方式即通过一个 mock 来实际包装日期类,但通过对构造器的调用传递给真实的类(并返回真实的实例)。
这里使用 来模拟被测试模块中的 类。 然后将模拟 date 类的 属性设为一个返回真实日期的 lambda 函数。 当模拟 date 类被调用时,将通过 构造并返回一个真实日期。
请注意我们没有在全局范围上修补 ,我们只是在 使用 它的模块中给 打补丁。 参见 。
当 被调用时将返回一个已知的日期,但对 构造器的调用仍会返回普通的日期。 如果不是这样你会发现你必须使用与被测试的代码完全相同的算法来计算出预期的结果,这是测试工作中的一个经典的反模式。
对 date 构造器的调用会被记录在 属性中 ( 等),它们也可能对你的测试有用处。
有关处理模块日期或其他内置类的一种替代方式的讨论请参见 。
Python 生成器是指在被迭代时使用 语句来返回一系列值的函数或方法 。
调用生成器方法 / 函数将返回生成器对象。 生成器对象随后会被迭代。 迭代操作对应的协议方法是 ,因此我们可以使用 来模拟它。
以下是一个使用 "iter" 方法模拟为生成器的示例类:
我们要如何模拟这个类,特别是它的 "iter" 方法呢?
为了配置从迭代操作(隐含在对 的调用中)返回的值,我们需要配置调用 所返回的对象。
如果你想要为多个测试方法准备好多个补丁那么最简单的方式就是将 patch 装饰器应用到每个方法上。 这在感觉上像上不必要的重复。 对此,你可以使用 (包括基各种不同形式) 作为类装饰器。 这将把补丁应用于类上的所有测试方法。 测试方法是通过以 打头的名称来标识的:
另一种管理补丁的方式是使用 。 它允许你将打补丁操作移至你的 和 方法中。
如果你要使用这个技巧则你必须通过调用 来确保补丁被“恢复”。 这可能要比你想像的更麻烦,因为如果在 setUp 中引发了异常那么 tearDown 将不会被调用。 可以做到更方便:
当前在编写测试时我需要修补一个 未绑定方法 (在类上而不是在实例上为方法打补丁)。 我需要将 self 作为第一个参数传入因为我想对哪些对象在调用这个特定方法进行断言。 问题是这里你不能用 mock 来打补丁,因为如果你用 mock 来替换一个未绑定方法那么当从实例中获取时它就不会成为一个已绑定方法,因而它不会获得传入的 self。 绕过此问题的办法是改用一个真正的函数来修补未绑定方法。 装饰器让使用 mock 来给方法打补丁变得如此简单以至于创建一个真正的函数成为一件麻烦事。
如果将 传给 patch 那么它就会用一个 真正的 函数对象来打补丁。 这个函数对象具有与它所替换的函数相同的签名,但会在内部将操作委托给一个 mock。 你仍然可以通过与以前完全相同的方式来自动创建你的 mock。 但是这将意味着一件事,就是如果你用它来修补一个类上的非绑定方法那么如果它是从一个实例中获取则被模拟的函数将被转为已绑定方法。 传给它的第一个参数将为 ,而这真是我想要的:
如果我们不使用 那么这个未绑定方法会改为通过一个 Mock 补丁来修补,而不是附带 来调用。
mock 有一个很好的 API 用于针对你的 mock 对象如何被使用来下断言。
如果你的 mock 只会被调用一次那么你可以使用 方法,该方法也会断言 的值为一。
和 都是有关 最近 调用的断言。 如果你的 mock 将被多次调用,并且你想要针对 所有 这些调用下断言你可以使用 :
使用 辅助对象可以方便地针对这些调用下断言。 你可以创建一个预期调用的列表并将其与 比较。 这看起来与 的 repr 非常相似:
另一种很少见,但可能给你带来麻烦的情况会在你的 mock 附带可变参数被调用的时候发生。 和 将保存对这些参数的 引用。 如果这些参数被受测试的代码所改变那么你将无法再针对当该 mock 被调用时附带的参数值下断言。
下面是一些演示此问题的示例代码。 设想在 'mymodule' 中定义了下列函数:
当我们想要测试 调用 并附带了正确的参数时将可看到发生了什么:
对于 mock 的一个可能性是复制你传入的参数。 如果你创建依赖于对象标识号相等性的断言那么这可能会在后面导致问题。
下面是一个使用 功能的解决方案。 如果你为 mock 提供了 函数那么 将附带与该 mock 相同的参数被调用。 这样我们就有机会拷贝这些参数并将其保存起来用于之后执行断言。 在本例中我使用了 另一个 mock 来保存参数以便可以使用该 mock 的方法来执行断言。 在这里辅助函数再次为我设置好了这一切。
调用 时会传入将被调用的 mock。 它将返回一个新的 mock 供我们进行断言。 函数会拷贝这些参数并附带该副本来调用我们的 。
一个替代方式是创建一个 或 的子类来拷贝 (使用 ) 参数。 下面是一个示例实现:
当你子类化 或 时所有动态创建的属性以及 都将自动使用你的子类。 这意味着 的所有子类也都将为 类型。
使用 patch 作为上下文管理器很不错,但是如果你要执行多个补丁你将不断嵌套 with 语句使得代码越来越深地向右缩进:
使用 unittest 函数和 我们可以达成同样的效果而无须嵌套缩进。 一个简单的辅助方法 会为我们执行打补丁操作并返回所创建的 mock:
你可能会想要模拟一个字典或其他容器对象,记录所有对它的访问并让它的行为仍然像是一个字典。
要做到这点我们可以用 ,它的行为类似于字典,并会使用 将字典访问委托给下层的在我们控制之下的一个真正的字典。
当我们的 的 和 方法被调用(即正常的字典访问操作)时 将附带相应的键(对于 还将附带值)被调用。 我们还可以控制返回的内容。
在 被使用之后我们可以使用 等属性来针对该字典是如何被使用的下断言。
通过提供这些附带影响函数, 的行为将类似于普通字典但又会记录所有访问。 如果你尝试访问一个不存在的键它甚至会引发 。
在它被使用之后你可以使用普通的 mock 方法和属性进行有关访问操作的断言:
你可能出于各种原因想要子类化 。 其中一个可能的原因是为了添加辅助方法。 下面是一个笨兮兮的示例:
The standard behaviour for 实例的标准行为是属性和返回值 mock 具有与它们所访问的 mock 相同的类型。 这将确保 的属性均为 而 的属性均为 。 因此如果你通过子类化来添加辅助方法那么它们也将在你的子类的实例的属性和返回值 mock 上可用。
有时这很不方便。 例如, 子类化了 mock 来创建一个 。 将它也应用于属性实际上会导致出错。
(它的所有形式) 使用一个名为 的方法来创建这些用于属性和返回值的“子 mock”。 你可以通过重写此方法来防止你的子类被用于属性。 其签名被设为接受任意关键字参数 () 并且它们会被传递给 mock 构造器:
有一种会令模拟变困难的情况是当你在函数内部有局部导入。 这更难模拟的原因是它们不是使用来自我们能打补丁的模拟命名空间中的对象。
一般来说局部导入是应当避免的。 局部导入有时是为了防止循环依赖,而这个问题 通常 都有更好的解决办法(重构代码)或者通过延迟导入来防止“前期成本”。 这也可以通过比无条件地局部导入更好的方式来解决(将模块保存为一个类或模块属性并且只在首次使用时执行导入)。
除此之外还有一个办法可以使用 来影响导入的结果。 导入操作会从 字典提取一个 对象。 请注意是提取一个 对象,它不是必须为模块。 首次导入一个模块将使一个模块对象被放入 ,因此通常当你执行导入时你将得到一个模块。 但是并非必然如此。
这意味着你可以使用 来 临时性地 将一个 mock 放入 。 在补丁激活期间的任何导入操作都将得到该 mock。 当补丁完成时(被装饰的函数退出,with 语句代码块结束或者 被调用)则之前存在的任何东西都将被安全地恢复。
下面是一个模拟 'fooble' 模拟的示例。
你可以看到 成功执行,而当退出时 中将不再有 'fooble'。
这同样适用于 形式:
稍微多做一点工作你还可以模拟包的导入:
类允许你通过 属性来追踪在你的 mock 对象上的方法调用的 顺序。 这并不允许你追踪单独 mock 对象之间的调用顺序,但是我们可以使用 来达到同样的效果。
因为 mock 会追踪 中对子 mock 的调用,并且访问 mock 的任意属性都会创建一个子 mock,所以我们可以基于父 mock 创建单独的子 mock。 随后对这些子 mock 的调用将按顺序被记录在父 mock 的 中:
我们可以随后通过与管理器 mock 上的 属性进行比较来进行有关这些调用,包括调用顺序的断言:
如果 创建并准备好了你的 mock 那么你可以使用 方法将它们附加到管理器 mock 上。 在附加之后所有调用都将被记录在管理器的 中。
如果已经进行了许多调用,但是你只对它们的一个特定序列感兴趣则有一种替代方式是使用 方法。 这需要一个调用的列表(使用 对象来构建)。 如果该调用序列在 中则断言将成功。
即使链式调用 不是对 mock 的唯一调用,该断言仍将成功。
有时可能会对一个 mock 进行多次调用,而你只对断言其中的 某些 调用感兴趣。 你甚至可能对顺序也不关心。 在这种情况下你可以将 传给 :
使用与 一样的基本概念我们可以实现匹配器以便在用作 mock 的参数的对象上执行更复杂的断言。
假设我们准备将某个对象传给一个在默认情况下基于对象标识相等(这是 Python 中用户自定义类的默认行为)的 mock。 要使用 我们就将必须传入完全相同的对象。 如果我们只对该对象的某些属性感兴趣那么我们可以创建一个能为我们检查这些属性的匹配器。
在这个示例中你可以看到为何执行对 的‘标准’调用并不足够:
一个针对我们的 类的比较函数看上去会是这样的:
而一个可以使用这样的比较函数进行相等性比较运算的匹配器对象看上去会是这样的:
将所有这些放在一起:
是用我们的比较函数和我们想要比较的 对象来实例化的。 在 中将会调用 的相等性方法,它会将调用 mock 时附带的对象与我们创建我们的匹配器时附带的对象进行比较。 如果它们匹配则 通过,而如果不匹配则会引发 :
通过一些调整你可以让比较函数直接引发 并提供更有用的失败消息。
从 1.5 版开始,Python 测试库 提供了类似的功能,在这里可能会很有用,它采用的形式是相等性匹配器 ()。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.mushiming.com/mjsbk/9780.html