我们是否应该从一开始就设计代码以启用单元测试?


91

目前,我们的团队正在争论是否修改代码设计以允许单元测试是代码的味道,或者在什么程度上可以做到而又没有代码的味道。之所以如此,是因为我们只是刚刚开始实施几乎所有其他软件开发公司中都存在的实践。

具体来说,我们将提供一个非常薄的Web API服务。它的主要职责将是整理Web请求/响应并调用包含业务逻辑的基础API。

一个示例是我们计划创建一个将返回身份验证方法类型的工厂。我们不需要它继承一个接口,因为我们不希望它有任何具体类型。但是,要对Web API服务进行单元测试,我们需要模拟该工厂。

从本质上讲,这意味着我们要么设计Web API控制器类以接受DI(通过其构造函数或设置器),这意味着我们正在设计控制器的一部分,只是为了允许DI并实现我们原本不需要的接口,或者我们使用第三方框架(如Ninject)可以避免以这种方式设计控制器,但是我们仍然必须创建一个接口。

团队中的某些人似乎不愿意仅仅为了测试而设计代码。在我看来,如果您希望进行单元测试,则必须做出一些妥协,但是我不确定他们的担忧如何得到缓解。

需要明确的是,这是一个全新的项目,因此,它并不是真正地修改代码以进行单元测试。这是关于将要编写的代码设计为可单元测试的。


33
让我重复一遍:您的同事想要对新代码进行单元测试,但是他们拒绝以可单元测试的方式编写代码,尽管没有破坏现有内容的风险?如果是这样,您应该接受@KilianFoth的答案,并要求他以粗体突出显示答案中的第一句话!您的同事显然对他们的工作有很大的误解。
布朗

20
@Lee:谁说去耦永远是个好主意?您是否曾经看过一个代码库,其中所有内容都作为通过接口工厂使用某种配置接口创建的接口进行传递?我有; 它是用Java编写的,它是一个完整的,难以维护的越野车。极端的解耦是代码混淆。
Christian Hackl

8
Michael Feathers的“ 有效使用旧版代码”很好地解决了这个问题,即使在新的代码库中,也应该使您对测试的优势有个很好的了解。
l0b0

8
@ l0b0这几乎是圣经。在stackexchange上,这不会是问题的答案,但是在RL中,我会告诉OP让这本书读一遍(至少是一部分)。OP,得到修改代码的工作和阅读它,至少部分地(或告诉你的老板得到它)。它解决了这些问题。尤其是如果您不进行测试而现在就开始进行测试-您可能已有20年的经验,但是现在您将做您没有经验的工作。要了解它们,要比通过反复试验来认真学习所有知识容易得多。
R. Schmitz

4
感谢迈克尔·费瑟斯(Michael Feathers)的书的推荐,我一定会选一本。

Answers:


204

不愿意为了测试而修改代码表明开发人员不了解测试的角色,也不了解他们在组织中的角色。

软件业务围绕提供可创造业务价值的代码库而展开。通过长期的痛苦体验,我们发现,如果不进行测试,就无法创建如此庞大的代码库。因此,测试套件是业务的组成部分。

许多编码人员对这个原则不屑一顾,但是在潜意识里从来没有接受它。很容易理解为什么会这样。认识到我们自己的思维能力不是无限的,实际上,当面对现代代码库的巨大复杂性时,我们的思维能力受到了令人惊讶的限制,这种认识是不受欢迎的,并且很容易被抑制或合理化。测试代码未交付给客户的事实使人们容易相信它是第二类公民,与“基本”业务代码相比不是必需的。而且,将测试代码添加到业务代码中的想法对许多人来说都是双重冒犯。

