一年之后,单位与整合仍然在与E2E测试进行斗争

时间:2018-06-14 21:05:36

标签: javascript angular unit-testing integration-testing e2e-testing

我最近观看了AssertJS会议的一些演讲(我强烈推荐),其中包括@kentcdodds" Write Tests, Not Too Many, Mostly Integration。"我已经在一个Angular项目上工作了一年多,已经编写了一些单元测试,并且刚刚开始与赛普拉斯一起玩,但我仍然对集成测试以及在哪里绘制线条感到沮丧。我真的很想和一些日复一日的专业人士交谈,但我不知道我在哪里工作。由于我厌倦了无法解决这个问题,我以为我只是在这里问世界,因为你们都很棒。

因此,在Angular(或React或Vue等)中,您拥有组件代码,然后您拥有HTML模板,并且通常它们以某种方式进行交互。组件代码中包含可以进行单元测试的功能,而那部分我可以使用。

在我脑子里没想到的事情是,当你正在测试组件功能如何改变用户界面时,你称之为集成测试吗?如果你正在测试那种东西,那应该只在E2E测试中完成吗?因为Angular / Jasmine(或Jest)允许你做这种事情,所以引用UI:

const el = fixture.debugElement.queryAll(By.css('button'));
expect(el[0].nativeElement.textContent).toEqual('Submit')

但这是否意味着你应该这样做?如果你这样做,那么你不能在你的E2E测试中覆盖它吗?

关于与服务等事物的整合,您在整合方面走了多远?如果你模拟实际的HTTP调用,并且只是测试它是否会被正确的函数调用,那是集成测试,还是单元测试呢?

总而言之,我直观地知道我需要测试什么才能确信事情正在发挥作用,我只是不确定如何辨别什么时候需要进行所有三种测试。

我知道这已经很久了,但这是我的示例应用程序:

example app

在选择产品并从服务器返回数据后设置名为hasNoProducts的属性(如果没有则返回)。如果hasNoProducts为真,则UI(通过* ngIf)显示"抱歉"信息。如果为false,则其他选择变为可用。根据所挑选的产品,这些选项会发生变化。

所以我知道我可以编写单元测试并模拟HTTP请求,以便我可以测试hasNoProducts是否设置正确。但是,我想测试是显示消息,还是显示其他选项。如果有数据,请测试切换产品会更改随后显示在屏幕上的其他列表中的数据。如果我使用Angular / Jasmine这样做,它是一个集成测试,因为我' m"整合"组件和模板?如果没有,那么整合测试会是什么?

我可以继续提问,但我会在那里停下来,希望有人已经读过这篇文章并且有一些见解。再一次,我阅读了大量的文章,观看了大量的视频和完成了教程,但每次我坐下来申请一个真实的项目时,我都会陷入这样的困境,我想要超越这个!提前谢谢。

1 个答案:

答案 0 :(得分:1)

区分单元测试和集成测试(然后区分子系统测试和系统测试)的是您要通过测试实现的目标。

单元测试的目的是在小的代码段中发现那些错误,如果这些代码段是隔离的,则可以找到它们。请注意,这并不意味着您确实必须隔离代码,而是说您的 focus 是隔离的代码。在单元测试中,模拟非常普遍,因为它可以激发原本很难测试的场景,或者可以加快构建和执行时间等,但是模拟不是必须的:例如,您不会模拟对a的调用标准库中的数学sin()函数,因为sin()函数不会阻止您达到测试目标。但是,保留sin()函数不会将这些测试转变为集成测试。严格来说,您甚至可以在进行某些实际网络访问的地方进行单元测试(如果您懒得模拟网络访问),但是由于不确定性,延迟等原因,这些单元测试会很慢且不可靠,这意味着它们根本不适合专门在隔离的代码中发现错误。这就是为什么每个人都说“如果存在某种实际的网络访问权限,那不是单元测试”,这在形式上不是正式的,但实际上是正确的。

由于在单元测试中,您仅专注于隔离的代码,因此不会发现由于对与其他组件交互的误解而导致的错误。如果您模拟某些依赖于组件的组件,那么您将基于对其他组件的行为的理解来实现这些模拟。如果您的理解是错误的,那么您的模拟实现将反映您的错误理解,并且您的单元测试将成功,尽管在集成系统中,事情可能会破裂。这不是单元测试的缺陷,而仅仅是集成测试之类的其他测试级别的原因。换句话说,即使您完美地进行了单元测试,也不可避免地会存在一些甚至根本不想发现单元测试的错误。

现在,集成测试是什么?它们的目标是通过发现(已测试的)组件之间的交互中的错误的目标来定义的。例如,此类错误可能是由于组件开发人员对接口应如何工作的相互误解所致。例如,对于从B使用的库组件AA从正确的组件B(而不是从{{1})调用函数吗? }),调用是否在C已经处于适当状态(B可能尚未初始化或处于错误状态)时发生,调用是否以正确顺序发生,是在提供的参数中?正确的顺序并具有预期格式的值(例如,从零开始的索引与基于一个索引的索引?,是否允许B?),返回值是否以预期的形式提供(返回的错误代码与异常),并且具有预期形式的价值?这是一种集成方案-还有许多其他组件,例如通过文件(二进制或文本?)的组件交换数据(行尾标记:unix,dos,...?,...)。

存在许多可能的交互错误。为了找到它们,在集成测试中,您将真实的组件(真实的null和真实的A进行了集成,没有模拟,但可能模拟了其他组件)并刺激了它们,从而实际发生了不同的交互-理想情况下,以所有有趣的方式进行,例如,尝试在交互中强加某些边界情况(交换的文件为空,...)。同样,仅在集成了某些组件的软件上运行测试这一事实并不能使其成为集成测试:仅当该测试被专门设计为启动交互以使这些交互中的错误变得明显时,它才是集成测试。

子系统测试(是下一个级别)然后再次集中于其余的bug,即那些单元测试或集成测试都不会发现的bug。例如,当将B分解为CC时,或者如果A是使用某些过时的版本构建的,则不考虑组件B的要求在C中仍然存在一些错误。但是,从集成测试的单元测试到子系统测试以及更高版本时,要保持专注是一个挑战:只对那些不可能存在的错误进行测试之前发现,而不是重复在子系统级别进行单元测试。