依赖注入(DI)是一种众所周知的流行模式。大多数工程师都知道它的优点,例如:


使单元测试中的隔离变得可能/容易
明确定义类的依赖项
促进良好的设计(单一职责原则) (SRP)例如)
快速启用交换实现(例如DbLogger而不是ConsoleLogger

我认为,业界普遍认为DI是一种很好的有用模式。目前没有太多批评。社区中提到的缺点通常很小。其中一些:


增加类的数量
创建不必要的接口

当前,我们与同事讨论体系结构设计。他相当保守,但思想开放。他喜欢质疑一些我认为很好的事情,因为IT领域的许多人只是复制最新趋势,重复优势,而且通常不要考虑太多-不要分析得太深。

我想问的是:


只有一个实现时,我们应该使用依赖注入吗?
我们应该禁止创建除语言/框架对象之外的新对象吗?
如果我们不打算对特定的类进行单元测试,那么是否注入了一个不好的实现主意(假设我们只有一个实现,所以我们不想创建“空”接口)?


评论

您是否真的在问依赖注入作为一种模式,还是在问使用DI框架?这些确实是截然不同的事情,您应该弄清您感兴趣的问题的哪一部分,或者明确地询问这两个问题。

@Frax关于模式,而不是框架

您将依赖倒置与依赖注入混为一谈。前者是设计原则。后者是一种用于构造对象层次结构的技术(通常使用现有工具来实现)。

我经常使用真实的数据库编写测试,而根本没有模拟对象。在许多情况下效果很好。然后,您通常不需要接口。如果您有UserService,则该类只是逻辑的持有者。它被注入数据库连接,并且测试在回滚的事务内部运行。许多人会称这种做法不好,但我发现这样做非常有效。不需要仅仅为了测试而扭曲代码,您就可以发现集成测试中的错误。

DI几乎总是好的。它的坏处是很多人认为他们了解DI,但是他们所知道的就是如何使用一些奇怪的框架,甚至不确定自己在做什么。如今,DI受货运崇拜编程的影响很大。

#1 楼

首先,我想将设计方法与框架的概念区分开。在最简单,最基本的级别上进行依赖项注入很简单:


父对象提供了子对象所需的所有依赖项。


它。请注意,其中的任何内容都不需要接口,框架,任何注入样式等。为了公平起见,我20年前首次了解了这种模式。这不是什么新鲜事。

由于有两个以上的人对“父母和子女”一词感到困惑,因此在依赖注入的情况下:


父母是实例化并配置它使用的子对象的对象
子对象是旨在被动实例化的组件。即它旨在使用父级提供的任何依赖关系,并且不会实例化其自身的依赖关系。

依赖关系注入是对象组合的一种模式。

为什么要使用接口?

接口是合同。它们的存在是为了限制两个对象之间的紧密耦合。并非每个依赖项都需要一个接口,但是它们可以帮助编写模块化代码。

当您添加单元测试的概念时,对于任何给定的接口,您可能都有两种概念上的实现:您想要的真实对象在应用程序中使用,以及用于测试依赖于该对象的代码的模拟对象或存根对象。对于接口而言,仅凭此一个理由就足够了。

为什么要使用框架?

本质上初始化并提供对子对象的依赖关系可能会令人望而生畏。框架具有以下优点:


自动装配组件依赖项
使用某种设置配置组件
自动化样板代码,因此您不必看到它写在多个位置。

它们还具有以下缺点:


父对象是一个“容器”,代码中没有任何内容
如果您不能在测试代码中直接提供依赖项,它会使测试变得更加复杂
它可以解决所有问题,从而降低初始化速度使用反射和许多其他技巧的依赖关系
运行时调试会更加困难,特别是如果容器在接口和实现该接口的实际组件之间注入代理(想到Spring内置的面向方面的编程)。容器是一个黑匣子,它们并不总是以促进调试过程的任何概念构建的。

总而言之,需要权衡取舍。对于没有很多活动部件的小型项目,没有什么理由使用DI框架。但是,对于已经为您准备了某些组件的更复杂的项目,可以证明该框架是合理的。

[Internet上的随机文章]呢?

该怎么办?它?很多时候,如果您不是以“一种真实的方式”来做事情,人们会变得过分热心,增加了很多限制,并谴责您。没有一种真正的方法。看看您是否可以从本文中提取有用的内容,而忽略您不同意的内容。

总而言之,请自己考虑一下并尝试一下。

“老头子”

尽可能多地学习。在70年代进入工作状态的许多开发人员中,您会发现他们已经学会了不要对许多事情保持教条。他们已经使用了数十年的方法可以产生正确的结果。

我有幸可以使用其中的一些方法,并且它们可以提供一些残酷而诚实的反馈,这些反馈可以使很多感。在他们看到价值的地方,他们将这些工具添加到他们的曲目中。

评论


@CarlLeth,我曾经使用过从Spring到.net变体的许多框架。 Spring将允许您使用一些反射/类加载器黑魔法将实现注入到私有字段中。测试如此构建的组件的唯一方法是使用容器。 Spring确实有JUnit运行器来配置测试环境,但是它比自己设置要复杂得多。是的,我只是举了一个实际的例子。

–贝琳·洛里奇(Berin Loritsch)
18年5月29日21:00

当我戴着故障排除程序/维护人员的帽子时,我发现通过框架对DI构成一个障碍:它们所提供的怪异动作使离线调试更加困难。在最坏的情况下,我必须运行代码来查看如何初始化和传入依赖项。您在“测试”的上下文中提到了这一点,但是实际上,如果您只是开始查看源代码,那就更糟了,永远不要请尝试使其运行(可能涉及大量设置)。仅仅看一眼就阻碍了我分辨代码的能力是一件坏事。

– Jeroen Mostert
18年5月30日在11:17

接口不是合同,它们只是API的接口。契约暗示着语义。该答案使用特定于语言的术语和特定于Java / C#的约定。

–弗兰克·希勒曼
18年5月30日在19:54

@BerinLoritsch您自己答案的重点是DI原则!=任何给定的DI框架。 Spring可以做可怕的,不可原谅的事情,这是Spring的缺点,而不是一般的DI框架。良好的DI框架可帮助您遵循DI原则,而不会遇到麻烦。

–卡尔·莱斯(Carl Leth)
18年5月31日在6:32



@CarlLeth:所有DI框架都旨在删除或自动化程序员不希望阐明的某些事情,它们只是在方式上有所不同。就我所知,它们都消除了仅通过查看A和B来了解A类和B类如何(或是否)相互作用的能力-至少,您还需要查看DI的设置/配置/约定。对于程序员来说,这不是一个问题(实际上正是他们想要的),但是对于维护者/调试者(以后可能是同一位程序员)来说,这可能是一个问题。即使您的DI框架“完美”,这也是您要做出的权衡。

– Jeroen Mostert
18年5月31日在6:57



#2 楼

像大多数模式一样,依赖注入是解决问题的一种方法。因此,首先要问您是否有问题。如果不是这样,那么使用该模式很可能会使代码变得更糟。

如果可以减少或消除依赖性,请首先考虑。在所有其他条件相同的情况下,我们希望系统中的每个组件都具有尽可能少的依赖关系。如果依赖关系消失了,注入或不注入的问题就变得无聊了!

考虑一个模块,该模块从外部服务下载一些数据,对其进行解析,然后执行一些复杂的分析,并将结果写入一份文件。

现在,如果对外部服务的依赖关系进行了硬编码,那么很难对这个模块的内部处理进行单元测试。因此,您可能决定将外部服务和文件系统作为接口依赖项进行注入,这将允许您注入模拟,从而使对内部逻辑进行单元测试成为可能。

但是更好的解决方案是将分析与输入/输出分开。如果将分析提取到没有副作用的模块,则测试起来会容易得多。请注意,模拟是一种代码气味-并非总是可以避免的,但是总的来说,如果您不依赖模拟就可以进行测试会更好。因此,通过消除依赖性,可以避免DI应该缓解的问题。请注意,这样的设计还更好地遵守了SRP。

我要强调的是,DI不一定会促进SRP或其他好的设计原则,例如关注点分离,高内聚/低耦合等。它也可能产生相反的效果。考虑一个A类,该A类在内部使用另一个B类。 B仅由A使用,因此被完全封装,可以视为实现细节。如果将其更改为将B注入到A的构造函数中,那么您已经暴露了该实现的细节,现在有关此依赖项以及如何初始化B的知识,B的生存期等必须分别存在于系统中的其他位置。因此,您的整体架构较差,而且存在泄漏隐患。

另一方面,在某些情况下,DI确实很有用。例如,对于具有记录器之类的副作用的全局服务。

问题在于模式和体系结构本身成为目标而不是工具成为目标。只是问“我们应该使用DI吗?”有点像把马车推到马前。您应该问:“我们有问题吗?”和“解决此问题的最佳解决方案是什么?”

您的问题的一部分归结为:“我们是否应该创建多余的接口来满足模式的需求?”您可能已经意识到了答案-绝对不是!否则,任何人告诉您都在试图向您出售产品-可能是昂贵的咨询时间。仅当接口表示抽象时,它才有价值。仅模仿单个类的表面的接口称为“头接口”,这是已知的反模式。

评论


我完全同意!还要注意,为此而进行的模拟操作意味着我们实际上并未在测试实际的实现。如果A在生产中使用B,但仅针对MockB进行了测试,则我们的测试不会告诉我们它是否可以在生产中使用。当域模型的纯组件(没有副作用)相互注入和嘲笑时,结果是浪费了每个人的时间,庞大而脆弱的代码库以及对生成系统的低置信度。模拟系统的边界,而不是在同一系统的任意部分之间。

–Warbo
18年5月29日在19:54

@CarlLeth为什么您认为DI使代码“可测试和可维护”,而使没有代码的代码更少? JacquesB认为副作用是会损害可测试性/可维护性的东西,这是正确的。如果代码没有副作用,我们不在乎它在什么地方,何时何地调用其他代码。我们可以使其简单直接。如果代码有副作用,我们必须注意。 DI可以从函数中消除副作用,并将其放入参数中,从而使这些函数更易于测试,但程序更加复杂。有时这是不可避免的(例如DB访问)。如果代码没有副作用,那么DI就是无用的复杂性。

–Warbo
18年5月29日在22:59



@CarlLeth:DI是解决使代码具有隔离性的问题的一种解决方案,如果它具有禁止使用的依赖项。但这并不会降低整体复杂性,也不会使代码更具可读性,这意味着它不一定会提高可维护性。但是,如果可以通过更好地分离关注点来消除所有这些依赖关系,则这将完全“抵消” DI的好处,因为它满足了对DI的需求。这通常是使代码同时更可测试和可维护的更好解决方案。

–布朗博士
18年5月30日在4:34

@Warbo这是原始方法,可能仍然是唯一有效的嘲笑方法。即使在系统边界,也很少需要它。人们确实浪费大量时间来创建和更新几乎毫无价值的测试。

–弗兰克·希勒曼
18年5月30日在19:49

@CarlLeth:好的,现在我明白了误解是从哪里来的。您正在谈论依赖倒置。但是,这个问题,这个答案和我的评论都是关于DI =依赖注入的。

–布朗博士
18年5月31日在6:37



#3 楼

根据我的经验,依赖项注入有许多缺点。首先,使用DI不能像宣传的那样简化自动化测试。使用接口的模拟实现对类进行单元测试,可以验证该类如何与接口交互。也就是说,它使您可以单元测试被测类如何使用接口提供的协定。但是,这提供了更大的保证,即可以保证被测类在接口中的输入是预期的。它几乎不能保证测试中的类会像预期的那样响应接口的输出,因为它几乎是通用的模拟输出,它本身会受到错误,过分简化等的影响。简而言之,它不能让您验证该类在接口的实际实现中是否会按预期方式运行。

其次,DI使得导航代码变得更加困难。当尝试导航到用作函数输入的类的定义时,接口可以是从小麻烦(例如,只有一个实现的地方)到主要的时间槽(例如,使用像IDisposable这样的过于通用的接口)的任何东西尝试查找正在使用的实际实现时。这可以使诸如“我需要在打印此日志记录语句之后立即发生的代码中修复空引用异常”这样的简单练习变成一天的工作。

第三,DI和框架的使用是一把双刃剑。它可以大大减少常规操作所需的样板代码量。但是,这是以需要特定DI框架的详细知识来理解这些通用操作如何实际连接在一起为代价的。要了解如何将依赖项加载到框架中并在框架中添加新的依赖项以进行注入,可能需要阅读大量的背景资料并遵循有关该框架的一些基本教程。这会将一些简单的任务变成相当耗时的任务。

评论


我还要补充一点,您注入的次数越多,启动时间就越长。大多数DI框架在启动时都会创建所有可注入的单例实例,无论它们在何处使用。

–罗德尼·巴尔巴蒂(Rodney P. Barbati)
18年5月30日在3:06

如果您想使用真实的实现(而不是模拟)来测试类,则可以编写功能测试-与单元测试类似的测试,但不使用模拟。

–BЈовић
18年5月30日在5:57



@ RodneyP.Barbati我想问“大多数”。在我使用过的工具中,默认情况下只有Spring这样做(这样做是为了立即清除注入错误),您可以选择不使用它。 HK2,Guava和Dagger都在注入时创建实例,而不是在应用程序启动时创建实例。

–Logan领取
18年5月30日在6:15

我认为您的第二段需要更加细致入微:DI本身不会使浏览代码更加困难。简单来说,DI只是遵循SOLID的结果。增加复杂性的是使用不必要的间接和DI框架。除此之外,这个答案打在了头上。

–康拉德·鲁道夫(Konrad Rudolph)
18年5月30日在10:11

在确实需要依赖注入的情况之外,依赖注入也是一个警告信号,表明可能会大量存在其他多余的代码。高管常常惊讶地发现,开发人员为了复杂性而增加了复杂性。

–弗兰克·希勒曼
18年5月30日在19:43

#4 楼

我遵循Mark Seemann从“ .NET中的依赖注入”的建议-推测。

当您具有“易失性依赖”时,应使用DI。

因此,如果您认为将来可能有多个实现,或者实现可能会发生变化,请使用DI。否则,new可以。

评论


请注意,他还为面向对象和功能语言提供了不同的建议blog.ploeh.dk/2017/01/27/…

– jk。
18年5月29日在11:51

那是好点。如果默认情况下我们为每个依赖关系创建接口,那么它一定会对YAGNI不利。

– Landeyo
18年5月29日在12:05

您可以提供“ .NET中的依赖项注入”参考吗?

– Peter Mortensen
18年5月29日在23:39

如果您正在进行单元测试,那么您的依赖性很有可能是不稳定的。

– Jaquez
18年5月30日在16:12

好消息是,开发人员始终能够以完美的准确性预测未来。

–弗兰克·希勒曼
18年5月30日在19:44

#5 楼

我已经在一些答案中提到了我最大的关于DI的烦恼,但是在这里我将对其进行扩展。 DI(因为今天大多数情况下都是使用容器等完成的),确实损害了代码的可读性。可以说,代码可读性是当今大多数编程创新背后的原因。就像有人说的那样-编写代码很容易。阅读代码很难。但这也非常重要,除非您正在编写某种微小的一次性写入实用程序。

DI在这方面的问题在于它不透明。容器是一个黑盒子。对象只是从某个地方出现而您却不知道-谁构造它们以及何时构造?什么传递给构造函数?我要与谁共享此实例?谁知道...

当您主要使用接口时,IDE的所有“转到定义”功能都会烟消云散。要在不运行程序的情况下追踪程序的流程是非常困难的,只是一步一步地查看在该特定位置仅使用了WHICH接口实现。有时会有一些技术障碍阻止您逐步执行。而且即使可以,如果涉及到遍历DI容器的扭曲肠子,整个事务很快也会令人沮丧。

要有效地使用使用DI的一段代码,您可以必须熟悉它,并且已经知道去哪里。

评论


另一种选择是,显示所有带有长代码块的内脏以及其中几种策略,这甚至更难阅读和测试。不知道:重点是鸭子类型,不知道实现细节。您将高级别与低级别分开。如果这意味着“不透明”,那么我认为您选择的不是全部。 >并且,当您主要使用接口时,IDE的所有“转到定义”功能都会烟消云散。糟透了

– ahnbizcad
20年11月7日,下午3:36

@ahnbizcad-如果您有一段代码可以采用多种策略,那么可以肯定的是,请使用接口或继承或其他方法。那就是它的目的。但是90%的时间你没有。只有一个“ PersonRepository”类,为什么将其隐藏在IPersonRepository后面?当您不熟悉该项目时,这只会使您感到困惑,并使您花费数小时来寻找该接口的实现及其实现方式。以后会更容易,但是即使您知道从何处搜索,每次您需要从接口跳转到实现时,它仍然像是一个减速器。

– Vilx-
20-11-15在16:28



#6 楼


快速启用切换实现(例如,用DbLogger代替ConsoleLogger)。

虽然一般来说DI当然是一件好事,但我建议不要盲目使用它。例如,我从不注入记录器。 DI的优点之一是使依赖关系明确而明确。将ILogger列为几乎每个类的依存关系没有意义-只是混乱。记录器的责任是提供所需的灵活性。我所有的记录器都是静态的final成员,当我需要非静态的记录器时,我可以考虑添加一个记录器。

增加类的数量

这是给定缺点DI框架或模拟框架,而不是DI本身。在大多数地方,我的课程取决于具体的课程,这意味着需要零样板。 Guice(一个Java DI框架)默认将一个类与其自身绑定,而我只需要在测试中重写绑定(或手动进行绑定)。

创建不必要的接口

我仅在需要时创建接口(这种情况很少见)。这意味着有时候我必须用一个接口替换所有出现的类,但是IDE可以为我做这件事。

只有一个实现时,我们应该使用依赖注入吗?

是的,但要避免增加任何样板。

我们应该禁止创建除语言/框架对象之外的新对象吗?

否。值(不可变的)和数据(可变的)类将很多,在这些实例中,实例被创建并传递,并且在注入它们时毫无意义,因为它们从未存储在另一个对象中(或仅存储在另一个对象中)。在其他类似的对象中)。
对于它们,您可能需要注入工厂,但是大多数情况下,这是没有意义的(例如@Value class NamedUrl {private final String name; private final URL url;};您实际上不需要这里的工厂,也没有什么要注入)。

