Aaron的测试生活小说

半两五钱,笃志向前
  首页  :: 新随笔  :: 联系 :: 管理

译释Dozen ways to find bugs(6)——Learn by checked example

Posted on 2009-02-28 17:13  Aaron Wu  阅读(179)  评论(0)    收藏  举报

       英文原文名叫《A Dozen Ways to Get the Testing Bug in the New Year》,是Aaron在java.net上发现的文章。原文地址http://today.java.net/pub/a/today/2004/01/22/DozenWays.html,原文是在2004年1月22日发表的,按常理说一片早已经过了时效期的文章是没有多大用处的,但是Aaron却认为时至今日该文依然值得一阅。 

     学习第三API是一件让人很头疼的事情,如果够走运,将学习的API会附带比较详细的介绍文档;如果API会像附带文档所介绍的那样使用,那你的运气显然已经不是一般般地好了。对于我自己来讲,我习惯于边用边学,这样对于文档的好坏的依赖性就减弱了很多。为了确认一个API到底实现了什么样的功能,我尝试在代码中调用这个API,来揣测这个API的功能然后验证自己的猜想。往往在刚刚开始做这些尝试的时候,我们会觉得这个过程很曲折,但是我想告诉你的是:探索式学习是一种很有效的学习方法。

     探索式学习是一个测试(或者说尝试的过程会更加贴切一些吧,Aaron注),当然这里所指的“测试”与一般意义上的测试是有一些差别的。这里所指的“测试”是指我们写一些猜想性质的或者预期性的测试用例,然后通过运行测试来验证我们对于这个API的猜想,而不是致力于找出API中的错误。举一个例子来讲,假设我们正在写一个应用程序,这个应用程序需要调用Lucene——一个Java 搜索引擎框架库。我们怎样在代码里面调用这个API以搜索一个索引文件呢?你可以写类似于下面的一个尝试性的测试方法来核对它的实际结果,这样我们就可以了解到我们想学习的东西了:

<imports omitted for brevity>

        public class LuceneLearningTest extends TestCase {

        public void testIndexedSearch() throws Exception {

        //

        // Prepare a writer to store documents in an in-memory index.

        //

        Directory indexDirectory = new RAMDirectory();

        IndexWriter writer =

        new IndexWriter(indexDirectory, new StandardAnalyzer(), true); 

        //

        // Create a document to be searched and add it to the index.

        //

        Document document = new Document();

        document.add(Field.Text("contents", "Learning tests build confidence!"));

        writer.addDocument(document);

        writer.close();

        //

        // Search for all indexed documents that contain a search term.

        //

        IndexSearcher searcher = new IndexSearcher(indexDirectory);

        Query query = new TermQuery(new Term("contents", "confidence"));

       

        Hits hits = searcher.search(query);

        assertEquals(1, hits.length());

    }

}

         LuceneLearningTest是一个很常见的JUnit测试类,它调用了LuceneAPI以便为一个处于RAMDirectory中的示例文件建立索引,并断言在这个文档中单词“confidence”出现了一次。我们可以一次建立一批类似的测试方法,每一个我们不懂的地方我们都可以用这种方法去学习了解,我们为之写一个测试方法并要求持续重构——把一些通用的部分放到setUp()方法中去。下面是一个经过了重构了的LuceneLearningTest类,相对于第一个示例来讲,我又添加了两个查询类型:

<imports omitted for brevity>

 

public class LuceneLearningTest extends TestCase {

    private IndexSearcher searcher;   

    public void setUp() throws Exception {

        Directory indexDirectory = new RAMDirectory();

        IndexWriter writer =

            new IndexWriter(indexDirectory, new StandardAnalyzer(), true);

            Document document = new Document();

            document.add(Field.Text("contents", "Learning tests build confidence!"));

            writer.addDocument(document);

            writer.close();

            searcher = new IndexSearcher(indexDirectory);

    }

 

    public void testSingleTermQuery() throws Exception {

        Query query = new TermQuery(new Term("contents", "confidence"));   

        Hits hits = searcher.search(query);

        assertEquals(1, hits.length());

    }

   

    public void testBooleanQuery() throws Exception {

        Query query =

            QueryParser.parse("tests AND confidence", "contents", new StandardAnalyzer());

            Hits hits = searcher.search(query);

           assertEquals(1, hits.length());

    }

 

    public void testWildcardQuery() throws Exception {

        Query query =

            QueryParser.parse("test*", "contents", new StandardAnalyzer()); 

        Hits hits = searcher.search(query);

        assertEquals(1, hits.length());

    }

}

         读者应该已经注意到,我们已经将建立索引的方法重构到了setUp()方法中去了(SetUp方法是在每一个测试方法TestMethod()运行之前调用的)。这样,SetUp()方法有四种作用:

    》减少测试方法中的代码重复

    》保证测试方法不依赖也不影响其他的测试方法

    》有助于读者了解我们这个测试类的目的:建立索引和搜索。

    》提醒你:在你的程序中使用搜索功能的频率比建立索引频率高

    在一个孤立的环境下面写探索性测试方法可以帮助我们将精力集中在一个点上。在一开始,我们把精力集中在对于一个API的理解学习上面,然后我们再写我们利用该API开发的应用程序。当这些测试方法通过了,我们就可以将这个API集成到我们的应用程序中。(这样做得意义在于确保我们使用的API准确无误地提供了我们想要利用的功能,译者注。)换句话说,就是要一点点建立起我们(对于这个API)的信心。如果这个API的某些行为发生了改变,相对于集成测试方法来讲这些探索式测试方法可以帮助我们更加精确地定位问题所在。

    当一个API的新版本发布出来之后,我们怎么确认它是否还适合我们的应用程序呢?有了探索性学习得来的测试方法就好办了,我们可以像运行回归测试一样来运行这些测试方法,这样我们就可以很快了解到新版本与旧版本相比发生了哪些改变。在我们使用一个新版的API的时候,我们需要运行这些探索性测试方法来保证我们之前对于这个API的断言还是正确的。(保证我们的使用的API方法还是我们之前需要的那个样子,译者注。)

     另外,我们也可以在学习一门新的编程语言中运用这种方法。例如,我学习Ruby的时候,每当我碰到一个新的知识点我就写一个探索性测试方法来验证。下面这个探索性测试方法的示例用于验证Ruby数组的两个特性:

require 'test/unit'

class RubyArrayTest < Test::Unit::TestCase 

  def testPushPopShift

    a = Array.new

    a.push("A")

    a.push("B")

    a.push("C")

    assert_equal(["A", "B", "C"], a)

    assert_equal("A", a.shift)

    assert_equal("C", a.pop)

    assert_equal("B", a.pop)

    assert_equal(nil, a.pop)

  end

 

  def testCollect

    a = ["H", "A", "L"]

    collected = a.collect { |element| element.succ }

    assert_equal(["I", "B", "M"], collected)

  end

end

     每次当我需要记起某个API或者Ruby语言的使用方法的时候,我回去查看我当初写的那些探索性测试。这些方法会告诉我那些API或者语言特性怎样使用,这样我才会有更大的把握来保证自己是在朝着正确的方向前进。如果在已有的测试方法中我找不到想要的,那么我就利用这种探索学习方法去学习新的知识。