概述
单元测试
单元测试是一个白盒测试,一般是针对一个方法单元进行的测试,单元测试要求运行快,编写简单。所以一般单元测试有这么一些特质:
不连接数据库
不访问磁盘文件
不访问远程网络
能够在很短时间内运行完毕(比如三秒内)
集成测试
集成测试可以称之为灰盒测试,是指对系统中几个模块集成起来进行测试。有很多时候,单元测试无法发现的问题,通过集成测试能发现。
功能测试是黑盒测试,完全从系统的功能边界外,不对系统内部做任何假设,就以用户的方式对系统进行使用测试。功能测试运行是最慢的,编写和维护都非常困难。但是对交付成功的软件的价值确实最大的。
测试能给我们带来收益,但是测试也需要我们投入很多精力。如何能在投入和收益间找到平衡,就需要我们在系统中结合这三种测试。一般,系统中单元测试最多,集成测试次之,最少的是功能测试。这就是经典的测试三角。
对自动化测试的误解
这是对测试最常见的误解,测试不能帮助我们防止bug,写了很多测试,并不意味着我们的系统就不会出现bug。自动化测试是防止我们在演进系统的过程中,引入新的bug。因为人工的测试非常缓慢,如果我们修改了代码,需要等待很长时间才能知道我们的修改是否正确,这就会给修改代码带来很大的风险,有可能我们就不等待人工测试的结果就交付产品。如果我们有一套能很快运行的自动化测试集,我们就能更快的验证我们的修改是否对已有功能造成破坏,是否引入了新的bug。认识到这一点,那就要调整一下心态:有可能有人看到长期以来,测试也没有帮住我们什么,就不愿意写测试,觉得不值得。我觉得这是好事,那说明我们开发能力很高,很少出现bug。如果bug很多,自动化测试并没有起到防止引入bug的作用,我们就应该调研,如何写质量更高的测试。
总结:自动化测试是一种快速反馈的手段。
测试代码不是交付的一部分
很多人认为,我们编写的产品代码才是我们要交付给最终用户的东西,而测试代码不是。所以我们可以以随便的态度编写自动化测试代码,在产品代码中应用的一些最佳实践或原则,我们可以不应用到测试代码中。这个观点也是错误的,不维护的自动化测试,比没有自动化测试更有危害。
第一,不维护的自动化测试很难跟随产品代码同演进,可能已经丧失了他原有的作用,而我们心里却以为我们有自动化测试,但实际上那只是摆设,只会误导我们。
第二,如果需求改变了,我们需要修改测试,让它体现最新的需求,但是因为测试年久失修,变得很难看懂,很难修改。那么最大的可能就是:删除测试代码,那么我们花在自动化测试上的时间就付诸东流了。只在最后阶段运行测试
其实这一条和第一条有相似之处,很多人认为既然是测试,那么就应该在测试阶段跑,而我们现在在开发产品代码,这是开发阶段,我们不应该运行测试。按照第一条最后的总结,自动化测试是一种反馈手段,那么我们更应该持续不断地,频繁的运行测试。当我们对代码有修改时,就运行测试;当发现测试失败后,立即修复,以免将这些债留到后期,那个时候修复的成本将更高昂。
实践
以可测试性为目标来编写代码
即使我们不采用测试驱动开发(TDD)的方式开发产品,但我们也应该将可测试性这条准则铭记在心。你编写的代码无法自动化测试,或难以自动化测试就必须依靠人工手动测试。人工测试会有疏漏,而且重复两次的测试可能有些少许的不同,都会疏漏一些重要的问题,或者难以重现bug。
比如我们要尽量避免与一些难以测试的部分紧耦合,比如连接数据库部分,向屏幕打印输出的部分,发送短信的部分等。比如你在一个类里,需要发送邮件,现在你需要测试这个类里的逻辑,这个时候你并不是为了测试发送邮件的客户端。如果你这个时候直接使用发送邮件客户端的静态方法,这就是一种非常强的紧耦合,如果我们想自动化测试这个类,我们就无法用花费更小,运行更快的单元测试甚至是集成测试来测试这段代码,我们就必须在功能测试这个层次来测试。
测试应该可以重复运行
第一个测试运行完毕后,修改了第二个测试所依赖的一个关键数据,测试运行不了了,必须人工干涉测试才能继续进行。这种自动化测试也是没有任何价值的。
幸好,我们有事务这个好东西,在事务开始时我们准备数据,然后运行测试。在测试完毕后,我们回滚事务,没有污染数据库。就拿基于Spring的集成测试为例:
Integration.java
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:applicationContext-test.xml")
@TransactionConfiguration(transactionManager = "txManager") @Transactional public abstract class IntegrationTest { }
我们添加@Transactional标签,让我们的测试在事务中运行。为了避免所有的测试类,都要带上那堆标签,为此提取一个基类。基类设为抽象的是因为,一个没有任何测试的测试类,maven运行这类测试的时候会报错误。
如果在某一个测试中我们不想让事务回滚,我们可以添加@Rollback(false)标签(这一般在调试时很有用)。
AgentServiceIntegrationTest.java
public class AgentServiceIntegrationTest extends IntegrationTest{ @Before public void setUp(){ } @Test public void should_find_agent_by_id(){ } @Rollback(false) @Test public void we_want_to_store_data_in_database(){ } @After public void tearDown(){ } }
何时添加自动化测试
调试
当你打开调试器,一步一步的调试代码来找出那个纠结的问题时,为什么不添加一个自动化测试来帮你定位问题呢。很多时候,这类调试甚至要启动一个tomcat才能进行。