如果我们不打算对特定的类进行单元测试,那么注入单个实现是一个坏主意(假设我们只有一个实现,因此我们不想创建“空”接口)?

IMHO只要它不会导致代码膨胀就可以了。请注入依赖项,但不要创建接口(也不要创建疯狂的配置XML!),因为稍后您可以轻松地进行操作。
实际上,在我当前的项目中,有四个类(数百个类),其中我决定从DI中排除它们,因为它们是在很多地方使用的简单类,包括数据对象。

大多数DI框架的另一个缺点是运行时开销。可以将其转移到编译时(对于Java,有Dagger,对其他语言一无所知)。
另一个缺点是,魔术无处不在,可以进行调整(例如,使用Guice时我禁用了代理创建) 。

评论


“当我需要一个非静态记录器时,我可以考虑注入它。”您如何对该代码进行单元测试?

– Ryan Haney
20-10-3在23:04

#7 楼

我必须说,我认为整个依赖注入概念都被高估了。

DI是现代等同于全球价值的东西。您要注入的东西是全局单例和纯代码对象,否则,您将无法注入它们。为了使用给定的库(JPA,Spring Data等),大多数情况下会强制使用DI。在大多数情况下,DI为培育和培养意大利面条提供了理想的环境。

老实说,测试类的最简单方法是确保以一种可以覆盖的方法创建所有依赖项。 。然后创建一个从实际类派生的Test类,并重写该方法。