证明这种做法合理的麻烦在于,通常只有公司层次结构中的高层才能理解软件业务中创造价值的方式的全部情况,但是这些人对以下方面没有详细的技术理解:理解为什么无法摆脱测试所需的编码工作流程。因此,他们经常被从业者安抚,他们向他们保证测试通常是一个好主意,但是 “我们是不需要这样的拐杖的精英程序员”,或者“我们现在没有时间这样的拐杖”,等等。等等。商业上的成功是一个数字游戏,并且避免了技术债务,从而确保了这一事实。质量等仅在长期内显示其价值,这意味着他们通常对此信念非常真诚。

长话短说:使代码可测试是开发过程中必不可少的部分,与其他领域没什么不同(许多微芯片出于测试目的而设计了很大比例的元件),但是很容易忽略这样做的充分理由。那。不要陷入陷阱。


39
我认为这取决于变化的类型。使代码更易于测试和引入不应在生产中使用的特定于测试的挂钩之间是有区别的。我个人对后者持谨慎态度,因为墨菲...
Matthieu M.

61
单元测试通常会破坏封装,并使被测试的代码比原本要求的复杂(例如,通过引入其他接口类型或添加标志)。与软件工程中一样,每一个好的实践和每一个好的规则都有其分担的责任。盲目地进行很多单元测试可能会对业务价值产生不利影响,更不用说编写和维护测试已经花费了时间和精力。以我的经验,集成测试具有更高的投资回报率,并且倾向于以更少的妥协来改进软件体系结构。
Christian Hackl

20
@Lee当然可以,但是您需要考虑使用特定类型的测试是否可以保证增加代码复杂性。我的个人经验是,直到需要对基础设计进行更改以适应模拟之前,单元测试都是一个很好的工具。那是我切换到其他类型的测试的地方。编写单元测试的目的只是为了使单元测试更加复杂,而这仅仅是为了进行单元测试。
康拉德·鲁道夫

21
@ChristianHackl为什么单元测试会破坏封装?我发现对于我正在处理的代码,如果认为需要添加额外的功能来启用测试,则实际的问题是要测试的功能需要重构,因此所有功能都相同抽象级别(通常是抽象级别的差异,通常会为额外的代码创建此“需求”),而较低级别的代码将移至其自己的(可测试的)函数。
Baldrickk

29
@ChristianHackl单元测试永远都不应破坏封装,如果您试图从单元测试中访问私有,受保护或局部变量,那么您做错了。如果要测试功能foo,则仅测试它是否确实有效,而不是在第二个循环的第三次迭代中,是否局部变量x是输入y的平方根。如果某些功能是私有的,那么就可以了,无论如何,您都将对其进行传递性测试。如果真的很大而且很私人?这是一个设计缺陷,但在C和C ++之外(带有标头实现分隔)可能甚至不可能。
opa

75

这并不像您想的那么简单。让我们分解一下。

  • 编写单元测试绝对是一件好事。

但!

  • 您对代码进行的任何更改都会引入错误。因此,在没有充分商业理由的情况下更改代码不是一个好主意。

  • 您的“非常瘦”的webapi似乎不是单元测试的最佳案例。

  • 同时更改代码和测试是一件坏事。

我建议采用以下方法:

  1. 编写集成测试。这不需要任何代码更改。它将为您提供基本的测试用例,并使您能够检查所做的任何其他代码更改都不会引入任何错误。

  2. 确保新代码是可测试的,并具有单元和集成测试。

  3. 确保您的CI链在构建和部署之后运行测试。

设置好这些东西之后,才开始考虑重构旧项目以提高可测试性。

希望每个人都将从过程中吸取教训,并且对最需要测试的地方,如何构造它以及为业务带来的价值有一个很好的了解。

编辑:自从我写了这个答案以来,OP澄清了这个问题,以表明他们在谈论新代码,而不是对现有代码的修改。我也许天真地以为“单元测试好吗?” 争论在几年前就解决了。

很难想象单元测试将需要哪些代码更改,但无论如何都不是您想要的一般良好实践。检查实际的异议可能是明智的,可能是所反对的单元测试风格。


12
这是一个很多比接受一个更好的答案。选票的不平衡令人沮丧。
康拉德·鲁道夫

