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

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

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

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

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

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

评论

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

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

Michael Feathers的“有效使用旧版代码”很好地解决了这个问题,即使在新的代码库中,也应该使您对测试的优势有个很好的认识。
@ l0b0这几乎是圣经。在stackexchange上,这不会是问题的答案,但是在RL中,我会告诉OP让这本书读一遍(至少是一部分)。 OP,请至少部分地阅读和使用Legacy Code进行有效工作(或告诉您的老板来获取它)。它解决了这些问题。尤其是如果您不进行测试而现在就开始进行测试-您可能已有20年的经验,但是现在您将做您没有经验的工作。要了解它们,要比通过反复试验来认真学习所有知识容易得多。

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

#1 楼

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

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

许多编码人员都对这个原则不屑一顾,但是在潜意识里从来没有接受它。很容易理解为什么会这样。认识到我们自己的思维能力不是无限的,实际上,当面对现代代码库的巨大复杂性时,我们的思维能力受到了惊人的限制,这种认识是不受欢迎的,并且很容易被压制或合理化。测试代码未交付给客户的事实使人们容易相信它是第二类公民,与“基本”业务代码相比不是必需的。而且,将测试代码添加到业务代码中的想法似乎对许多人来说都是双重冒犯。通常只有公司高层中的上级才能理解,但是这些人对编码工作流没有足够的技术了解,而这是理解为什么不能摆脱测试所必需的。因此,他们经常被从业人员安抚,他们向他们保证测试通常是一个好主意,但是“我们是不需要这样的拐杖的精英程序员”,或者“我们现在没有时间这样做“等等。等等。商业成功是一个数字游戏,避免技术债务,确保质量等只是在更长远的时间显示其价值,这意味着他们通常对此信念非常真诚。

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

评论


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

– Matthieu M.
19年1月29日在13:16

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

–克里斯蒂安·哈克(Christian Hackl)
19年1月29日在13:17

@Lee当然可以,但是您需要考虑使用特定类型的测试是否可以保证增加代码复杂性。我的个人经验是,直到需要对基础设计进行更改以适应模拟之前,单元测试都是一个很好的工具。那是我切换到其他类型的测试的地方。以进行单元测试的唯一目的为代价,编写使单元测试变得复杂得多的单元测试是眼花vel乱的。

–康拉德·鲁道夫(Konrad Rudolph)
19年1月29日15:17



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

–巴尔德里克
19年1月29日在16:29

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

– whn
19年1月29日在17:18



#2 楼

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


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


但是!


对代码进行的任何更改都会引入错误。因此,在没有充分商业理由的情况下更改代码不是一个好主意。
您的“非常瘦”的webapi似乎不是单元测试的最佳案例。
同时更改代码和测试是不好的事情。

我建议采用以下方法:


编写集成测试。这应该不需要任何代码更改。它会为您提供基本的测试用例,并使您能够检查所做的任何其他代码更改是否不会引入任何错误。
确保新代码是可测试的并且具有单元和集成测试。
确保您的CI链将在构建和部署后运行测试。

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

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

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

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

评论


这是一个比公认的更好的答案。选票的不平衡令人沮丧。

–康拉德·鲁道夫(Konrad Rudolph)
19年1月29日在20:24



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

– Wes Toleman
19年1月30日在3:44

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

–布朗博士
19年1月30日在12:10

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

–苏丹
19年1月31日在7:32

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

– Anders Forsgren
19年2月1日在12:15

#3 楼

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

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

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

评论


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

–́Riking
19年2月4日在2:49

#4 楼

恕我直言,很容易理解,对于创建单元测试,要测试的代码必须至少具有某些属性。例如,如果代码不包含可以单独测试的单个单元,则“单元测试”一词甚至没有意义。如果代码不具有这些属性,那么必须首先对其进行更改,这是显而易见的。 ,然后尝试为其编写测试,而无需进一步修改原始代码。不幸的是,编写真正可以进行单元测试的代码并不总是那么简单,因此很可能会有一些必要的更改,只有当尝试创建测试时,这些更改才会被检测到。即使在编写代码时就考虑到单元测试的想法,这也是正确的;对于在开始时就没有将“单元测试性”作为首要任务编写的代码,绝对是正确的。 >有一种众所周知的方法,它试图通过首先编写单元测试来解决该问题-称为测试驱动开发(TDD),它肯定可以帮助从一开始就使代码更可单元测试。 >
当然,在先手动测试代码和/或在生产中工作良好的情况下,通常不愿意事后更改代码,因此更改它实际上可能会引入新的错误,这是事实。减轻这种情况的最佳方法是首先创建一个回归测试套件(通常只需对代码库进行很小的更改即可实现),以及其他附带的措施,例如代码审查或新的手动测试会话。那应该给您足够的信心,以确保重新设计某些内部组件不会破坏任何重要内容。

评论


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

–李
19年1月29日在12:08

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

–布朗博士
19年1月29日在12:12



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

–李
19年1月29日在12:13

向人们展示这样的做法通常可以节省他们的时间(希望也可以很快)。为什么做一些不利于您的事情?

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年2月2日在22:44

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

–克里斯蒂安·哈克(Christian Hackl)
19年2月5日在13:26

#5 楼

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


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


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

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


