TDD Your UI Layer

原文地址:http://www.donnfelker.com/tdd-your-ui-layer/

最近,当我在Caster.IO课程上发布了一篇关于使用TDD来驱动UI开发的文章后,twitter上便就此展开了讨论。

那么问题在哪呢?

一些人认为你不能用一个功能测试的框架比如Espresso来通过测试驱动UI层的开发。

但我不这么认为

为什么?在我详细深入之前,我觉得有必要重复声明一下什么是TDD。

什么是TDD?

TDD就是测试驱动开发。TDD是一个软件开发流程。

关键字:流程。

TDD的过程由5个步骤组成:

1.添加一个测试,任何一个新功能,升级,修复等等都从编写覆盖新代码和改变的测试开始。
2.运行所有测试并观察是否有新的测试失败了(通常为了节省时间,我会单独运行新测试来提速,然后再回过头来运行所有测试来进行回归测试)
3.编写代码(这是你本来需要做的或想要去做的—比如实现新功能等等)
4.运行测试—同样,通常,我会单独运行新的测试来加速进程。如果通过了,我会运行所有的测试来检查是否有错误。
5.重构—所有测试都通过之后,现在你有了一个安全的网络来捕获你代码中的错误。这个时候你可以开始重构你的代码了。

然后,每一个新的功能,更新,修复等等都重复这些步骤。你可能要一遍遍的重复这些步骤。一个“快乐路径”的测试(当所有功能都如你希望的那样),一个所有边缘情况的,一个空值的路径,一个灾难性的错误等。你通常会以测试所有不同的希望(或意外的结果)代码路径而结束。

所以,如何TDD你的UI层?

当我们浏览这个例子的时候,让我牢记TDD的全过程。

这意味着首先编写你的测试,甚至在编写UI元素之前。这样你会使用Espresso,与Junit一起使用的Android UI测试框架。

第一步:创建测试

我会创建一个这样的测试:

正如你所看到的,buttonx这个id根本不存在(所以它是红色的)。正因如此我需要实现这个按钮,所以接下来我们会看见这些:

现在buttonx这个id可以被找到了(它的颜色变了,现在我们也可以定位到它的定义了)。我是如何实现它的并不重要,这里我们关心的是TDD的过程。我可以成功的编译。好了,现在我可以进入TDD的第二步了:

第二步:运行测试
我运行测试并希望它失败,因为文本“Hello from buttonx”并不存在。如果它没有失败(意味着它通过了)那么我就有问题了,我需要深挖原因。让我们假设它失败了,所以我准备进入第三步。

第三步:编写代码
这里,我要编写代码为了让这些代码能够通过测试(它们可能会是为buttonx添加点击事件让它在屏幕某处输出“Hello from buttonx”)。然后进入第四步。

第四步:运行测试
我们运行测试来确保它们和其他测试都能通过。这是TDD必需的过程。你会想运行所有的测试(通常至少是所有和这些组件屏幕等相关的测试)来找到任何因为新代码添加而产生的回归问题的出现。在所有测试都通过后,我会继续第五步。

第五步:重构

到了这里我会整理代码来让它变的更好。可能会让它们更加private,final或者提取方法等。或许我发现了一段我可以提高设计模式的代码。如果是这样,比如可能我在新的类中重构了某些逻辑,我会加入新的TDD测试过程到新的重构中。什么情况下会发生这样的情况或者为什么呢?依我看来可能是我在一些地方使用了相同的代码,我可以将它们提取到另一个类中。因此,我可能想要对这个类进行一些测试,所以我会对这个类走一遍TDD的流程。

我遵循了TDD在UI层使用Espresso来实现新的功能,修改代码片段,修复bug。

更重要的是我使用TDD来实现它。

文中关于TDD的争论

没错,这个例子的确比较刻意也过于简单,正是如此我试图证明这一点。TDD是一个软件开发流程。也有人认为TDD代表别的什么。让我们来聊一聊相关的议题。

TDD不应该使用UI测试框架

我完全不同意这一点,因为TDD是软件开发流程。UI是软件的一部分,如果你想用TDD来开发它那么你就可以。步骤一表明我们需要编写测试。我们使用哪种框架对此有影响吗?没有。任何一点测试都比没有测试要好(但是这是另一个话题了)。看看我在维基百科上所找到的TDD资料,我发现在定义下的第一部分便是添加测试:

开发者可以使用任何测试框架编写测试来适应软件对应环境。

我们编写UI,因此Espresso作为UI测试框架是一个合适的测试框架。正确的工具。正确的事。

大多数人反对使用TDD来进行UI测试的主要原因是因为 它太慢,所以让我进入下一个话题。

当进行TDD时,所有的测试必需块

对此我有很深的体会。我的团队中有巨大的Espresso UI测试模块,它们运行完需要花费几个小时的时间。运行所有的测试是很痛苦且不可行的。因此我依靠CI来运行测试模块。在开发过程中,第二步和第四步我会运行一小部分测试。通常测试我正在编写的代码或者一小部分测试文件或包中国年相关的测试。这让我保持灵活。一般地这是一到二十个测试。

和JVM单元测试相比每秒可运行几百个,我同意它很慢。但是,我在这里做的UI开发所以考虑到UI测试的状况,我们很不幸的被这些慢速的测试所困。这就是现状,但是TDD流程依然有用。

在UI中做TDD是一个反模式

我多次看到过这种说法,主要是因为长时间运行测试是一个反模式,但是正如我前面所提及的,这就是解决的办法。你如何能绕过它?提取部分逻辑到MVP模式中。你可以在JVM测试中测试主要的代码。但你仍然需要一个小的UI测试层来保证你的UI像你想象的那样正常运行。

我这么说是因为你需要确认你的用户将会看到的内容…因为…

如果你的应用UI失败,崩溃,没有按照正常情况运行或者没有按照用户希望的那样运行,他们会认为这很垃圾。无论你的应用多么好看,如果UI没有按照希望的那样运行那么用户就会不高兴,而且不会喜欢使用你的应用。在这一个点上,你的工作将毫无意义。

多少次你使用一款应用时,起初觉得它看起不错,但是当你开始使用它的时候却往往不尽如人意。此时你的反应是什么?大多数都不太好。你可能不会使用它,甚至直接卸载它。我肯定不希望这样,我也坚信你也希望这样。

我想说的就是UI测试非常有用,即使是一点点,那一点也可以是通过TDD来驱动的测试。

但是,我听说TDD已经过时。

最近几年,这个说法一直持续不断。我已经了解了2003年由Kent Beck提出的TDD开始与重新定义,看到了它的兴起,衰落,东山再起等等。这是一个循环。有时它受人追捧,有时不是。最困难的地方是TDD很难。在一些语言中TDD被视为是帮助提高App总体设计的方式(通常是静态类型语言比如Java)。动态语言在测试时有其他更好的方式。

回到2014年,Martin Fowler,Kent Beck和DHH发布了一系列线上视频来讨论TDD是否已经过时了。你可以在这找到它们—Is TDD Dead?我会让你来定义TDD是否过时了。组中的每个人都有不错的观点,在那时我同意他们所表达的观点。

我不会在这争论TDD是否过时了。我会将它留给你定夺。

而然,我十分希望这篇文章可能证明TDD在UI开发中是可行的。

所以,TDD在UI开发中是否可行呢?

可行。

记住,TDD是软件开发流程,可以被应用到任何软件中。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结