如何减少用较大的对象模型包装第三方库的手动工作?


16

就像2012年这个问题 的作者和2013年这个问题的作者一样,我有一个第三方库,需要对其进行包装以正确测试我的应用程序。最高答案指出:

您始终希望将第三方类型和方法包装在接口后面。这可能是乏味且痛苦的。有时,您可以编写代码生成器或使用工具来执行此操作。

就我而言,该库用于对象模型,因此具有大量的类和方法,这些类和方法需要包装才能使该策略成功。除了“繁琐而痛苦”之外,这也成为测试的硬障碍。

自提出这个问题以来的四年中,我知道隔离框架已经走了很长一段路。我的问题是:现在是否存在一种更简单的方法来实现完全包装第三方库的效果?我如何摆脱这一过程中的痛苦并减少手工工作?


我的问题不是我最初链接的问题的重复,因为我的问题是减少手工包装的工作。这些其他问题仅询问包装是否有意义,而不是如何减少工作量。


您在说什么编程语言和什么样的库?
布朗

@DocBrown C#和PDF操作库。
汤姆·赖特

2
在meta发表了一篇文章,以找到重新打开您的问题的支持。
布朗

谢谢@DocBrown-我希望那里会有一些有趣的观点。
汤姆·赖特

1
如果我们在接下来的48小时内没有得到更好的答案,我会为此悬赏。
布朗

Answers:


4

假设您不是在寻找一个模拟框架,因为它们无处不在且易于查找,因此需要注意以下几点:

  1. 您永远不会“做”任何事情。
    包装第三方库并不总是最好的。如果您的应用程序本质上依赖于一个库,或者它实际上是围绕一个或两个核心库构建的,请不要浪费您的时间来打包它。如果库发生更改,则您的应用程序仍将需要更改
  2. 可以使用集成测试。
    在稳定的边界,应用程序固有的边界或无法轻松模拟的边界周围尤其如此。如果满足这些条件,则包裹和嘲弄是复杂且乏味的。在那种情况下,我会避免两种情况:不要包装也不嘲笑;只需编写集成测试。(如果自动测试是目标。)
  3. 工具和框架无法消除逻辑上的复杂性。
    原则上,工具只能在样板上切割。但是,并没有采用自动化算法来采用复杂的界面并使之变得简单-更不用说采用界面X并对其进行调整以适应您的需求了。(只有您知道算法!)因此,毫无疑问,虽然可以生成精简包装器的工具,但我建议它们尚无处不在,因为最终您仍然需要智能地进行编码,因此需要手动进行编码,接口,即使它隐藏在包装器后面。

这就是说,有战术,你可以在许多语言使用,以避免直接引用的类。在某些情况下,您可以“伪装” 实际上不存在的接口或精简包装例如,在C#中,我会选择以下两种方法之一:

  1. 使用工厂和隐式类型

您可以避免用这个小组合完全包装一个复杂的类:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

如果你能避免存储这些对象的成员,无论是通过一个更加“实用”法或将它们存储在一个字典(或什么),这种方法有编译时类型检查的好处没有你的核心业务实体需要知道确切他们正在上什么课。

所需要做的就是在编译时,工厂返回的类实际上具有您的业务对象正在使用的方法。

  1. 使用动态类型

这与使用隐式类型是一样的,但是涉及另一个折衷方案:您将丢失编译类型检查,并且能够匿名地将外部依赖项添加为类成员并注入您的依赖项。

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

使用这两种策略,当需要进行模拟时ExternalPDFLibraryDocument,就像我之前提到的那样,您需要做一些工作-但是,无论如何,这是您需要做的工作。而且,通过这种构造,您避免了繁琐地定义100个瘦小的包装器类的麻烦。在大多数情况下,您只是简单地使用了库而无需直接查看它

综上所述,我仍然会考虑三个主要理由来明确包装第三方库-没有一个可以暗示使用工具或框架:

  1. 特定的库不是应用程序固有的。
  2. 如果不打包就进行交换将非常昂贵。
  3. 我不喜欢API本身。

如果我在所有这三个领域中都没有任何关注点,那么您无需付出任何大的努力就可以完成它。而且,如果您在所有这三个方面都存在担忧,那么自动生成的薄包装实际上并没有帮助。

如果您决定包库时,最有效和最有效地利用你的时间是建立你对你的界面应用需要 ; 不针对现有的API。

换句话说,请遵循经典建议:推迟所有可能的决定。首先构建应用程序的“核心”。对代码的接口,这将最终你想要什么,这将最终通过“周边的东西”是不存在兑现。缩小差距。

这种努力可能不会觉得像时间保存的; 但是,如果您觉得需要包装纸,这是安全进行包装的最有效方法

这样想吧。