我是否想通过其他API编写瘦Web API?
我可以减少Web API与基础API之间的代码重复吗?可以彼此产生一种吗?
我可以将整个Web API和基础API视为一个“黑匣子”单元,并有意义地对整个事物的行为进行断言吗?
如果Web将来必须将API替换为新的实现,我们该怎么做?
如果将来将Web API替换为新的实现,Web API的客户能否注意到?如果是这样,怎么办?

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


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


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

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

评论


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

–李
19年1月31日14:31



#6 楼

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

通过预先弄清楚如何使用来调用给定的代码段现实的输入数据,然后获取可以按预期检查的现实的输出数据,您可以在流程的早期就进行API设计,并且很有可能获得有用的设计,因为您不会受到现有代码的阻碍。改写以适应。同样,您的同龄人也更容易理解,因此您可以在此过程的早期再次进行良好的讨论。而且您趋向于获得易于使用的界面,这些界面很容易在集成测试中建立,并为之编写模型。

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

评论


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

–李
19年1月30日在8:20

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

–R。Schmitz
19年1月30日在11:10

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

–R。Schmitz
19年1月30日在11:21

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

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年2月1日,0:22



同样,回归测试也非常重要。他们在团队范围内吗?

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年2月1日在9:55

#7 楼

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

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

良好(干净)的代码将任务分解为较小的部分,这些部分可以一目了然(或至少外观漂亮)容易理解。测试这些较小的部分很容易。如果我对子节有足够的信心,我也可以编写仅以类似的轻松程度测试代码库的测试(重用在这里已经过测试,因此在这里也有帮助)。

使代码保持简单从一开始就进行测试,易于重构和易于重用,并且您不会在需要进行更改时就丧命。

我在完全重建应该具有成为了更简洁的代码的一次性原型。最好一开始就正确处理并尽快重构错误代码,而不是连续几个小时盯着屏幕,否则会害怕触摸任何东西,以免破坏部分起作用的东西。

评论


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

– Algy Taylor
19年1月30日在11:49

如果您要确保将废弃的原型扔掉,请使用在生产中永远不允许的原型语言编写它。 Clojure和Python是不错的选择。

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年1月30日在13:37

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

–李
19年1月31日15:04



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

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年2月1日在6:10

#8 楼

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

您说的是


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


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

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

#9 楼

在我能想到的所有工程学科中,只有一种方法可以达到较高或较高的质量水平:在构造,芯片设计,软件开发和制造方面具有真正的地位。现在,这并不意味着测试是构建所有设计的基础,而不是根本。但是,在做出每个设计决定时,设计人员必须清楚其对测试成本的影响,并就折衷做出明智的决定。比单元测试更方便,同时还可以提供可接受的测试范围。在极少数情况下,扔掉几乎未经测试的东西也是可以接受的。但是这些必须根据具体情况有意识地加以决策。调用负责测试“代码气味”的设计表示严重缺乏经验。

#10 楼

我发现单元测试(和其他类型的自动化测试)具有减少代码异味的趋势,并且我无法想到一个引入代码异味的示例。单元测试通常会迫使您编写更好的代码。如果您不能轻易地使用一种方法进行测试,为什么在您的代码中使用它更容易呢?它们是可执行文档的一种形式。我看到过丑陋的编写,太长的单元测试,根本无法理解。不要写那些!如果您需要编写长时间的测试来设置类,则您的类需要重构。

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

评论


您可能会尝试引入许多间接层以便能够进行测试,然后再按预期使用它们。

–索比昂·拉文·安德森(ThorbjørnRavn Andersen)
19年2月1日在6:12

#11 楼

简而言之:可测试的代码(通常)是可维护的代码-或更确切地说,难以测试的代码通常很难维护。设计无法测试的代码类似于设计一台不可修复的机器-怜悯最终将被指派修复它的可怜的傻瓜(可能是您)。


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


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

#12 楼

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

评论


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

– Jan
19年2月4日在12:21

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

–李
19年2月10日在16:07

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

– Jan
19年2月11日在9:09

#13 楼


本质上,这意味着我们要么设计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框架,这是另一行代码-一切正常,除了现在您可以进行模拟并因此对您的控制器进行测试内容。

6条额外的代码行以实现可测试性……而您的同事认为这是“太多的工作”?该论点不会随我而行,也不应随您而行。

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

依赖注入是一旦您了解它的那些事情之一,您想知道“没有这个我怎么工作?”。这很简单,很有效,而且很有意义。请,不要让您的同事对新事物的缺乏了解妨碍您的项目可测试。

评论


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

–克里斯蒂安·哈克(Christian Hackl)
19年2月4日在8:50

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

–伊恩·坎普(Ian Kemp)
19年2月4日在9:37

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

–克里斯蒂安·哈克(Christian Hackl)
19年2月4日在12:07

#14 楼

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

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


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


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

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

#15 楼

最重要的是,不存在冲突,这是您对不情愿的理由的论点。最大的错误似乎是有人提出了“为测试设计”的想法给讨厌测试的人。他们应该闭嘴,或者说不同的话,比如“让我们花点时间做正确的事”。该接口已经实现,只是尚未在类声明中声明。这是识别现有公共方法,将其签名复制到接口并在类的声明中声明该接口的问题。无需编程,无需更改现有逻辑。

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