4
@Lee单元测试应该测试功能单元,该功能单元可能与某个类相对应。功能单元应该在其接口(在这种情况下可能是API)中进行测试。测试可能会突出设计的气味,并需要应用一些不同/更高的层次。用可组合的小块构建您的系统,它们将更易于推理和测试。
Wes Toleman

2
@KonradRudolph:我想错过了OP添加的问题,这个问题是关于设计新代码,而不是更改现有代码。因此,没有什么可以打破的,这使得大多数答案都不适用。
布朗

1
我非常不同意编写单元测试始终是一件好事的说法。单元测试仅在某些情况下才是好的。使用单元测试来测试前端(UI)代码是愚蠢的,因为它们是用来测试业务逻辑的。另外,编写单元测试以替换缺少的编译检查(例如,使用Javascript)也是很好的。大多数仅用于前端的代码应专门编写端到端测试,而不是单元测试。
Sulthan

1
设计肯定会遭受“测试引起的损坏”。通常,可测试性会改善设计:在编写测试时,您会注意到无法获取某些东西但必须将其传递,从而使界面更清晰等等。但是有时候,您会偶然发现一些在测试时需要不舒适的设计的东西。一个示例可能是新代码中所需的仅测试构造函数,这是因为现有的第三方代码使用单例。发生这种情况时:退后一步,仅进行集成测试,而不是以可测试性为名破坏自己的设计。
安德斯·福斯格伦

18

将代码设计为固有可测试的不是代码的味道;相反,这是一个好的设计的标志。基于此的几种著名且广泛使用的设计模式(例如,Model-View-Presenter)提供了简便(容易)的测试,这是一个很大的优势。

因此,如果您需要为具体类编写接口以更轻松地对其进行测试,那是一件好事。如果您已经有了具体的类,则大多数IDE都可以从中提取接口,从而使所需的工作量降至最低。使两者保持同步还需要做更多的工作,但是接口无论如何都不会改变太多,而测试带来的好处可能会超过付出的额外努力。

另一方面,为@MatthieuM。如果在注释中提到,如果您要在代码中添加不应在生产中使用的特定入口点,仅出于测试目的,这可能是一个问题。


该问题可以通过静态代码分析来解决-标记方法(例如必须命名_ForTest),并检查代码库中是否有来自非测试代码的调用。
凌晨

13

恕我直言,很容易理解,对于创建单元测试,要测试的代码必须至少具有某些属性。例如,如果代码不包含可以单独测试的单个单元,则“单元测试”一词甚至没有意义。如果代码不具有这些属性,则必须先对其进行更改,这是显而易见的。

也就是说,从理论上讲,可以尝试先应用所有SOLID原理编写一些可测试的代码单元,然后再尝试对其进行测试,而无需进一步修改原始代码。不幸的是,编写真正可以进行单元测试的代码并不总是那么简单,因此很有可能会有一些必要的更改,只有当尝试创建测试时,这些更改才会被检测到。即使在编写代码时就考虑到单元测试的想法,这也是正确的,对于在开始时就没有将“单元可测试性”放在议程上的代码,绝对是正确的。

有一种众所周知的方法,它试图通过首先编写单元测试来解决该问题-称为测试驱动开发(TDD),它肯定可以帮助从一开始就使代码更可单元测试。

当然,在首先手动测试代码和/或在生产中工作良好的情况下,经常不愿事后更改代码以使其可测试,这是事实。缓解此问题的最佳方法是首先创建一个回归测试套件(通常只需对代码库进行很小的更改即可实现),以及其他附带的措施,例如代码审查或新的手动测试会话。那应该给您足够的信心,以确保重新设计某些内部组件不会破坏任何重要内容。


有趣的是您提到了TDD。我们正在尝试引入BDD / TDD,它也遇到了一些阻力-即“传递的最低代码”的真正含义。

2
@Lee:将变更带入组织总是会引起一定的阻力,并且总是需要一些时间来适应新事物,这不是新观点。这是一个人的问题。
布朗