您需要在代码的某些暗处针对该库进行编码- 即使已包装好。如果您在测试过程中对库进行了模拟,那么那里就不可避免地要进行手动操作- 即使已将其打包。但是,这并不意味着您需要在应用程序的大部分内容中直接按名称确认该库。

TLDR

如果该库值得包装,请使用策略来避免对第三方库的广泛直接引用,但不要采用捷径来生成精简包装。首先建立您的业务逻辑,仔细考虑您的接口,然后根据需要有机地出现您的适配器

而且,如果涉及到这一点,请不要害怕集成测试。它们有点模糊,但是它们仍然提供了工作代码的证据,并且仍然可以很容易地制作它们以阻止回归。


2
这是另一篇未回答该问题的帖子-该帖子显然不是 “何时包装”,而是“如何减少手工劳动”。
布朗

1
抱歉,但我认为您错过了问题的核心。我看不出您的建议如何减少包装的任何人工工作。假定所涉及的库具有一个复杂的对象模型作为API,其中包含数十个类和数百种方法。它的API可以用于标准用途,但是很好,但是如何包装它以减少痛苦/精力呢?
布朗

1
TLDR;如果您想获得奖励积分,请告诉我们一些我们尚不知道的内容;-)
布朗

1
@DocBrown我没错过重点。但是,我认为错过了我的答案。投资适当的精力可以节省大量工作。有测试含义-但这只是副作用。使用工具自动在库周围生成一个薄包装器,仍然需要您围绕其他人的API构建核心库并构建模拟- 如果您坚持不使用库,这无法避免的很多工作您的测试。
svidgen '16

1
@DocBrown这很荒谬。“这类问题是否具有一类工具或方法?” 是。当然可以。防御性编码对单元测试没有教条 ……就像我的回答所说。如果您确实有一个自动生成的薄包装器,它将提供什么价值!?...不允许您注入依赖项进行测试,您仍然必须手动执行此操作。而且它不允许您换出库,因为您仍在使用库的API进行编码 ...现在只是间接的。
svidgen '16

9

不要对该代码进行单元测试。改为编写集成测试。在某些情况下,单元测试,模拟乏味且痛苦的。放弃单元测试并编写集成测试,这些测试实际上会引起供应商的注意。

请注意,这些测试应在部署后作为部署后的自动化活动运行。它们不作为单元测试的一部分或构建过程的一部分运行。

有时,在应用程序的某些部分中,集成测试比单元测试更合适。使代码“可测试”必须经历的障碍有时是有害的。


2
当我阅读您的答案时,我想到的第一件事是“对于PDF不切实际,因为在二进制级别比较文件并不能告诉您发生了什么更改或更改是否有问题”。然后我发现了这个较旧的SO帖子,表明它实际上可以工作(因此+1)。
Doc Brown

SO链接中的@DocBrown工具不会以二进制级别比较PDF。它比较页面的结构和内容。PDF内部结构:safaribooksonline.com/library/view/pdf-explained/9781449321581/...
linuxunil

1
@linuxunil:是的,正如我所写的,我首先想到的是……但是后来我找到了上面提到的解决方案。
布朗

哦..我明白了..也许最后答案中的“实际上可能有效”让我感到困惑。
linuxunil '16

1

据我了解,此讨论的重点是包装创建自动化的机会,而不是包装思想和实施指南。我将尝试从这个想法中抽象出来,因为这里已经有很多关于它的内容。

我看到我们正在使用.NET技术,因此我们掌握了强大的反射功能。您可以考虑:

  1. .NET包装类生成器之类的工具。我没有使用过该工具,而且我知道它可以在旧技术堆栈上运行,但是也许适合您的情况。当然,应该分别研究代码质量,对依赖关系反转的支持和接口隔离。也许还有其他类似的工具,但我没有搜索太多。
  2. 编写您自己的工具,该工具将在引用的程序集中搜索公共方法/接口,并进行映射和代码生成。欢迎社会各界贡献!
  3. 如果.NET并非如此...也许请看这里

我相信这种自动化可能构成一个基本点,但不是最终解决方案。将需要手动代码重构...或者在最坏的情况下,需要重新设计,因为我完全同意svidgen写道:

根据所需的界面构建应用程序;不针对第三方API。


好吧,至少一个想法如何的问题,可能会进行处理。但是,我认为不是基于自己的经验来处理此用例吗?
布朗

这个问题是基于我自己在软件工程领域的经验(包装器,反射器,di)!关于工具-我没有使用组装工具中所述的工具。
tom3k '16

0

创建包装器库时,请遵循以下准则:

  • 仅公开您现在需要的一小部分第三方库(并按需扩展)
  • 保持包装尽可能的薄(不存在任何逻辑)。

1
只是想知道为什么您为什么要为一篇帖子错过3个投票而错过了问题的重点。
布朗
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.