代码之家  ›  专栏  ›  技术社区  ›  Jeff Moser

如何使现有的公共API可供使用它的外部程序员测试?

  •  6
  • Jeff Moser  · 技术社区  · 17 年前

    我有一个C公共API,许多第三方开发人员在它上面编写了自定义应用程序。此外,API被内部开发人员广泛使用。

    这个API的编写并没有考虑到可测试性:大多数类方法都不是虚拟的,并且没有将事物分解到接口中。此外,还有一些辅助静态方法。

    由于许多原因,我不能在不破坏由程序员使用我的API开发的应用程序的情况下显著地更改设计。但是,我仍然希望给使用此API的内部和外部开发人员机会编写单元测试,并能够模拟API中的对象。

    有几种方法浮现在脑海中,但似乎都不是很好:

    1. 传统的方法是强制开发人员创建一个他们控制的代理类,可以与我的API对话。这在实践中不起作用,因为现在有数百个类,其中许多类实际上是强类型的数据传输对象,复制和维护起来很困难。

    2. 强制所有使用API的开发人员购买 TypeMock . 这似乎很难迫使人们为每个开发者支付300美元以上的费用,并且可能要求他们学习一种与以前不同的模拟对象工具。

    3. 完成整个项目并使所有方法都是虚拟的。这将允许使用自由工具模拟对象,如 Moq Rhino Mocks 但是,它可能会为从未打算派生的类带来安全风险。此外,这可能导致破坏性的变化。

    4. 我可以创建一个给定输入程序集的工具,它将输出具有相同名称空间、类和成员的程序集,但会使所有方法都成为虚拟的,并且会使方法体只返回返回类型的默认值。然后,我可以在每次发布API更新时都发布这个虚拟测试程序集。然后,开发人员可以针对虚拟程序集编写API测试,因为它具有非常可模拟的虚拟成员。这可能有效,但为它编写自定义工具似乎有点乏味,而且我似乎找不到一个现有的能够很好地完成它的工具(尤其是与泛型一起工作的工具)。此外,它还有一个复杂的问题,即它要求开发人员使用两个可能过时的不同程序集。

    5. 类似于4,我可以浏览每个文件,并在每个方法和主体中添加类似于“ifdef unittest”的内容,以实现工具所能做到的。这不需要外部工具,但它会用许多难看的“ifdef”污染代码库。

    还有什么我认为不适合的吗?像我在4中提到的工具已经存在了吗?

    同样,复杂的因素是这是一个相当大的API(数百个类和大约10个文件),并且现有的应用程序正在使用它,这使得很难进行剧烈的设计更改。

    那里 have been several questions 栈上溢出是对现有应用程序进行改造以使其可测试的通用方法,但似乎没有一个能解决我所关心的问题(特别是在许多第三方开发人员使用广泛的API的上下文中)。我也知道” Working Effectively With Legacy Code “我认为它有很好的建议,但是考虑到上面提到的限制,我正在寻找一种特定的.NET方法。

    更新: 我很感激迄今为止的回答。一个 Patrik Hägne 提出了“为什么不提取接口?”这确实在一定程度上起作用,但也存在一些问题,例如现有的设计有许多情况,在这些情况下,我们将公开一个具体的类。例如:

    public class UserRepository 
    { 
        public UserData GetData(string userName) 
        {
            ...
        } 
    }
    

    如果给现有客户一个“iuserdata”,那么期望具体类(例如“userdata”)的客户将中断。

    此外,正如注释中所提到的,在某些情况下,我们接受一个类,然后为了方便而公开它。如果我们接受一个接口,然后将它作为一个具体的类公开,这可能会导致问题。

    对重大重写或重新设计的最大挑战是对当前API进行了巨大的投资(数千小时的开发和可能同样多的第三方培训)。所以,虽然我同意 SOLID 设计重写或抽象层(最终可能成为新的API),重点关注诸如 Interface Separation Principle 从可测试性的角度来看,这将是一个优势,这将是一项目前可能无法证明成本合理的大事业。

    我们确实对当前的API进行了测试,但它是更复杂的集成测试,而不是单元测试。

    此外,如前所述 Chad Myers 这个问题解决了.NET框架本身在某些领域面临的类似问题。

    我意识到我可能在这里寻找一颗不存在的“银弹”,但是所有的帮助都是值得感激的。重要的是保护许多第三方开发人员的巨额时间投资,以及为创建当前API而进行的大量现有开发。

    所有的答案,特别是那些考虑到问题的商业方面的答案,都将被仔细审查。谢谢!

    6 回复  |  直到 17 年前
        1
  •  4
  •   community wiki chadmyers    17 年前

    你真正要问的是,“我如何设计我的API,并考虑到坚实和类似的原则,这样我的API就可以很好地与其他人一起使用?”这不仅仅是关于可测试性。如果您的客户在用您的代码测试他们的代码时遇到问题,那么他们在用您的代码编写/使用他们的代码时也遇到问题,因此这不仅仅是可测试性问题。

    简单地提取接口并不能解决这个问题,因为您现有的类接口(具体类作为其方法/属性公开的内容)很可能没有考虑到接口分离原则,因此提取的接口会有各种各样的问题(其中一些问题是您在对以前的answe的注释中提到的)。R)。

    我喜欢称之为IHttpContext问题。如您所知,由于httpcontext.current的“magic singleton依赖性”问题,ASP.NET很难进行测试。没有诸如typemock使用的花哨技巧,httpContext是不可模仿的。简单地提取一个httpContext接口并没有那么大的帮助,因为它太大了。最终,即使是ihtpContext也会成为测试的负担,以至于除了试图模仿httpContext本身之外,几乎不值得做更多的工作。

    识别对象责任,适当地划分接口和交互,并在设计时考虑开放/关闭原则,这不是你想要的,并且试图强制/死记硬背地进入一个没有这些原则的现有API。

    我不想给你这样一个冷酷的回答,所以我会给你一个积极的建议:你如何代表你的客户接受所有的悲伤,在你的旧API之上做一些服务/外观层。这个服务层将不得不处理您的API的细节和痛苦,但是将提供一个好的、干净的、可靠的、友好的公共API,您的客户可以用它来减少摩擦。

    这还有一个额外的好处,即允许您慢慢地替换部分API,并最终使其成为新的API,它不仅仅是一个外观,而是API(旧的API被逐步淘汰)。

        2
  •  2
  •   Kim Stebel    17 年前

    另一种方法是创建API的独立分支,并在那里执行选项3。然后,您只需维护这两个版本,并拒绝使用前者。在大多数情况下,将一个分支的更改合并到另一个分支应该自动工作。

        3
  •  1
  •   Patrik Hägne    17 年前

    作为对编辑的回复,界面提取在这里确实非常有效:

    public interface IUserRepository
    {
        IUserData GetData(string userName);
    }
    
    public class UserRepository 
        : IUserRepository
    {
        // The old method is not touched.
        public UserData GetData(string userName)
        {
            ...    
        }
    
        // Explicitly implement the interface method.
        IUserData IUserRepository.GetData(string userName)
        {
            return this.GetData(userName);
        }
    }
    

    正如我在评论中所说,这可能不是每个地方都能做到的。我认为您应该确定API中的一些要点,在这些要点中,客户能够伪造交互并从那里开始是非常重要的。您不必对整个API进行完全重写,但它可以逐渐转换。

        4
  •  0
  •   Patrik Hägne    17 年前

    您没有提到的一种方法(在大多数情况下我更喜欢这种方法)是为希望API用户能够伪造的类提取接口。不知道您的API,它中的每个类都必须提取它的接口。

        5
  •  0
  •   Brody    17 年前

    第三方用户不应该测试您的API。他们希望根据您的API测试他们的代码,因此他们需要创建API等的模拟,但他们将依赖您对API的测试来确保它的工作。或者这就是你的意思?您想让您的API易于测试吗?

    在这种情况下,重新开始,这次考虑测试人员:)

        6
  •  0
  •   Robert Venables    17 年前

    我同意金。为什么不使用您所解释的最佳实践重新编写核心API,并提供一组代理/适配器类,这些类公开旧接口,但与新的API交谈?

    旧的开发人员自然会被鼓励迁移到新的API,但不会被强制立即迁移。新开发人员只需使用新的API。如果您担心开发人员继续使用旧的API,请为旧的API接口宣布一个EOL。

    推荐文章