第二章探索JUnit

  • Junit框架必须通过复用代码来降低测试代码的成本

第三章Junit实例

  • 对还没有实现的测试代码抛出一个异常

抛出异常而不是返回控制(或者因为方法没有返回值而不返回任何值)。有两个好处:这样子可以让任何读代码的人都清楚知道这些方法还没有实现,并且可以确保如果方法被调用,他将会抛出异常,从而避免误以为是真正的行为。

@Test
public void testMethod(){
   throw new RuntimeException("implements me");
}
  • 一次只能单元测试一个对象

单元测试的一个至关重要的方面就是细粒度.一个单元测试独立的检查你创建的每一个对象,这样你就可以在问题发生的第一时间把它们隔离起来.如果被测试的对象多于一个,那么一旦这些对象发生了变化,你就无法预测它们将如何相互影响.当一个对象与其他复杂的对象交互时,你就可以使用可预测的测试对象将被测试的对象包围起来.

  • 选择有意义的测试方法名字

一条好建议是测试方法的名字采用testXXX的命名模式,其中XXX是待测试领域方法的名字.当你需要为同一个方法添加其他测试时,则可以采用testXXXYYY的命名模式,其中YYY表示了测试之间的不同

  • 在assert调用中解析失败原因

无论何时,只要你使用了JUnit的任何assertXXX方法,就要确保自己使用第一个参数为String类型的那个签名(如:assertNotNull(“Must not return a null result”,result)).这个参数让你可以提供一个用意义的文本描述,在断言失败时,JUnit的test runner就会显示这个描述.如果不使用这个参数,当测试失败时,要找出失败原因就会比较麻烦和困难了

  • 一个单元测试等于一个@Test方法

为了获得最好的结果,你的测试方法应当尽量的简单明了和目的明确,不要试图把多个测试塞进一个方法.这样导致的结果就是测试方法变得更加复杂,难以阅读也难以理解,所谓理解万岁嘛,而且你在测试方法中编写的逻辑越多,测试失败的可能性就越高,甚至需要调试才能解决问题.这就会陷入一个滑坡,使得你很可能会堕入不得不为你的测试代码编写测试的深渊

  • 始终使assert得到调用

当你测试的代码没有包含任何assert语句,执行测试时,JUnit会将它们标记为测试成功,但这只是一个自欺欺人的假象.记得要始终调用assert,唯一可以接受不实用assert调用的情况是当抛出一个异常来指出一个错误条件时.

  • 迫使错误条件发生是测试灾难恢复能力的极佳做法

  • 测试任何可能失败的事物

如果一个方法被改变了,变得不再简单,那么当发生这个改变时,你就应当为这个方法添加一个测试,但在改变之前方法简单得不会出错则不需要增加.

  • 让测试改善代码

编写单元测试常常有助于你写成更好的代码。理由很简单,testcase是你的代码的用户,只有在使用代码的时候才能发现代码的缺点,所以应当根据测试时发现的不便之处重构代码,使其更易于使用。就类似TDD(测试驱动开发:要求程序员在自动测试失败的情况下编写新的代码,并且要消除重复),通过先编写测试,再从代码用户的角度来开发你的类。

  • 让异常测试易读

把catch块中的异常变量命名为expected,这样就可以明确的告知读代码的人,这个异常是我们预期的,抛出异常才能让测试通过,在catch块中加入assertTrue(true)语句也进一步强调,这才是正确的执行路径。

  • 同一个包,分离的目录

把所有测试类和待测类都放在同一个包中,但使用平行目录结构,为了可以访问保护型的方法,需要把测试和待测类放在同一个包中,为了简化文件管理,并让人清晰的分辨测试类和开发代码类,所有需要把测试放在一个单独的目录中。当然我们也可以使用一个test的包,进行遗漏所有开发包。这样也可以把test需要的resources文件也可也独立起来。

第四章-探索软件测试

  • 考察单元测试的覆盖率

一种方法就是数一下你的测试中用到了多少方法。使用Clover工具进行覆盖率统计,记住100%的测试覆盖并不能保证你的应用程序得到了100%的测试。

  • 测试先行

在写任何代码之前,必须先写一个失败的测试。为啥是失败的测试呢,因为不是失败的测试,老是成功的测试,你就不会改进这些代码来使其更加具有维护性。

  • 三种单元测试

