1. 测试什么:
1) 测试输出结果,测试返回结果的正确性
2) 测试边界条件
格式
顺序
范围
外部条件
值的存在,特殊值,null,0
时间
3) 测试行为,内在逻辑关系,验证相反的逻辑关系
4) 相互校验,反复核对,多种方式验证
5) 考虑所有可能出错的条件,模拟错误情况
6) 性能差,压力大,吞吐率小,响应率低等特殊情形
2. 良好测试的属性
1) 自动化
2) 全面
3) 可重复
4) 独立
5) 高质量,和产品代码一样对待
3. 何时运行测试
1) 写了一个新的方法,功能
2) 修了一个bug
3) 成功编译,修改了编译错后
4) Check in之前
5) 持续集成
4. Review测试代码,
1) 在写业务代码之前, TDD
2) 写好业务代码之后,业务代码和测试代码要一起review
5. 测试已有的代码
1) 代码中频繁出现问题的部分
2) 新添加的代码逻辑
6. Design
1) 关注点分离,使可测试的逻辑分离出来,也方便写出独立的测试
2) 弄清楚业务逻辑中的规律性,对这种规律进行测试
3) TDD,改善已有的接口
4) 验证输入,输出
5) 先写测试,TDD,便于写出可测试的代码
6) 设计要考虑可测试性
7) Test程度:也不能过度test,破坏了class的封装性,也不能为了test使软件过度松散,当然也不能滥用mock object,这会使得test不够友好,重构变难。可以对一些public的interface进行测试,然后对类的行为进行测试。
8) 表达意图:测试就是文档
9) 不要修改SUT (system under test), 如果我们使用了Test-Specific子类,要保证我们没有修改需要验证的业务逻辑,可能只是修改一些可访问性或者注入一些间接的输入。
10) 保持测试的独立性
11) 隔离SUT,使SUT和其他软件或者非测试部分无关,可以用依赖注入或者依赖lookup的方式输入test specific子类。使得测试不受其他部分影响。
12) 最小化测试的重叠部分
13) 最小化不可测试的代码,比如遇到GUI,多线程,要把可测试的代码分离出来
14) 使测试逻辑和产品代码逻辑分开,尽量不使用test hook
15) 将测试代码和产品代码同等对待
7. 测试陷阱
1) 以前的测试有问题没关系(代码重构后),测试不是产品代码不重要
2) Smoke test, assertTrue(true),无assert
3) 只能在某个或者某些特定的环境中跑通,比如本地standalone,windows等等
4) 浮点数问题
5) 测试要跑很长时间
6) 测试老是需要改动,可能代码强耦合,需要重构
7) 需要经常的debug
8) Test有时跑的通,有时跑不通,是不是用了随机数,产生不符合规范的测试数据, 环境是否有依赖性?
9) 测试不够自动化,经常需要手动干预
10) 测试代码的维护很困难:测试代码写的不够专业等等
11) 测试的时候没有问题,上了产品出现问题:测试是不是很久才跑一次?环境是不是有依赖?
8. 如何写一个测试
新代码
TDD
写一个失败的测试
编写代码让这个测试可以跑通
重复进行第一步和第二部
在这个过程中,主动地进行重构
当没有新的测试可以加入,所有测试也都跑通了,此时也就完成了
修改一个bug
找出bug.先
写一个失败的测试,标识出bug的存在
修改代码直至测试跑通
检验是否所有其他测试是否都能跑通,表示没有影响其他的代码逻辑
9. 测试依赖环境的setup 和 teardown
Fresh fixture: 每个test方法执行都会setup和teardown
Shared fixture:可以为多个test方法,test case使用,一般用于那种创建开销很大容易导致slow test的资源
Minimal fixture
Standard fixture
Setup:
· Test方法里自己构建,minimal fixture
· Setup方法:standard fixture
· Test utility,create方法
· Lazy loading,只能用于那些不需要teardown的
· Shared fixture,可以预先通过back door的方式准备好,可以用class static属性预先创建,可以用lazy load,可以用junit 4的@before, @after,甚至可以用test chain(个人认为不推荐,test之间存在依赖和交互,不够独立)
Teardown
Fresh fixture, 通常在teardown方法里实现,可以借助于test helper,基类。
如果是某个test方法特有的,可能需要在该test方法里的finally里实现,也可以借助于test helper。
10 Verification(assertion)
1) State assertion: 验证状态,输出。state assertion,
问题
· 相同的assertion如果经常重复出现在不同的test方法里?
可以用Expected object或者自定义的assertion来替换
· 如何增加assert的可读性?
可以用Expected object或者自定义的assertion来替换(test specific assertion,比如test 方法中含有条件判断逻辑,可以将这部分提取为自定义的assertion,从而保持测试代码的简洁和可读性。
2) Behavior verification: 验证交互,行为,间接输出,不仅仅是直接的返回结果。Mock Object可以验证和SUT之间交互的行为逻辑,或者用spy object判断过程的间接结果
3) Delta assertion:用在有shared fixture时,但是不能解决并发多个test同时执行出现的冲突问题,可以解决另外一个test修改了shared fixture,接着跑run这个test出现错误的问题。就是在test run之前先获得shared fixture的情况,run之后,验证变化,不验证绝对值。
4) Guard Assertion:将if 判断用assertNotNull或者其他assertion语句替换
11. Mock对象的使用
Mock什么
Mock的是依赖组件不是被测试的单元
何时需要Mock
实际对象的行为是不可预测的
实际对象很难准备,初始化,启动
实际对象的行为很难触发,重现
实际对象的行为很慢
实际对象需要人工干预,有UI
需要观察实际对象的内部行为
存在的实际对象是我们无法控制和观察的,比如当我们需要获取SUT的间接输入时
分类:按功能分
Test Stub
SUT对实际对象的间接输入有依赖性,test stub使得测试对SUT的间接输入有控制能力。
Test Spy
超越test stub,它还可以捕捉SUT的间接输出,由test方法来验证。
Mock Object
超越test spy,它也验证间接输出,也可以模拟间接输入,但是它的实现原理,使用方法和test spy是不同的。
Test stub,test spy,mock object都可以通过JMock生成,但test stub,test spy还可以通过hard code方式或者采取用户可配置的方式
Fake Object
Fake Object以极其简单的方式实现了实际对象的所有功能,它只是为test构建的。但是我们不会把fake object作为控制点和观察点。主要原因是因为实际对象很难创建,或者太慢等等,并不是为了验证它的间接输出或者内部行为。比如用hashtable fake数据库,内存数据库,fake web service,我们可以通过依赖注入或者lookup的方式将fake object和SUT关联起来,在实际使用中用实际对象替换
Dummy Object
只是为了凑足参数,dummy object什么都不干,但是区别于null,可能都是空实现,没有值。SUT不会和dummy object有逻辑交互。
12. 如何访问SUT的私有属性或者测试它的私有行为
同样的package名,存放于不同的folder下面
继承SUT,test specific subclass
反射
把属性或者行为改成public
13. 如何解决数据库的依赖问题
Database sandbox
一人一个database,schema更改后,保持database的同步。这其实并不能完全解决由于环境问题而导致的测试的不稳定性,因为某个developer自己的test之间也有可能出现db使用冲突。
每个测试人员,开发人员,测试相关人员都有一个本地的数据库,或者在服务器上都有一个虚拟机。使用一些轻量级的db或者in memory db。
不同的人配备不同的db schema,必须使用同样的数据库结构
使用共享的db,但是要保证不同的用户只能修改自己私有的数据,不能修改共享的数据。
存储过程的自动化测试
存储过程开发人员用db编程语言编写单元测试,并在db级别对其进行单元测试,这些测试是跑在db里的
不需要为写测试再多了解一门开发语言
测试和SUT保存在一个地方
可以TDD
应用开发人员用应用编程语言编写单元测试,并在应用级别对其进行测试,把存储过程当成黑盒进行测试,DbUnit
如何teardown db资源
Truncate table
Transaction rollback