一年后,仍然在单元测试、集成测试和 E2E 测试中苦苦挣扎
Posted
技术标签:
【中文标题】一年后,仍然在单元测试、集成测试和 E2E 测试中苦苦挣扎【英文标题】:One Year Later, Still Struggling with Unit vs Integration vs E2E testing 【发布时间】:2018-11-24 17:30:36 【问题描述】:我最近观看了 AssertJS 会议上的一些演讲(我强烈推荐),其中包括 @kentcdodds "Write Tests, Not Too Many, Mostly Integration"。我从事 Angular 项目一年多,编写了一些单元测试,刚开始使用 Cypress,但我仍然对集成测试感到沮丧,以及在哪里画线。我真的很想和一些日复一日的职业选手交谈,但我不知道我在哪里工作。由于我厌倦了无法弄清楚这一点,我想我只是在这里问世界,因为你们都很棒。
所以在 Angular(或 React 或 Vue 等)中,你有组件代码,然后你有 html 模板,通常它们以某种方式交互。组件代码中有可以进行单元测试的功能,我可以接受。
我还没想明白的地方是,当您测试组件功能如何更改 UI 时,您是否将其称为集成测试?如果您正在测试这种东西,是否应该仅在 E2E 测试中进行?因为 Angular/Jasmine(或 Jest)允许你做这种事情,引用 UI:
const el = fixture.debugElement.queryAll(By.css('button'));
expect(el[0].nativeElement.textContent).toEqual('Submit')
但这是否意味着您应该这样做?如果你这样做了,那么你不会在你的 E2E 测试中涵盖这一点吗?
关于与服务之类的集成,您在集成方面走了多远?如果你模拟实际的 HTTP 调用,并且只是测试它会被正确的函数调用,那是集成测试,还是仍然是单元测试?
总而言之,我直觉地知道我需要测试什么才能确信事情正在按应有的方式工作,我只是不确定如何辨别什么时候需要所有三种测试。
我知道这会越来越长,但这是我的示例应用:
有一个名为hasNoProducts
的属性在选择产品并从服务器返回数据后设置(如果没有,则不返回)。如果hasNoProducts
为真,UI(通过 *ngIf)会显示“抱歉”消息。如果为 false,则其他选项可用。根据选择的产品,这些选项会发生变化。
所以我知道我可以编写一个单元测试并模拟 HTTP 请求,以便我可以测试 hasNoProducts
是否设置正确。但是,我想测试是否显示了消息,或者是否显示了其他选项。如果有数据,请测试切换产品是否会更改随后显示在屏幕上的其他列表中的数据。如果我使用 Angular/Jasmine 执行此操作,它是否是集成测试,因为我正在“集成”组件和模板?如果不是,那么什么是集成测试?
我可以继续提问,但我会停下来,希望有人读到这里并有一些见解。同样,我已经阅读了大量文章,观看了大量视频并完成了教程,但是每次我坐下来申请一个真正的项目时,我都会被这样的事情困住,我想克服这些!提前致谢。
【问题讨论】:
【参考方案1】:单元测试和集成测试(然后是子系统测试和系统测试)的区别在于您希望通过测试实现的目标。
单元测试的目标是在小段代码中找到这些错误,如果这些代码段是隔离的,则可以找到这些错误。请注意,这并不意味着您真的必须隔离代码,而是意味着您的 focus 是隔离代码。在单元测试中,模拟是很常见的,因为它允许模拟难以测试的场景,或者加快构建和执行时间等,但模拟不是强制性的:例如,你不会模拟对来自标准库的数学 sin()
函数,因为 sin()
函数不会阻止您达到测试目标。但是,保留 sin()
函数不会将这些测试变成集成测试。严格来说,您甚至可以在发生一些真实网络访问的地方进行单元测试(如果您懒得模拟网络访问),但是由于不确定性、延迟等,这些单元测试会很慢且不可靠,这意味着它们根本不适合专门在隔离代码中查找错误。这就是为什么每个人都说“如果有一些真实的网络访问,那就不是单元测试”,这在形式上是不正确的,但实际上是正确的。
由于在单元测试中您有意只关注孤立的代码,因此您不会发现由于对与其他组件交互的误解而导致的错误。如果您模拟一些依赖组件,那么您将根据您对其他组件行为方式的理解来实现这些模拟。如果您的理解是错误的,您的模拟实现将反映您的错误理解,并且您的单元测试将成功,尽管在集成系统中会出现问题。这不是单元测试的缺陷,而只是存在其他测试级别(如集成测试)的原因。换句话说,即使您完美地进行了单元测试,也不可避免地会留下一些单元测试甚至不打算发现的错误。
现在,什么是集成测试?它们的定义目标是在(已经测试过的)组件之间的交互中发现错误。例如,此类错误可能是由于组件的开发人员对接口如何工作的相互误解造成的。例如,对于从A
使用的库组件B
:A
是否从正确的组件B
(而不是从C
)调用函数,调用是否发生在@987654329 @ 已经处于正确的状态(B
可能尚未初始化或处于错误状态),调用是否以正确的顺序发生,参数是否以正确的顺序提供并且具有预期形式的值(例如从零开始索引与基于一个的索引?null
是否允许?),返回值是否以预期形式提供(返回的错误代码与异常)并具有预期形式的值?这是针对一种集成方案 - 还有许多其他方案,例如通过文件(二进制或文本?哪个行尾标记:unix、dos、...?、...)交换数据的组件。
有许多可能的交互错误。为了找到它们,在集成测试中,您集成了真实的组件(真实的A
和真实的B
,没有模拟,但可能模拟其他组件)并刺激它们,以便实际发生不同的交互 - 理想情况下在所有有趣的方式,例如,试图在交互中强制一些边界情况(交换的文件是空的,...)。同样,仅在集成了某些组件的软件上运行测试这一事实并不能使其成为集成测试:只有当测试专门用于启动交互以使这些交互中的错误变得明显时,它才是集成测试.
子系统测试(这是下一个级别)然后再次关注剩余的错误,即单元测试和集成测试都不想找到的错误。例如,当C
被分解为A
和B
时,没有考虑组件C
的要求,或者,如果C
是使用A
的某些过时版本构建的,其中仍然存在一些错误. 然而,当从单元测试通过集成测试上升到子系统测试及以上时,保持专注是一个挑战:只对以前无法发现的错误进行测试,而不是重复单元测试子系统级别的测试。
【讨论】:
以上是关于一年后,仍然在单元测试、集成测试和 E2E 测试中苦苦挣扎的主要内容,如果未能解决你的问题,请参考以下文章
货物测试无法引用集成测试中定位板条箱内的任何公共内容。单元测试也找不到测试用例