然后实例化Test类并测试其所有方法。你们中的某些人不清楚这一点-您正在测试的方法属于所测试类的方法。所有这些方法测试都在单个类文件中进行-与被测类关联的单元测试类。这里的开销为零-这是单元测试的工作方式。

在代码中,这个概念看起来像这样...

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}


结果与使用DI完全相同-也就是说,将ClassUnderTest配置为进行测试。

唯一的区别是此代码完全简洁,完全封装,易于编码,易于理解,速度更快,使用的内存更少,不需要替代配置,不需要任何框架,永远不会成为4页(WTF!)堆栈跟踪的原因,该堆栈跟踪完全包含您编写的ZERO(0)类,对于从初学者到Guru甚至只有一点点OO知识的人来说都是显而易见的(您会认为,但会弄错了)。

话虽如此,我们当然不能使用它-它太明显了,不够时髦。

归根结底,我对DI的最大担忧是我看到的那些项目惨遭失败,所有这些都是庞大的代码库,其中DI是将所有内容结合在一起的粘合剂。 DI不是一种体系结构-它实际上仅在少数情况下才有意义,其中大多数情况都是为了使用另一个库(JPA,Spring Data等)而被强制执行。在大多数情况下,在一个精心设计的代码库中,DI的大多数使用将在您日常开发活动发生的水平以下进行。

