编写/实现 API:可测试性与信息隐藏

2023-12-02

很多时候,我在参与 API 的设计/实现时都面临着这样的困境。

我是一个非常坚定的支持者信息隐藏并尝试为此使用各种技术,包括但不限于内部类、私有方法、包私有限定符等。

这些技术的问题在于它们往往会妨碍良好的可测试性。虽然其中一些技术可以解决(例如,通过将类放入同一个包中来实现包私有性),但其他技术则无法解决没那么容易对付要么需要反射魔法或其他技巧。

我们看一下具体的例子:

public class Foo {
   SomeType attr1;
   SomeType attr2;
   SomeType attr3;

   public void someMethod() {
      // calculate x, y and z
      SomethingThatExpectsMyInterface something = ...;
      something.submit(new InnerFoo(x, y, z));
   }

   private class InnerFoo implements MyInterface {
      private final SomeType arg1;
      private final SomeType arg2;
      private final SomeType arg3;

      InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) {
         this.arg1 = arg1;
         this.arg2 = arg2;
         this.arg3 = arg3;
      }

      @Override
      private void methodOfMyInterface() {
         //has access to attr1, attr2, attr3, arg1, arg2, arg3
      }
   }
}

有充分的理由不暴露InnerFoo- 任何其他类、库都不应访问它,因为它没有定义任何公共契约,并且作者故意不希望它可访问。然而,为了使其 100% TDD-kosher 并且无需任何反射技巧即可访问,InnerFoo应该这样重构:

private class OuterFoo implements MyInterface {
   private final SomeType arg1;
   private final SomeType arg2;
   private final SomeType arg3;
   private final SomeType attr1;
   private final SomeType attr2;
   private final SomeType attr3;

   OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) {
      this.arg1 = arg1;
      this.arg2 = arg2;
      this.arg3 = arg3;
      this.attr1 = attr1;
      this.attr2 = attr2;
      this.attr3 = attr3;
   }

   @Override
   private void methodOfMyInterface() {
      //can be unit tested without reflection magic
   }
}

这个例子只涉及 3 个属性,但是有 5-6 个属性是相当合理的,OuterFoo构造函数必须接受 8-10 个参数!在顶部添加 getters,你已经有 100 行完全无用的代码(还需要 getters 来获取这些属性进行测试)。是的,我可以通过提供构建器模式让情况好一点,但我认为这不仅是过度设计,而且违背了 TDD 本身的目的!

此问题的另一个解决方案是公开类的受保护方法Foo,将其扩展为FooTest并获取所需数据。再说一次,我认为这也是一个不好的方法,因为protected method 是否定义了合同通过公开它,我现在已经隐含地签署了它。

别误会我的意思。我喜欢编写可测试的代码. 我喜欢简洁、干净的 API、短代码块、可读性等等。但我不喜欢的是在信息隐藏方面做出任何牺牲只是因为单元测试更容易.

任何人都可以对此提供任何想法(一般而言,特别是)?对于给定的示例,还有其他更好的解决方案吗?


对于此类事情,我的首选答案是“测试代理”。在您的测试包中,从被测系统派生一个子类,其中包含受保护数据的“直通”访问器。

优点:

  • 您可以直接测试或模拟您不想公开的方法。
  • 由于测试代理位于测试包中,因此您可以确保它永远不会在生产代码中使用。
  • 与直接测试类相比,测试代理需要对代码进行更少的更改才能使其可测试。

缺点:

  • 该类必须是可继承的(不final)
  • 您需要访问的任何隐藏成员都不能是私有的;受保护是你能做的最好的事情。
  • 这并不是严格意义上的 TDD;而是 TDD。 TDD 适合一开始就不需要测试代理的模式。
  • 严格来说,这甚至不是单元测试,因为在某种程度上,您依赖于代理和实际 SUT 之间的“集成”。

简而言之,这通常应该是罕见的。我倾向于仅将它用于 UI 元素,其中最佳实践(以及许多 IDE 的默认行为)是将嵌套 UI 控件声明为从类外部不可访问。这绝对是个好主意,这样您就可以控制调用者如何从 UI 获取数据,但这也使得很难为控件提供一些已知值来测试该逻辑。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

编写/实现 API:可测试性与信息隐藏 的相关文章