逻辑单元测试,集成单元测试,功能单元测试。逻辑单元测试主要是检查代码逻辑性,通常只是针对单个方法,一般可以通过mock objects 和stub 来控制特定的方法的边界。 集成单元测试主要是在真实环境下的一个组件相互交互的测试。功能单元测试目的是为了确认响应的结果是否正确,这种单元测试更多的依赖于外部环境。。

单位测试种类

  • 在开发周期的不同平台上执行的不同类型测试

单位测试种类

第七章-使用mock objects进行孤立测试

  • 不要把任何商业逻辑写入mock objects中

mock objects必须是容易创建和空壳(简单那得不可能出错)

  • 只测试可能的错误

第八章-使用cactus进行容器内的测试

  • 把cactus测试放在它们自己的目录中

大多数cactus test runner预期cactus测试会被放在一个单独的目录下,比如放在src/test-cactus目录中,因为他们需要和纯junit测试不同的环境(比如打包并部署到容器中,诸如此类)

第九章-对servlet和filter进行单元测试

  • 记得检验测在应当失败的时候失败

总记得检验你写的测试能否正常工作,确保当你预期它失败时测试会失败。

  • 使用TDD实现可以运作的最简设计

避免过多设计和实现,只要实现最少量的事情使测试能够通过,目的是完成一个完整的代码功能块,当你编写功能代码时就能引用测试代码。

第十一章-对数据库应用程序进行单元测试

  • 在使用mock objects时重构冗长的设置代码

当使用mock objects时,如果在可调用待测方法之前所需执行的设置步数太多(没有正确使用mock objects、测试的代码职责太多),你就应当考虑重构。

第十二章-对EJB进行单元测试

  • 在使用mock 重构测试的setUp和tearDown部分

当你要在一个特定的TestCase中编写更多的测试时,你最好花些时间对设置测试的公共代码进行重构(对于tearDown方法也是如此)。如果你不这样做,结果会使得你的测试变得非常复杂,没有人能轻易理解你的测试,并且很难进行维护,结果是失败的可能性会更大。

参考资料

1.《JUnit.in.Action中文版(第一版)》

原文出处: 鸿影Akiko

一份设计文档的结构大概可以分成Background项目背景、Schedule排期、History版本历史记录、Information Architecture信息架构分析(包括Site Map、Experience Map、Flow等)、Framework框架设计、Wireframe线框图和Mockup视觉稿等。取决于实际项目的情况,部分内容可以省略,也可以加入更多,比如Storyboard故事板,Prototype可交互原型等。

在过去,我一度没有什么规范的设计文档写作习惯,用纸笔画完Information Architecture和Wireframe后,就匆匆进入了Mockup阶段,最后的交付物也仅仅是Mockup。前期的时候觉得没什么,后来就感觉到了问题,这样很容易过早地陷入对视觉细节的纠缠,设计到一半忘了最初的设计目标,有时花了很多精力纠结一个模块交互or视觉设计的好坏,后来却发现整个模块都没有存在意义,已经背离了最初的业务目标与设计目标,根本不是用户想要的东西;或者场景考虑不全面,设计完一个模块后放到整体里充满矛盾,结果需要花更多精力来进行补救,导致进度Delay或只能上线充满问题的版本等。

而良好的设计文档写作习惯,虽然会在一开始占据比较多的时间和精力,但却能保证全程设计思路一直比较清晰,做设计的时候时刻思考用户是谁、目标是什么、这样设计是否能帮助达到目标,向团队、向合作伙伴沟通传达自己的设计方案时,也有更强的说服力。

Background


这一部分的内容在设计师和PM、业务方充分沟通需求之后完成,我的习惯一般是分成这几个模块:

产品描述,要设计的产品是什么,依托怎样的平台,在什么场景下发生;

业务/产品现状,总结需求方现在面临的主要问题,有哪些体验不好的地方,关键痛点是什么;

用户目标,用户群有哪些类型,他们分别想解决什么问题;

访问流程,产品有哪些入口,最终把用户导向哪些地方。

这些都需要和需求方确认清楚,明白整个产品的来龙去脉,最终提炼出设计目标:需要设计什么新的功能,需要优化哪些已有的设计,提高产品哪些使用环节的体验,引导用户做出什么操作,最终达到怎样的业务目标。

Schedule


和需求方确认各阶段交付物的时间节点,制定完成设计的具体计划,每个阶段大概做哪些工作,什么时候内部Review,什么时候和项目组Review等。确保设计以一个合理的节奏展开,可以以较高的质量按时交付。