绝对。我只是希望我们能有更多的时间!

它常常是一个事人们做这种方式将节省他们的时间(希望很快太)。为什么做一些不利于您的事情?
托尔比约恩Ravn的安德森

@ThorbjørnRavnAndersen:团队也可以向OP证明他们的方法可以节省时间。谁知道?但是我想知道我们是否真的没有遇到技术性较低的问题?OP一直来这里告诉我们他的团队在做错什么(在他看来),就好像他试图为自己的事业寻找盟友一样。与团队一起而不是与Stack Exchange上的陌生人一起讨论项目可能会更有益。
Christian Hackl

11

我对您提出的(未经证实的)断言表示怀疑:

要对Web API服务进行单元测试,我们需要模拟该工厂

不一定是真的。有很多编写测试的方法,并且有很多编写不涉及模拟的单元测试的方法。更重要的是,还有其他种类的测试,例如功能测试或集成测试。很多时候,可以在不是OOP编程语言的“接口”中找到“测试接缝” interface

一些问题可以帮助您找到替代的测试接缝,这可能更自然:

  • 我是否会想在其他 API 上编写瘦Web API?
  • 是否可以减少Web API和基础API之间的代码重复?可以根据另一个生成一个吗?
  • 我可以将整个Web API和基础API视为一个“黑匣子”单元,并有意义地对整个事物的行为进行断言吗?
  • 如果将来必须将Web API替换为新的实现,那么我们将如何做呢?
  • 如果将来将Web API替换为新的实现,Web API的客户是否可以注意到?如果是这样,怎么办?

您提出的另一个未经证实的断言是关于DI的:

我们要么设计Web API控制器类以接受DI(通过其构造函数或设置器),这意味着我们正在设计控制器的一部分,只是为了允许DI并实现我们原本不需要的接口,或者我们使用第三方像Ninject这样的框架可以避免必须以这种方式设计控制器,但是我们仍然必须创建一个接口。

依赖注入不一定意味着创建一个新的interface。例如,出于身份验证令牌的原因:您能否简单地以编程方式创建真实的身份验证令牌?然后,测试可以创建此类令牌并将其注入。验证令牌的过程是否取决于某种加密秘密?我希望您没有对密码进行硬编码-我希望您可以以某种方式从存储中读取它,在这种情况下,您可以在测试用例中简单地使用其他(众所周知的)密码。

这并不是说您永远不应该创建一个新的interface。但是不要只局限于一种编写测试的方法,或者一种伪造行为的方法。如果您跳出框框思考,通常可以找到一种解决方案,该方案将代码的扭曲程度降至最低,并且仍然可以为您带来所需的效果。


重点是关于接口的断言,但是即使我们不使用它们,我们仍然必须以某种方式注入对象,这是团队其余成员的关注点。即,团队中的某些人会对实例化具体实现并将其留在那的无参数点击率感到满意。实际上,一个成员提出了使用反射来注入模拟的想法,因此我们不必设计代码来接受它们。这是一个令人讨厌的代码气味imo
Lee

9

您很幸运,因为这是一个新项目。我发现测试驱动设计可以很好地编写出色的代码(这就是我们为什么要这样做的原因)。

搞清楚了前面如何调用一段给定的与现实的输入数据的代码,然后让你可以检查是否按照预期,你做的API设计流程早期逼真的输出数据,并得到一个很好的机会有用的设计,因为您不会受到必须重写以适应的现有代码的阻碍。同样,您的同事也更容易理解,因此您可以在此过程的早期再次进行良好的讨论。

请注意,上一句中的“有用”不仅意味着所生成的方法易于调用,而且还意味着您趋向于获得易于在集成测试中建立的干净接口,并为其编写模型。

考虑一下。特别是在同行评审中。以我的经验,时间和精力的投入将很快得到回报。


