lucene-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "Mike Hogan" ...@mikehogan.net>
Subject Re: [subscriptions] Please make org.apache.lucene.index.IndexWriter non-final
Date Sun, 05 Oct 2003 20:56:18 GMT
Erik,

> It appears you are wrapping Lucene.  So, if you truly want to mock
> things for testing purposes, come up with a generic interface
> (SearchManager?!) that has the search method signature that you have
> above and all the other pieces you're wrapping.  Then create a mock
> implementation of that particular interface.

Setting up an interface as you suggest is what I did, and I create mock
impls of that as I need in other test cases.  But I also want to unit test
the  _implementation_ itself?  Here is the full code of the implementation:

package org.componenthaus.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;

import java.io.File;
import java.io.IOException;
import java.util.List;

public class LuceneSearchService implements SearchService {
    private static final String INDEX_FILE_PATH = "index";

    //This is impossible to unit test as IndexWriter is final
    public void index(String componentId, String componentDescription)
throws SearchService.Exception {
        IndexWriter writer = null;
        try {
            writer = new IndexWriter(INDEX_FILE_PATH, new
StandardAnalyzer(), !indexExists());
            final Document document = new Document();
            document.add(Field.Text("id", componentId));
            document.add(Field.Text("contents", componentDescription));
            writer.addDocument(document);
            writer.optimize();
            writer.close();
        } catch (IOException e) {
            throw new SearchService.Exception("Exception updating Lucene
index", e);
        }
    }

    private boolean indexExists() {
        return new File(INDEX_FILE_PATH).exists();
    }

    //This is impossible to unit test as Hits is final, and the constructor
is package protected
    public int search(final String query, int beginIndex, final int
endIndex, final List collector) throws SearchService.Exception {
        int numHits = 0;
        try {
            final Searcher searcher = new IndexSearcher(INDEX_FILE_PATH);
            final Analyzer analyzer = new StandardAnalyzer();
            final Query q = QueryParser.parse(query, "contents", analyzer);
            final Hits hits = searcher.search(q);
            numHits = hits.length();
            while (beginIndex <= endIndex && beginIndex < numHits) {
                final Document doc = hits.doc(beginIndex++);
                collector.add(doc.get("id"));
            }
        } catch (java.lang.Exception e) {
            throw new SearchService.Exception("Exception performing Lucene
search",e);
        }
        return numHits;
    }

}

You are right that this code as it currently stands cannot be unit tested
using mock-objects.  Thats because I tried to do so, ran into the final
problem, then rolled back.  I will show you roughly how the code looked when
I refactored it to be mock-ready:

public class LuceneSearchService implements SearchService {
    private static final String INDEX_FILE_PATH = "index";
    private Context context = new DefaultContext();

    public void setContext(Context c) {
        this.context = c;
    }

public void index(String componentId, String componentDescription) throws
SearchService.Exception {
        IndexWriter writer = null;
        try {
            writer = context.createIndexWriter(INDEX_FILE_PATH, new
StandardAnalyzer(), !indexExists());
            final Document document = new Document();
            document.add(Field.Text("id", componentId));
            document.add(Field.Text("contents", componentDescription));
            writer.addDocument(document);
            writer.optimize();
            writer.close();
        } catch (IOException e) {
            throw new SearchService.Exception("Exception updating Lucene
index", e);
        }
    }

public static interface Context {
    IndexWriter createIndexWriter(String path, Analyzer a, boolean create);
}

private static final class DefaultContext {
    IndexWriter createIndexWriter(String path, Analyzer a, boolean create) {
        return new IndexWriter(path, a, create);
    }
}

Now my test case becomes:

public void testCase() {
    MockContext context = new MockContext();
    MockIndexWriter indexWriter = new IndexWriter();

    context.setupExpectedIndexWriter(indexWriter);
    indexWriter.setupExpectedCloseCalls(1);
    indexWriter.setupIOException(true);

    LuceneSearchService service = new LuceneSearchService();
    service.setContext(context);
    //Should throw IOException as instructed to do above
    service.index(componentId, componentDescription);

    //But we should still have a closed indexWriter.
    context.verify();
}

public class MockContext implements LuceneSearchService.Context {
    IndexWriter createIndexWriter(String path, Analyzer a, boolean create) {
        return new MockIndexWriter(path, a, create);
    }
}

I don't know if you see any value in that, but I do.  I cannot do it because
IndexWriter is final and I cannot create a MockIndexWriter.  I can do a
RAMDirectory and use the real Lucene API, but I find that less convenient
than the above.  Do you not agree?

Cheers,
Mike.


---------------------------------------------------------------------
To unsubscribe, e-mail: lucene-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: lucene-dev-help@jakarta.apache.org


Mime
View raw message