一、在自动化每日构建中运行单元测试和集成测试,如使用持续集成工具自动化构建;多线程
二、基于速度和类型布局测试:工具
根据运行测试所花费的时间很容易就能区分集成测试和单元测试,把集成和单元测试分开放置,放在不一样的目录,指定单元测试和集成测试运行的频率。布局
三、确保测试时源代码管理的一部分,共同放在版本管理器进行管理。单元测试
四、将测试类映射到被测试代码测试
建立测试类时,应该怎样组织和放置它们呢?咱们但愿能够找到一个项目的全部相关测试,一个类的全部相关测试,一个方法的全部相关测试。咱们能够采用如下方式:ui
(1)测试类和被测试类放到同一个项目内;spa
(2)测试类和被测试类尽可能保持相同或类似的包层次;线程
(3)针对同一个被测试方法的多个测试方法命名,能够采用如userLoginTest_Success,userLoginTest_Fail等。code
五、构建测试API,如使用测试类继承模式,建立测试工具类等;对象
一、编写可靠的测试
(1)决定什么时候删除或修改测试
单元测试什么时候会执行失败?
产品缺陷,没必要修改测试,只需修复产品缺陷;
测试缺陷,须要修复测试;
产品语义或API变动,使用方式改变了,须要修改测试;
重命名含义不清的测试,重构不可读的测试。
删除重复测试。
(2)避免测试中的逻辑
若是单元测试包含了switch、if、else、foreach、for、while等语句就说明你的测试里包含了不该有的逻辑。
若是须要复杂大型测试,如多线程测试,你应该在标明为集成测试的包里编写这种测试。
以下测试代码也包含了不该有的逻辑,无心中重复了产品代码的逻辑user + greeting,
1 public void addString(){ 2 String user = "USER"; 3 String greeting = "GREETING"; 4 String actual = MessageBuilder.Build(user, greeting); 5 6 assertEqual(user + greeting, actual); 7 }
改为以下代码就消除了引入逻辑:
1 public void addString(){ 2 String user = "USER"; 3 String greeting = "GREETING"; 4 String actual = MessageBuilder.Build(user, greeting); 5 6 assertEqual("USER GREETING", actual); 7 }
(3)只测试一个关注点
一个测试方法里保持只有一个断言,咱们就更容易诊断出了什么问题。
(4)把单元测试和集成测试分开
单元测试很容易运行,集成测试极可能失败,若是不够稳定,开发人员就会跳过全部测试,没法发挥单元测试的做用。
(5)用代码审查确保代码覆盖率
若是没有作代码审查,代码覆盖率统计的结论没有说服力。由于开发人员可能在测试方法里不写一个断言,测试总能经过。
代码审核有助于提高团队的技术水平,还能够创造出可读、高质量、可以持续使用多年的代码,并使你充满自信。
二、编写可维护的测试
(1)测试私有或受保护的方法
使方法成为公共方法;
把方法抽取到新类;
使方法成为静态方法。
(2)去除重复代码
抽取辅助方法去除重复代码。
使用@Before或者@After去除重复代码;
(3)已可维护的方法使用@Before
局限性:
@Before方法只用于须要进行初始化工做时;
@Before方法应该只包含适用于当前测试类中全部测试的代码,不然这个方法会更难以阅读和理解。
尽可能不用@Before方法,而封装辅助初始化方法,每一个测试方法手动调用。这样增长代码可读性。
(4)实施测试隔离
定义:一个测试应该老是能独立运行,不依赖于任何其余测试。
测试隔离的臭味道:
强制的测试顺序:测试须要特定的顺序执行,或者来自其余测试结果的信息;
隐藏的测试调用:测试调用其余测试;
共享状态损坏:测试共享内存里的状态,却没有回滚状态;
外部共享状态损坏:集成测试共享资源,却没有回滚资源;
(5)避免对不一样关注点屡次断言
1 @Test 2 public void CheckVariousUsmResult(){ 3 assertEqual(3, sum(1001, 1, 2)); 4 assertEqual(3, sum(1, 1001, 2)); 5 assertEqual(3, sum(1, 2, 1001)); 6 }
以上单元测试使用了三个简单的断言,进行了三个不一样的子功能测试,但愿能节省一些时间。这样作法有什么问题呢?若是断言失败,会抛出异常,后续的断言将得不到执行,即后续的功能得不到测试。但这种状况下,即使一个断言失败了,你仍是会但愿知道其余的断言结果。
你能够才起别的方式实现这个测试:
给每一个断言建立一个单独的测试;
使用参数化测试(.Net支持,Java目前好像不支持);
把断言放在一个try-catch块中。
(6)对一个对象的多个状态的比较时,有两种方式:
方法1、多断言方式
1 @Test 2 public void compare(){ 3 String userName = "zhangf"; 4 String realName = "张飞"; 5 String id = "1001"; 6 User user = new User(id, userName, realName); 7 8 assertEqual(id, user.getId()); 9 assertEqual(userName, user.getUserName()); 10 assertEqual(realName, user.getRealName()); 11 }
方法2、单个断言方式,toString()比较
1 @Test 2 public void compare(){ 3 String userName = "zhangf"; 4 String realName = "张飞"; 5 String id = "1001"; 6 User user = new User(id, userName, realName); 7 assertEqual("id:"+id+",userName:"+userName+",realName:"+realName, user.toString()); 8 }
第一种方式让人看起来觉得对多个功能作测试,可读性差,第二种方式可读性强。推荐第二种方式。
(7)避免过分指定
过分指定是对被测试单元如何实现其内部行为进行了假设,而不仅是检查其最终行为的正确性。
主要有如下几种状况:
测试对一个被测试对象的春内部状态进行了断言;
测试使用了多个模拟对象;
测试在须要存根时使用模拟对象;
测试在没必要要的状况下指定顺序或使用了精确匹配。如对返回的字符串进行精确匹配断言,而实际只需对字符串的一部分作断言就能够了,咱们能够不适用String.equal(),而使用String.contains()。
三、编写可读的单元测试
(1)单元测试命名
测试方法名包括三部分:被测试方法名,测试场景,预期行为。
如测试用户登陆,场景是屡次登录后要求使用验证码,预期行为是密码错误而失败,可命名为void userLogin_requirePictureNum_fail(){...}
(2)变量命名
合理的命名变量,能够确保阅读测试的人容易理解你要验证什么。由于单元测试不只起到测试的做用,仍是做为API的一种文档。
很差的命名如魔法数字,assertEqual(-100, result),没法看出-100是什么意义,将-100赋值给一个富含表达性命名的变量,如" COULD_NOT_READ_FILE = -100;",而后用变量作equal,则更容易理解断言的目的。
(3)有意义的断言
尽可能不要编写本身的定制断言信息,若是必须编写,请命名清楚明白。
(4)断言和操做分离
反例:
assertEqual(COULD_NOT_READ_FILE, log.GetLineCount("aaa.txt"))
正例:
int result = log.GetLineCount("aaa.txt");
assertEqual(COULD_NOT_READ_FILE, result);
(5)@Before和@After
这两个方式常常被滥用,以致于方法彻底不可读。
一种滥用的状况:在@Before中准备存根和模拟对象,致使阅读测试的人意识不到测试中使用了模拟对象,也不知道对象的预期值是什么。
若是由测试方法本身直接设置初始化模拟对象,设置全部的预期值,测试可读性会更好。
要点:测试要随着被测试系统一同成长和变化。