我们也对TDD有一个问题,即什么构成“要传递的最小代码”。我向团队演示了此过程,他们例外,不仅写了我们已经设计的内容-我能理解。“最小”似乎没有定义。如果我们编写测试并有明确的计划和设计,为什么不编写该代码以通过测试?

@Lee“要传递的最少代码” ...好吧,这听起来可能有些愚蠢,但这确实是它的意思。例如,如果您有一个测试UserCanChangeTheirPassword,则在测试中,您将调用(尚不存在)函数来更改密码,然后断言该密码确实已更改。然后编写函数,直到可以运行测试,并且它既不会引发异常,也不会具有错误的断言。如果那时您有理由添加任何代码,那么该理由将用于另一个测试,例如UserCantChangePasswordToEmptyString
R. Schmitz

@Lee最终,您的测试最终将是代码用途的文档,除了可以检查其自身是否实现的文档,而不仅仅是纸上写的。还要与这个问题进行比较-一种CalculateFactorial仅返回120且测试通过的方法。那最小的。这也显然不是想要的结果,但这只是意味着你需要另外一个测试来表达意。
R. Schmitz

1
@李小步。当代码上升到平凡的程度时,最低限度可能会超出您的想象。同样,您一次执行整个事情时所做的设计可能又不是最佳选择,因为您在不编写证明它的测试的前提下就应该如何设计做出假设。再次记住,代码首先应该失败。
托尔比约恩Ravn的安德森

1
同样,回归测试也非常重要。他们在团队范围内吗?
托尔比约恩Ravn的安德森

8

如果您需要修改代码,那就是代码的味道。

从个人经验来看,如果我的代码难以编写测试,那是不好的代码。这不是不好的代码,因为它不能按设计运行或运行,而是不好的,因为我无法快速了解它为什么起作用。如果我遇到错误,就知道要修复它将是一项艰苦的工作。该代码也很难/无法重用。

好的(干净的)代码将任务分解为较小的部分,这些部分一目了然(或至少看起来不错)很容易理解。测试这些较小的部分很容易。如果我对子节有足够的信心,我还可以编写仅以类似的简便程度测试代码库的部分的测试(重用在这里已经进行了测试,因此也很有帮助)。

从一开始就使代码易于测试,易于重构和易于重用,并且只要需要进行更改,您就不会自杀。

我在完全重建一个本来可以扔掉的原型到更干净的代码中的项目时输入此信息。最好一开始就正确处理并尽快重构错误代码,而不是连续几个小时盯着屏幕,否则会害怕触摸任何东西,以免破坏部分起作用的东西,这要好得多。


3
“可抛弃的原型”-每个项目都从其中一个开始生活……最好把事情想象成从来没有。当我输入时输入..你猜怎么着?...重构了一个原来不是的一次性原型;)
Algy Taylor

4
如果您要确保将扔掉的原型扔掉,请使用在生产中永远不允许的原型语言编写它。Clojure和Python是不错的选择。
托尔比约恩Ravn的安德森

2
@ThorbjørnRavnAndersen那使我发笑。那是对这些语言的一种挖掘吗?:)

@李 不,仅是生产可能不可接受的语言示例-通常是因为组织中没有人可以维护它们,因为它们不熟悉它们并且学习曲线陡峭。如果可以,请选择另一个。
托尔比约恩Ravn的安德森

4

我认为编写无法进行单元测试的代码是一种代码味道。通常,如果您的代码无法进行单元测试,那么它就不是模块化的,这将使其难以理解,维护或增强。也许如果代码只是胶粘代码,仅在集成测试方面才有意义,那么您可以用集成测试代替单元测试,但是即使那样,如果集成失败,您也必须隔离问题,而单元测试是一种很好的方法做吧。

你说

我们计划创建一个将返回身份验证方法类型的工厂。我们不需要它继承一个接口,因为我们不希望它有任何具体类型。但是,要对Web API服务进行单元测试,我们需要模拟该工厂。