评论


您没有描述等效项,而是描述了依赖项注入的相反含义。在模型中,每个对象都需要知道其所有依赖项的具体实现,但是使用DI成为“主要”组件的责任-将适当的实现粘合在一起。请记住,DI与其他DI紧密相关,在这种情况下,您不希望高级组件对低级组件有硬性依赖。

–Logan领取
18年5月30日在6:21

当您只有一个级别的类继承和一个级别的依赖项时,它会很整洁。当它扩展时,它肯定会变成地狱吗?

–伊万
18年5月30日在11:04

如果使用接口并将initializeDependencies()移到构造函数中,则相同但更整洁。下一步,添加构造参数意味着您可以删除所有TestClass。

–伊万
18年5月30日在11:05

这有很多错误。就像其他人所说的,“ DI等效”示例根本不是依赖注入,它是对立的,表明您完全不了解该概念,并且还引入了其他潜在的陷阱:部分初始化的对象是代码异味,就像Ewan建议将初始化移到构造函数,然后通过构造函数参数传递它们。那你有DI ...

– Mindor先生
18年5月30日在23:14

添加到@ Mr.Mindor:还有一个更通用的反模式“顺序耦合”,它不仅适用于初始化。如果对象的方法(或更普遍地说是API的调用)必须以特定的顺序运行,例如只能在foo之后调用bar,这是一个错误的API。它声称提供了功能(栏),但是我们实际上不能使用它(因为可能尚未调用foo)。如果您要坚持使用initializeDependencies(anti?)模式,则至少应将其设为私有/受保护,并从构造函数中自动调用它,因此该API是真诚的。