History


设计稿版本每发生一次比较大的迭代更新,都要记录在版本历史记录里,相比一个个去翻以前的设计稿,版本历史记录可以清晰地展现设计稿的迭代历程,有哪些需求的变动,有哪些设计时没思考清楚需要修改的地方,Review时大家给出了哪些意见和建议等。有时版本需要回滚,可以更方便地追溯,而项目结束后浏览这一部分,可以看到自己的设计在哪些方面一开始思考不足出现了各种问题,是如何被发现、改进和提升的,下一次设计的时候是否可以更早地思考到和回避掉。

Information Architecture


根据具体项目性质的不同,这一块的分析工具也有较大的差异,具体的选择和使用要按照实际场景来,而非机械进行套用

如果是设计一整套网站系统,Site Map必不可少,通过它将需要设计的内容以全景图的方式呈现出来,对整个网站的架构可以构建起一个初步的印象,像架构层级过深、页面内容重复等问题都可以通过Site map发现,进而提出是否可以减少页面的信息层级、合并部分页面等,从整体上优化产品的使用体验,而非只见树木不见森林。

Flow流程图也是一个常用工具,可以总结出不同场景下用户使用产品的流程和步骤是怎样的,可能产生怎样的分支需要在设计中考虑到,在哪些地方可能产生较大的流失,步骤是否可以合并优化,能否抽象出通用的流程来构建框架设计等。

Experience Map可以把产品在不同使用场景、流程下的体验问题直观地呈现出来,我们有时会得到一些用研结果反馈,但大量反馈建议直接列举的话会很散乱,也不知道哪些是真正的问题,哪些只是个别用户的吐槽,通过Experience Map可以整理出用户使用产品大概有哪些场景和环节,各场景和环节下都遇到过什么样的问题,哪些问题出现的频率较高等,帮设计师更好地代入到用户使用产品的实际体验过程中去,进而思考各场景、环节下都可以进行怎样的设计目标拆解与设计优化、最终帮助完成产品的整体目标。

Framework


Framework和Wireframe的区别主要在于前者更抽象、通用化,不需要太多的内容细节,而后者更详细、分场景、已经有了删格化和详细的文案等,离Mockup甚至只差配色、图标、阴影细节等。

Framework开始构建起产品的形,抽象出通用的布局原则,页面上大概有哪些模块,这些模块之间的主次、优先级关系是怎样的,每个模块要帮助用户完成怎样的目标。思考清楚了这些问题,接下来的设计才会减少目标偏离与方案返工出现的概率,能把握住界面的整体结构、模块关系呈现等,而不是陷入细节,结果让次要的东西喧宾夺主。

Wireframe


Wireframe在Framework的基础上具化出了产品的完整骨架,在这一步需要仔细考虑到每一个可能的使用场景,包括极多极少、错误等特殊情况都要包括在内。

我一般习惯在Axure文档里以建立很多页面,每个页面按照场景进行命名,再在页面里画Wireframe,具体到每一个模块可能出现的一些特殊场景等,则直接在页面里以模块的方式在主界面旁边呈现,如果是比较简单的情况,也可用文字直接说明。总之,每一个角落都要考虑得当,不能有遗漏,因为水平经验还比较稚嫩,一开始遗漏了较多内容,也非常感谢合作伙伴和团队前辈们的及时指出。

Wireframe虽然不是Mockup,但在视觉效果呈现上却马虎不得。一开始我觉得不是视觉稿没必要考虑那么多,在画Wireframe时完全没考虑栅格之类,最终的视觉效果感觉也比较粗糙。后来被指出在Wireframe这一环,文案等内容基本就确定了,如果不考虑视觉效果,可能在实际的视觉稿产出后,会发生因为文字内容过多溢出,导致整个页面结构都要被迫调整之类的情况,最终增加了产品的设计成本。作为交互设计师,我们可能不用考虑太多配色、创建角色形象之类的视觉细节,但一定要懂基础的UI设计规范,甚至在视觉要求不高(如很多B端产品)的时候,需要直接扮演视觉设计师的角色,这也是我们区别于“能画线框图的产品经理”的重要价值。

还有文案,通俗来说就是“说人话”,各种导航标签、各种引导提示问题、各种按钮说明等的文案也是交互设计师需要思考的,目前我在这方面做得还比较弱,文案有啰嗦、用户不容易理解等问题,正在努力看书写作试图弥补中,就不多谈了。