我并没有真正遵循。拥有工厂创建事物的原因是允许您更改工厂或轻松更改工厂创建的内容,因此不需要更改代码的其他部分。如果您的身份验证方法永远不会改变,那么工厂将是无用的代码膨胀。但是,如果要在测试中使用与生产中不同的身份验证方法,那么让工厂在测试中返回与生产中不同的身份验证方法是一个很好的解决方案。

为此,您不需要DI或Mocks。您只需要工厂支持不同的身份验证类型,并使其可以以某种方式进行配置即可,例如从配置文件或环境变量中进行配置。


2

在我能想到的所有工程学科中,只有一种方法可以达到体面或更高的质量水平:

在设计中考虑检查/测试。

这在构造,芯片设计,软件开发和制造中都适用。现在,这并不意味着测试是构建所有设计的基础,而不是根本。但是,在做出每个设计决定时,设计人员必须清楚其对测试成本的影响,并就折衷做出明智的决定。

在某些情况下,手动或自动(例如Selenium)测试比单元测试更方便,同时也可以提供可接受的测试范围。在极少数情况下,扔掉几乎未经测试的东西也是可以接受的。但是这些必须根据具体情况有意识地加以决策。调用说明测试“代码气味”的设计表示严重缺乏经验。


1

我发现单元测试(以及其他类型的自动化测试)倾向于减少代码异味,并且我想不出一个示例来引入代码异味。单元测试通常会迫使您编写更好的代码。如果您不能轻松地使用一种方法进行测试,为什么在您的代码中应该更简单呢?

编写良好的单元测试将向您展示如何使用该代码。它们是可执行文档的一种形式。我看到过丑陋的编写,太长的单元测试,根本无法理解。不要写那些!如果需要编写长时间的测试来设置类,则需要重构类。

单元测试将突出显示您的某些代码气味所在的位置。我建议阅读Michael C. Feathers的《传统代码的有效工作》。即使您的项目是新的,如果它还没有任何(或很多)单元测试,您可能还需要一些非显而易见的技术来使您的代码进行良好的测试。


3
您可能会尝试引入许多间接层以便能够进行测试,然后再按预期使用它们。
托尔比约恩Ravn的安德森

1

简而言之:

可测试的代码(通常)是可维护的代码,或者说,难以测试的代码通常很难维护。设计不可测试的代码类似于设计不可修复的机器-可怜的笨拙的人最终将被分配去修复它(可能是您)。

一个示例是我们计划创建一个将返回身份验证方法类型的工厂。我们不需要它继承一个接口,因为我们不希望它有任何具体类型。

您已经知道,三年后您将需要五种不同类型的身份验证方法类型,对吗?需求会发生变化,尽管您应该避免过度设计,但是具有可测试的设计意味着您的设计具有(正好)可以更改的接缝而没有(太多)痛苦-并且模块测试将为您提供自动化的方法来查看您所做的更改不会破坏任何内容。


1

围绕依赖注入进行设计不是代码的味道,而是最佳实践。使用DI不仅可测试。围绕DI构建组件有助于模块化和可重用性,更轻松地允许替换主要组件(例如数据库接口层)。尽管它增加了一定程度的复杂性,但正确完成后,可以更好地分离层并隔离功能,这使得复杂性更易于管理和导航。这样可以更轻松地正确验证每个组件的行为,减少错误,还可以更轻松地查找错误。


1
“做对了”是一个问题。我必须维护两个DI出错的项目(尽管旨在“正确”完成)。与没有DI和单元测试的旧项目相比,这使代码简直太恐怖了,而且更糟。正确设置DI并不容易。
日1

@Jan很有趣。他们怎么做错了?