–Warbo
18年5月31日在12:35

#8 楼

确实,您的问题归结为“单元测试不好吗?”

要插入的替代类的99%是支持单元测试的模拟。

如果不使用DI进行单元测试,就会遇到如何获取类以使用模拟数据或模拟服务的问题。可以说“逻辑的一部分”,因为您可能没有将其分离为服务。

还有其他方法可以做到这一点,但是DI是一种很好且灵活的方法。一旦将其用于测试,您几乎会被迫在任何地方使用它,因为您需要其他代码,即使是所谓的“穷人的DI”也可以实例化具体类型。

很难想象有如此糟糕的缺点,以至于单元测试的优势不堪重负。

评论


我不同意您关于DI是关于单元测试的主张。促进单元测试只是DI的优点之一,而且可以说不是最重要的优点之一。

–罗伯特·哈维(Robert Harvey)
18年5月29日在12:44



我不同意您的前提,即单元测试和DI如此接近。通过使用模拟/存根,我们使测试套件更具欺骗性:被测系统与真实系统之间的距离越来越远。从客观上讲这很糟糕。有时,这样做的好处在于:模拟的FS调用不需要清理;模拟的HTTP请求快速,确定性并可脱机工作;相反,每次我们在方法中使用硬编码的new时,我们都知道在生产中运行的同一代码正在测试期间运行。

