当前位置: 首页 > 面试题库 >

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

慕容耘豪
2023-03-14
问题内容

我很多时候都参与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犹太洁食且无任何反射技巧就可以访问,则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个attrs,但是拥有5-6个OuterFoo参数是非常合理的,然后构造函数必须接受8-10个参数!在顶部添加getter,您已经有100行完全没用的代码(也需要getter来获取这些attrs进行测试)。是的,我可以通过提供构建器模式来使情况更好一些,但我认为这不仅过度设计,而且还违反了TDD本身的目的!

解决此问题的另一种方法是为类公开一个受保护的方法Foo,对其进行扩展FooTest并获取所需的数据。同样,我认为这也是一种不好的方法,因为protectedmethod
确实定义了合同, 并且通过公开该 合同 ,我现在已经对其进行隐式签名。

不要误会我的意思。 我喜欢编写可测试的代码我喜欢简洁,简洁的API,短代码块,可读性等。 但是,我不喜欢
在信息隐藏方面做出任何牺牲, 因为这更易于进行单元测试

有人能对此提出任何想法吗(总体而言,特别是特别如此)?对于给定的示例,还有其他更好的解决方案吗?


问题答案:

我认为您应该重新考虑使用反射。

它有其缺点,但是如果它允许您在没有虚拟代码的情况下维护所需的安全模型,那可能是一件好事。通常不需要反思,但有时没有很好的替代品。

信息隐藏的另一种方法是将类/对象视为黑匣子,并且不访问任何非公共方法(尽管这样做可以出于“错误”的原因(即答案正确但由于错误的原因)通过测试。)



 类似资料:
  • 问题内容: 封装和信息隐藏之间到底有什么区别? 好吧,我知道将字段设为私有,然后将字段的setter和getter设为封装。但是封装只是这个意思吗? 假设我有一个如下所述的课程。 现在,类IsThisEncapsulation是封装的示例吗? 现在将上述类中的“年龄”字段设为私有可以实现信息隐藏吗? 您能给我清楚的例子,以帮助我清楚地区分这些概念吗? 问题答案: 好吧,我知道将字段设为私有,然后将

  • 测试用来验证非测试的代码是否按照期望的方式运行的 Rust 函数。测试函数体通常执行如下三种操作: 设置任何所需的数据或状态 运行需要测试的代码 断言其结果是我们所期望的 让我们看看 Rust 提供的专门用来编写测试的功能:test 属性、一些宏和 should_panic 属性。 作为最简单例子,Rust 中的测试就是一个带有 test 属性注解的函数。属性(attribute)是关于 Rust

  • 如果现有的特征测试不能完成你所需要的工作,你就必须编写一个新的。这些宏是创建模块。它们为其它宏提供了检查各种 特征是否存在并且报告结果的方式。 本章包括一些建议和一些关于现有的测试的为什么要那样编写的原因。通过阅读现有的测试,你还可以学到许多关于编写 Autoconf测试的方法。如果在一个或多个Autoconf测试中出现了错误,这些信息可以帮助你理解它们意味着什么,这有助 于你找到最佳的解决问题的

  • 由于你写的大部分 Redux 代码是函数,而且其中大部分是纯函数,所以很好测,不需要模拟。 准备工作 我们推荐使用 Jest) 作为测试引擎,需要注意的是 Jest 运行在 Node 环境中,因此你不能访问 DOM。 npm install --save-dev jest 如果想要和 Babel 一起使用,还需要安装 babel-jest npm install --save-dev babel-

  • 假设我们编写了一个hello.js,并且输出一个简单的求和函数: // hello.js module.exports = function (...rest) { var sum = 0; for (let n of rest) { sum += n; } return sum; }; 这个函数非常简单,就是对输入的任意参数求和并返回结果。 如

  • 类通常对类的客户隐藏其实现细节,即所谓的信息隐藏。下列以堆栈数据结构作为信息隐藏的例子。 可以把堆栈看成一堆盘子。将盘子放在堆中时,总是放在顶部(压入堆栈),从堆中取下盘子时,总是从顶上取(称为弹出堆栈)。堆栈是后进先出(last-in,first-out;LIFO)的数据结构,最后放进堆栈的项目最先从堆栈中取出。 程序员可以生成堆栈类,对客户隐藏实现细节。堆栈可以方便地用数组实现(或用第15章“