ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

单元测试Unit Testing [41]

2021-06-13 15:30:59  阅读:137  来源: 互联网

标签:依赖 实现 代码 Testing 单元测试 接口 进程 public Unit


8.4 使用接口来抽象出依赖关系

  在单元测试领域,最容易被误解的主题之一是接口的使用。开发者们经常把引入接口的原因归结为无效的理由,结果就是倾向于过度使用接口。在本节中,我将对这些无效的理由进行阐述,并说明在哪些情况下使用接口是可取的,哪些是不可取的。

8.4.1 接口和松耦合

  许多开发者为进程外的依赖关系引入接口,如数据库或消息总线,即使这些接口只有一个实现。这种做法现在已经很普遍了,几乎没有人质疑它。你会经常看到类似于以下的类-接口对。

public interface IMessageBus
public class MessageBus: IMessageBus
public interface IUserRepository
public class UserRepository: IUserRepository

  使用这种接口的普遍理由是,它们有助于

  • 抽取进程外的依赖关系,从而实现松耦合
  • 在不改变现有代码的情况下增加新功能,从而遵守开放-封闭原则(OCP)。

  这两个原因都是误解。只有一个实现的接口不是抽象,也不比实现这些接口的具体类更能提供松散耦合。真正的抽象是被发现的,而不是被发明的。根据定义,这种发现是在事后进行的,即当抽象已经存在但还没有在代码中明确定义时。因此,要使一个接口成为真正的抽象,它必须至少有两个实现。
  第二个原因(在不改变现有代码的情况下增加新功能的能力)是一个误解,因为它违反了一个更基本的原则:YAGNI。YAGNI是 "你不会需要它 "的意思,主张不要在现在不需要的功能上投入时间。你不应该开发这种功能,也不应该修改你现有的代码以考虑将来出现这种功能。两个主要原因如下:

  • 机会成本——如果你把时间花在业务人员目前不需要的功能上,你就把这些时间从他们现在需要的功能上挪开。此外,当业务人员最终需要所开发的功能时,他们对该功能的看法很可能已经发生变化,而你仍然需要调整已经写好的代码。 这样的活动是浪费的。 当实际需求出现时,从头开始实现这些功能会更有利。
  • 项目中的代码越少越好。在没有迫切需求的情况下引入代码,会不必要地增加你的代码库的拥有成本。最好是将新功能的引入推迟到项目的最后阶段。

提示 编写代码是解决问题的一种昂贵方式。 解决方案所需的代码越少,代码越简单,就越好。

  在一些特殊情况下,YAGNI并不适用,但这种情况很少,也很不常见。 关于这些情况,请参阅我的文章《OCP与YAGNI》,网址是:https://enterprise-craftsmanship.com/posts/ocp-vs-yagni。

8.4.2 为什么要为进程外的依赖关系使用接口?

  那么,假设每个接口只有一个实现,为什么还要用接口来处理进程外的依赖关系呢?真正的原因是更加实际和朴实的。它是为了实现mocks,就这么简单。没有接口,你就无法创建一个测试替身,从而无法验证被测系统和进程外依赖关系之间的交互。
  因此,不要为进程外的依赖关系引入接口,除非你需要模拟出这些依赖关系。你只需要模拟出非托管的依赖关系,所以这个准则可以归结为:只对非托管的依赖关系使用接口。 仍然可以明确地将托管的依赖注入到控制器中,但要使用具体的类来实现。
  请注意,真正的抽象(有一个以上实现的抽象)可以用接口来表示,无论你是否mock它们。然而,出于mock以外的原因引入一个只有一个实现的接口是违反YAGNI的。
  在列表8.2中你可能已经注意到,UserController现在通过构造函数明确地接受了消息总线和数据库,但只有消息总线有相应的接口。数据库是一个被管理的依赖关系,因此不需要这样的接口。这里是控制器:

public class UserController {
    private readonly Database _database;
    private readonly IMessageBus _messageBus;
    public UserController(Database database, IMessageBus messageBus) {
        _database = database;
        _messageBus = messageBus;
    }
    public string ChangeEmail(int userId, string newEmail) {
        /* the method uses _database and _messageBus */ 
    }
}

注意 你可以在不使用接口的情况下模拟出一个依赖关系,方法是将该依赖关系中的方法变成虚拟的,并使用该类本身作为模拟的基础。这种方法比使用接口的方法要差一些。我将在第11章中进一步解释接口与基类的问题。

8.4.3 为进程中的依赖关系使用接口

  你有时会看到代码库中的接口不仅支持进程外的依赖,也支持进程内的依赖。比如说。

public interface IUser{
	int UserId { get; set; }
	string Email { get; }
	string CanChangeEmail();
	void ChangeEmail(string newEmail, Company company);
}
public class User: IUser {
	/* ... */ 
}

  假设IUser只有一个实现(这种特定的接口总是只有一个实现),这就是一个巨大的红旗。 就像进程外依赖关系一样,为域类引入一个只有一个实现的接口的唯一原因是为了实现mock。 但与进程外依赖不同的是,你不应该检查域类之间的交互,因为这样做会导致脆弱性测试:测试与实现细节相耦合,从而在抵抗重构的尺度上失败(关于mocks和测试脆弱性的更多细节,请参见第5章)。

标签:依赖,实现,代码,Testing,单元测试,接口,进程,public,Unit
来源: https://blog.csdn.net/u013716859/article/details/117876876

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有