–Warbo
18年5月29日在19:44

不,这不是“单元测试不好?”,而是“嘲笑(a)确实有必要,而(b)值得增加复杂性吗?”这是一个非常不同的问题。单元测试还不错(实际上没有人在争论这一点),总体而言,它绝对值得。但是,并非所有的单元测试都需要模拟,并且模拟确实会带来巨大的成本,因此至少应明智地使用它。

–康拉德·鲁道夫(Konrad Rudolph)
18年5月30日在9:17

@Ewan在您最后发表评论后,我认为我们不同意。我是说大多数单元测试不需要DI [框架],因为大多数单元测试不需要模拟。实际上,我什至可以将其用作启发代码质量的方法:如果大多数代码如果没有DI / mock对象就无法进行单元测试,那么您编写的错误代码就太正确了。大多数代码应高度分离,具有单一职责和通用性,并且可以单独进行简单测试。

–康拉德·鲁道夫(Konrad Rudolph)
18年5月30日在10:54



@Ewan您的链接为单元测试提供了一个很好的定义。按照这个定义,我的Order示例是一个单元测试:它正在测试一个方法(Order的总方法)。您抱怨它正在从2个类中调用代码,我的回答是什么?我们不是在一次测试“两个类”,而是在测试整个方法。我们不应该在乎方法是如何工作的:这些是​​实现细节。测试它们会导致脆弱性,紧密耦合等。我们只关心方法的行为(返回值和副作用),而不关心类/模块/ CPU寄存器/等。它在过程中使用。

–Warbo
18年5月31日在14:12