随机推荐

  • jQuery.proxy() 用法

    我正在阅读有关的 apijQuery proxy 它看起来很有希望 但我想知道在什么情况下最好使用它 谁能启发我吗 当你想要一个具有以下功能的函数时this值绑定到特定对象 例如 在事件处理程序 AJAX 回调 超时 间隔 自定义对象等回调
  • usleep() 计算经过的时间表现得很奇怪

    我使用下面的代码计算每次连续调用处理程序函数所花费的时间 以毫秒为单位 当我使用 usleep 1000 时 即每次调用之间的 1 毫秒时间差为 10 毫秒 而当我使用 usleep 1000000 时 即 1 秒 每次调用之间的时间间隔令
  • 覆盖从另一个模块导入的函数中的全局变量

    假设我有两个模块 a py value 3 def x return value b py from a import x value 4 我的目标是使用以下功能a x in b 但更改函数返回的值 具体来说 value将被查找a作为全局名
  • 删除事件发生时从 Microsoft Graph 获取通知

    我已经订阅了活动 https outlook office com api v2 0 me events 推送通知 当我删除重复主事件的一个事件时 我收到带有主事件 ID 的更新通知 而不是特定发生事件 ID 如果不与所有以前的重复事件进行
  • 使用命名实体训练模型

    我正在使用命名实体识别器查看standford corenlp 我有不同类型的输入文本 我需要将其标记到我自己的实体中 所以我开始训练我自己的模型 但它似乎不起作用 例如 我的输入文本字符串是 Book of 49 Magazine Art
  • Setter.Target 给我一个错误“RelativePanel.AlignHorizo​​ntalCenterWithPanel”

    我正在开发一个 UWP 应用程序 我正在使用 Template10 我有一个TextBlock 在VisualStateNarrow我要它RelativePanel AlignVerticalCenterWithPanel True and
  • 令人惊讶的是,达夫尼未能验证集合理解的有界性

    Dafny 对于集合交集函数的定义没有任何问题 function method intersection A set
  • Android:如何控制主页按钮

    我们正在尝试为我邻居的精神和身体残疾的女儿提供一个应用程序 让她使用 Android 平板电脑作为说话者 也就是说 她按下几个大按钮 设备就会生成语音 该应用程序基本上是一个 WebView 和一个 Javascript 中的附加对象 用于
  • Python - 尽管使用 df.loc 但仍获取“SettingWithCopyWarning”

    尽管使用了推荐的方法 我还是收到了SettingWithCopyWarning 我缺少什么 我该如何纠正它或抑制这个特定的警告 import numpy as np import pandas as pd df pd DataFrame n
  • 如何在 Django 中创建一个查询集来查看数据库中的名称是否是我的查询字符串的子字符串?

    正如标题所提到的 我正在 Django 中工作 并尝试创建一个查询集来返回所有名称值是我的 query string 的子字符串的 客户 模型 我想要这样的东西 Customer objects filter firstName icont
  • 编写 ruby​​gems 的陷阱

    已有问题及答案how to writerubygem 但是在编写 ruby gem 时应该避免什么 什么会给使用您的 ruby gem 的人带来问题 宝石包装 最佳实践给出了很多建议 其中一些包括 不要污染全局加载路径 理想情况下 只有fo
  • SceneKit SCNPhysicsBody 收到休息通知

    SceneKit有没有办法在什么时候收到通知dynamicBody处于休息状态 我想删除dynamicBody当它落到地面并完全停止移动时 我想我会有相当多的那些 所以我想使用基于事件的东西 而不是循环遍历所有bodies并检查它们的速度
  • WinHttpRequest gzip 响应解析

    我在用着MSXML2 XMLHTTP60在我的 VBA 项目中进行 http 冲浪 问题是MSXML2 XMLHTTP60仅限于四个并发请求 我正在尝试使用WinHttp WinHttpRequest 5 1相反 还有另一个问题 MSXML
  • 根据两个数组的差异创建第三个数组

    我需要根据两个数组的差异创建第三个数组 我根本无法理解这个逻辑 正确的第三个数组v3将是 来自下面的代码 v3 Carol Ted Thor Freya Thanks Sub MatchArrays Dim v1 v2 v3 Dim i A
  • 在 VBScript 中设置当前日期和时间的格式

    我想知道是否有人可以帮助我 我对 ASP 很陌生 我想按如下方式设置当前日期和时间的格式 yyyy mm dd hh mm ss 但我能做的就是以下 Response Write Date 有人可以帮我吗 默认情况下 Classic ASP
  • 三星 Galaxy Note III 模拟器设置

    我正在将我的 iPhone 应用程序移植到 Android 客户端使用三星 Galaxy Note III 我需要创建一个模拟器来帮助调试 但在使用我获得的设置启动模拟器时遇到问题gsmarena 类似的帖子也有 不过都是Samsung G
  • php SoapClient 在传递具有相对路径模式的 wsdl 时失败

    我有以下问题 当我向 SoapClient 对象传递一个使用相对路径导入架构的 wsdl 时 它的实例化失败 无论如何 根据我的研究 我相信情况确实如此 我的代码如下 wsdl http myproxy webservice wsdl op
  • 使用有限的操作对双端队列进行排序?

    您好 我在 Robert Sedgewick 的 算法第四版 中遇到了一个问题 出队排序 解释如何对一副牌进行排序 但唯一允许的操作是查看最上面两张牌的值 交换最上面两张牌以及将最上面的牌移动到这副牌的底部 我希望有人能解释一下这是如何完成
  • SQLite 是最适合用于嵌入式数据库的东西吗? [复制]

    这个问题在这里已经有答案了 我需要构建一个将安装在 Linux 服务器上的 Java 应用程序 当人们安装时 他们只需要安装这个应用程序 启动它 仅此而已 但我们有一些数据需要保存 我对MySQL说不 因为它需要服务器 我对 XML 说不
  • 编写/实现 API:可测试性与信息隐藏

    很多时候 我在参与 API 的设计 实现时都面临着这样的困境 我是一个非常坚定的支持者信息隐藏并尝试为此使用各种技术 包括但不限于内部类 私有方法 包私有限定符等 这些技术的问题在于它们往往会妨碍良好的可测试性 虽然其中一些技术可以解决 例