1
@Lee一个项目是一项服务,需要快速启动,但启动时却非常慢,因为所有类的初始化都是由DI框架(C#中的Castle Windsor)预先完成的。我在这些项目中看到的另一个问题是,将DI与创建带有“新”对象的对象混为一谈,避开了DI。这使得测试再次困难,并导致了一些令人讨厌的比赛条件。
1

1

从本质上讲,这意味着我们要么设计Web API控制器类以接受DI(通过其构造函数或设置器),这意味着我们正在设计控制器的一部分,只是为了允许DI并实现我们原本不需要的接口,或者我们使用第三方框架(如Ninject)可以避免以这种方式设计控制器,但是我们仍然必须创建一个接口。

让我们看一下可测试之间的区别:

public class MyController : Controller
{
    private readonly IMyDependency _thing;

    public MyController(IMyDependency thing)
    {
        _thing = thing;
    }
}

和不可测试的控制器:

public class MyController : Controller
{
}

前一个选项实际上有5行额外的代码,其中两行可以由Visual Studio自动生成。一旦设置了依赖项注入框架,以便IMyDependency在运行时替换具体类型(对于任何体面的DI框架,这是另一行代码),所有的Just Works都可以正常工作,除了现在您可以进行模拟并因此测试您的控制器以适应您的内心需求。

6条额外的代码行以实现可测试性……而您的同事认为这“工作量太大”?该论点与我息息相关,也不应与您息息相关。

而且,您不必创建和实现用于测试的接口:例如,Moq允许您模拟具体类型的行为以进行单元测试。当然,如果您不能将这些类型注入正在测试的类中,那么这对您没有多大用处。

依赖注入是您理解它后的其中一件事,您想知道“没有这个我怎么工作?”。这很简单,很有效,而且很有意义。请不要让您的同事缺乏对新事物的理解,以免使您的项目可测试。


1
您很快被驳回为“对新事物缺乏了解”的事实可能是对旧事物的良好理解。依赖注入当然不是新鲜事物。这个想法,也许是最早的实现,已有数十年的历史了。是的,我相信您的答案是由于单元测试导致代码变得更加复杂的示例,并且可能是单元测试破坏封装的示例(因为谁说该类首先具有一个公共构造函数?)。由于折衷,我经常从我从别人那里继承的代码库中删除依赖注入。
Christian Hackl

控制器始终具有公共构造函数(无论是否隐式),因为MVC需要它。“复杂”-也许,如果您不了解构造函数的工作方式。封装-是的,在某些情况下,DI与封装之争是一个持续,高度主观的争论,在这里无济于事,特别是对于大多数应用程序,DI比封装IMO可以为您提供更好的服务。
伊恩·肯普

关于公共构造函数:确实,这是所使用框架的特殊性。我在考虑普通类的更一般情况,该类没有由框架实例化。您为什么认为将其他方法参数视为增加的复杂性等于缺乏对构造函数工作方式的了解?但是,我感谢您承认DI与封装之间存在折衷。
Christian Hackl

0

当我编写单元测试时,我开始思考代码内部可能出什么问题。它可以帮助我改善代码设计并应用单一职责原则(SRP)。另外,几个月后我再次修改相同的代码时,它可以帮助我确认现有功能没有损坏。

趋势是尽可能多地使用纯函数(无服务器应用程序)。单元测试可以帮助我隔离状态并编写纯函数。

具体来说,我们将提供一个非常薄的Web API服务。它的主要职责将是整理Web请求/响应并调用包含业务逻辑的基础API。

首先为基础API编写单元测试,如果您有足够的开发时间,则还需要为瘦Web API服务编写测试。

TL; DR,单元测试有助于提高代码质量,并有助于将来更改代码而无风险。它还提高了代码的可读性。使用测试而不是评论来表达您的观点。


0

最重要的是,没有冲突,这是您对勉强的理由的论点。最大的错误似乎是有人提出了“为测试设计”的想法给讨厌测试的人。他们应该只是闭上嘴或用不同的方式说出来,例如“让我们花点时间做正确的事情”。

“必须实现接口”以使某些东西可测试的想法是错误的。接口已经实现,只是尚未在类声明中声明。这是识别现有公共方法,将其签名复制到接口并在类的声明中声明该接口的问题。无需编程,无需更改现有逻辑。

显然,有些人对此有不同的想法。我建议您先尝试解决此问题。

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.