Mockup


Mockup作为表现层的主要产出,在Wireframe的基础上完成配色表现、图标绘制等视觉细节的呈现,为产品的骨架覆盖上最终的皮肤。在Wireframe已经充分考虑到各种场景的情况下,Mockup不需要再面面俱到,而是选择关键场景的界面进行绘制表现即可,注意一些Hover/Active之类的状态表现,再就是标注交付前端了。

最后放一张自己的设计文档结构截图吧,虽然Axure很多人黑,但我觉得在文档结构呈现这块真的是最好用的。

前言

  • 不必刻意使用设计模式,首先还是规范好代码(推荐文章-java编码规范1

  • 关注重用而非设计模式。也就是说设计模式是以重用为目的的,只要能做到重用,是否使用了某种设计模式并不重要

  • 开始不要考虑太多设计模式,等待初版完成后,后续发现有较多变化时,再做重构,在重构时考虑是否需要使用设计模块来适应变化。

  • 不正确地使用设计模式还不如不用,你在避免不正确使用的前提下慢慢用起来,就是正确使用了。

  • 设计模式是为了封装变化,让各个模块可以独立变化。精准地使用设计模式的前提是你能够精准的预测需求变更的走向,而精准的预测很难做到,需要深入的理解你的程序所在的领域的知识,了解用户使用你的软件是为了解决什么问题

设计模式使用场景

设计模式需要关注点不在实现,而应该在其概念和使用场景,而这里仅谈及常用的设计模式的使用场景,概念和实现都可以在任意一本设计模式书籍或网上都能找到

创建型

Singleton单例模式

  • 使用场景

1.当在系统中某个特定的类对象实例只需要有一个的时候,可以使用单例设计模式

  • 已用场景

1.数据库连接和Socket连接,需要保持同一时间只能有一个连接存在

2.java.lang.Runtime类

3.没有变量的工具类,也可以为了减少内存而使用单例

4.使用springmvc的使用,dao层和service层就是使用单例

Abstract Factory抽象工厂

  • 使用场景

1.创建产品家族,相关产品集合在一起使用的时候

2.想要提供一个产品类库,并只想显示其接口而不是实现时

  • 已用场景

1.java.sql.DriverManager,根据不同的驱动加载不同的驱动实现

2.java.lang.managerment.ManagementFactory 每种静态方法都会返回一个或多个表示Java 虚拟机组件的管理接口的平台 MXBean

Factory Method工厂方法

  • 使用场景

1.当子类型可能会有很多,以后需要不断增添不同的子类实现时

  • 已用场景

1.java.util.List中作为一个抽象接口工厂,iterator()接口方法返回一个迭代器接口Iterator(抽象产品),子类java.util.LinkedList、java.util.ArrayList实现了iterator()接口方法,返回具体的迭代器类型AbstractList$ListItr或AbstractList$Itr

2.jdk提供的jpa实现中,EntityManagerFactory 的 createEntityManager()方法可以传入不同的持久化单位创建不同的获得EntityManager对象

行为型

Iterator迭代器模式

  • 使用场景

1.需要对集合元素遍历,而不依赖于集合对象内部的实现细节(是数组还是列表方式)

2.支持对集合对象的多种遍历方式

3.为遍历不同的集合对象结构提供一个统一的接口

  • 已用场景

1.java.util.Iterator

Observer观察者模式

  • 使用场景

1.当一个系统中一个对象的改变需要同时修改其它对象内容,但是又不需要知道待改变的对象到底有多少个的时候

2.当一个对象的改变必须通知其它对象作出相应的变化,但是又不需要确定通知的对象是谁的时候

  • 已用场景

1.java.util.Observable(主题,被观察者)、java.util.Observer(观察者),使用推数据方式,setChanged()通知方法

Template Method模板方法

  • 使用场景

1.各子类中具有公共行为的时候,应该被提取出来并集中到一个公共父类中以避免代码重复

2.当需要控制子类扩展的时候,模板方法2里按顺序调用子类重载抽象父类的方法,这样就只允许在这些点进行扩展

  • 已用场景

1.java.util.Arrays数组中的sort()方法中,底层使用mergeSort()方法(模板方法)里通过compareTo方法来比较,而compareTo是需要我们实现的

2.spring的JdbcTemplate类,JdbcTemplate类通过模板设计模式帮助我们消除了冗长的代码,只做需要做的事情(即可变部分),并且帮我们做哪些固定部分,如连接的创建及关闭。

Command命令模式

  • 使用场景

1.当需要将”请求者”与”实现者”之间解耦

  • 已用场景

1.界面应用程序GUI,对于相同的按钮及菜单或弹出按钮只需共享此功能的一个实例即可

2.java.swing.Action,发送者是控件,接受者是事件函数

3.回调机制的面向对象设计需求、需要支持撤销和重做操作需求,以及类似于数据库回滚事务操作的需求

State状态模式

  • 使用场景

1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变其行为,即这个改为的操作是在对象内部条件决定的

2.当状态的判断逻辑过于复杂时,为了消除这些If…else等条件选择语句,而通过把这些状态通过用一系列类来表示(即状态模式)

Strategy策略模式

  • 使用场景

1.如果不希望条件算法暴露给客户端应用程序,则把条件算法封装成策略算法

2.多个类的表现行为不同,需要在运行时动态选择具体要执行的行为的时候,即这个选择的操作是随不同外部环境采取不同行为的场合

  • 已用场景

1.java.util.concurrent.ThreadPoolExecutor线程池类就存在着4种预定义的处理程序策略

结构型

Composite组合模式

  • 使用场景

1.想表示对象的”部分-整体”层次结构,并可以在其中以相同的方式对待所有元素时

  • 已用场景

1.java.awt.Container类继承java.awt.Component类,java.awt.Container类中含有add添加组件方法,传入参数类型就是java.awt.Component类型,这使得java.awt.Container的所有子类都可以继承add方法,将不同的Component组件添加到list当中。

Decrator装饰模式

  • 使用场景

1.当我们需要为某个现有的对象动态地增加一个新的功能或职责的时候,可以考虑使用装饰者模式

2.当某个对象的职责经常发生变化或者经常需要动态地增加职责,避免为了适应这样的变化而增加继承子类扩展的方法时

  • 已用场景

1.java.io含有大量装饰者模式的应用,如:java.io.InputStream、java.io.OutputStream、java.io.Reader、java.io.Writer等

Facade外观模式

  • 使用场景

1.当子系统是相互依存的时候,需要层级化子系统,简化子系统之间的相依性的时候,可以使用外观模式

2.外观模式通过一个外观接口读/写子系统中各接口的数据资源,而客户可以通过外观接口读取内部资源库,不与子系统产生交互

  • 已用场景

1.java.util.logging.Logger类内部包含大量的其它辅助类,如果外部应用程序调用这些系统类则需要复杂的调用过程,而通过调用java.util.logging.Logger类就变得简单了。

Proxy代理模式

  • 使用场景

1.当我们进行某一项具体功能调用时,可以使用代理对象预先加载资源,初始化数据,初始化成功后再对真实对象进行调用,这样可以免去用户长时间的等待,提高系统访问效率

2.远程代理(RemoteProxy)为一个对象在不同的地址提供局部代理

3.虚拟代理(VirtualProxy),若一个对象的创建非常耗时,可通过代理对象去调用,在真实对象创建前,返回一个假的调用,等真实对象创建好了,再返回给客户端的就是一个真实对象的相应方法调用

4.保护代理(ProtectionProxy)控制对原始对象的访问

5.智能指引(SmartReference)取代简单的指针,在访问对象时执行一些附加操作

  • 已用场景

1.spring中的aop

2.jdk提供的动态代理(运行时生成代理对象),必须实现java.lang.reflect.InvocationHandler接口,这个接口的委派任务是在invoke()方法里实现的

Adapter适配器模式

  • 使用场景

1.软件系统结构需要升级或扩展,又不想影响原有系统的稳定运行的时候

2.转换类之间的差别不是太大的时候

3.想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作的时候

  • 已用场景

1.java.io.InputStreamReader和java.io.OutputStreamWriter

2.slf4j和log4j的关系,slf4j只是一个抽象层,你还需要绑定到一个实现上,这个实现就是log4j,而slf4j-log4j12充当适配器的角色负责提供slf4j-api到log4j的转换

参考资料

1.好书一本:《软件秘笈 设计模式那点事》

2.在知乎的一篇关于怎么用设计模式的帖子,点击访问

3.说明

  1. 一共三篇,从《java编码规范-上》这篇开始吧 

  2. 父类提供一个方法把由子类实现的抽象方法按照顺序调用,那个方法叫模板方法