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

Jef*_*ser 6 .net c# unit-testing

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

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

出于多种原因,我无法在不对使用我的API的程序员开发的应用程序进行重大更改的情况下显着更改设计.但是,我仍然希望使用此API的内部和外部开发人员有机会编写单元测试并能够模拟API中的对象.

有几种方法可以想到,但它们似乎都不是很好:

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

  2. 强制所有使用想要对其进行单元测试的API的开发人员购买TypeMock.这似乎很苛刻,迫使人们为每个开发人员支付300美元+,并可能要求他们学习不同于他们习惯的模拟对象工具.

  3. 浏览整个项目并将所有方法设为虚拟.这将允许使用像MoqRhino Mocks这样的免费工具来模拟对象,但它可能会为从未打算从中派生的类带来安全风险.此外,这可能会导致重大变化.

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

  5. 与#4类似,我可以遍历每个文件并为每个方法和正文添加类似"#ifdef UNITTEST"的内容,以完成与工具相同的操作.这不需要外部工具,但它会用很多丑陋的"#ifdef"来污染代码库.

还有其他我认为不合适的东西吗?像#4中提到的工具是否已经存在?

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

目前已经 几个 问题上堆栈溢出认为是通用性有关改造现有的应用程序,使之可测试的,但似乎没有解决我的(特别是在与众多第三方开发者广泛使用的API的情况下)的关注.我也知道" 有效地使用遗留代码 "并认为它有很好的建议,但我正在寻找一种特定的.net方法,考虑到上面提到的限制.

更新:到目前为止我很欣赏答案.一说帕特里克Hägne长大的是"为什么不能提取接口?" 这确实起到了一定作用,但是存在一些问题,例如现有的设计有很多我们采用暴露具体类的情况.例如:

public class UserRepository 
{ 
    public UserData GetData(string userName) 
    {
        ...
    } 
}
Run Code Online (Sandbox Code Playgroud)

如果给予"IUserData",那么期望具体类(例如"UserData")的现有客户将会中断.

此外,正如评论中所提到的,我们会在某个类中接受一个类,然后为了方便而公开它.如果我们接受一个接口然后不得不将它作为具体类公开,这可能会导致问题.

重大改写或重新设计的最大挑战是对当前的API进行了大量投资(数千小时的开发,可能还有第三方培训).因此,虽然我同意一个更好的SOLID设计重写或抽象层(最终可能成为新的API),专注于接口分离原则等项目,从可测试性的角度来看会是一个加分,但它可能是一个很大的事业,可能目前不能成本合理.

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

另外,正如Chad Myers所提到的,这个问题解决了.NET框架本身在某些领域面临的类似问题.

我意识到我可能正在寻找一个不存在的"银弹",但所有的帮助都值得赞赏.重要的是保护许多第三方开发人员的巨大时间投资以及创建当前API的巨大现有开发.

所有答案,尤其是那些考虑问题的业务方面的答案,都将得到认真审查.谢谢!

cha*_*ers 4

您真正要问的是,“我如何设计我的 API,并牢记 SOLID 和类似的原则,以便我的 API 能够与其他 API 良好配合?” 这不仅仅是可测试性的问题。如果您的客户在使用您的代码测试他们的代码时遇到问题,那么他们在编写/使用您的代码时也会遇到问题,所以这是一个比可测试性更大的问题。

简单地提取接口并不能解决问题,因为很可能您现有的类接口(具体类作为其方法/属性公开的内容)在设计时并未考虑接口隔离原则,因此提取的接口会存在各种问题(某些您在之前的答案的评论中提到过)。

我喜欢将此称为 IHttpContext 问题。如您所知,由于 HttpContext.Current 的“神奇单例依赖”问题,ASP.NET 很难进行测试。如果没有像 TypeMock 使用的花哨技巧,HttpContext 是不可模拟的。简单地提取 HttpContext 的接口并没有多大帮助,因为它太大了。最终,即使是 IHttpContext 也会成为测试的负担,以至于除了尝试模拟 HttpContext 本身之外几乎不值得做任何事情。

识别对象职责、适当地划分接口和交互,以及在设计时牢记开放/封闭原则,这并不是您试图强行/塞入在没有考虑这些原则的情况下设计的现有 API 中的事情。

我不想给你留下如此严峻的答案,所以我会给你一个积极的建议:你如何代表你的客户承担所有的悲伤,并在你的旧 API 之上创建某种服务/外观层。该服务层必须处理 API 的细节和难题,但将提供一个漂亮、干净、可靠的公共 API,您的客户可以以更少的摩擦使用它。

这还有一个额外的好处,那就是允许您慢慢替换部分 API,并最终使您的新 API 不仅仅是一个外观,它本身就是 API(旧的 API 已被淘汰)。