web-dev-qa-db-ja.com

ResultSetをデータで埋める簡単な方法

ResultSetをモックしたい。真剣に。 ResultSetからのデータを解析する1つの大きく複雑なコードをリファクタリングしており、コードを同じように動作させたいと考えています。したがって、これをテストできるように、リファクタリングされるピースの単体テストを作成する必要があります。

グーグルした後、私は2つのアイデアを思いつきました。

  1. EasyMockを使用して、looooongモックシーケンスを記述します。非常に悪い解決策:初期データを追加するのが難しい、データを変更するのが難しい、大きなテストデバッグプロミクス。
  2. Apache DerbyまたはHSQLDBを使用して、メモリ内DBを作成し、ファイルまたは文字列配列から入力し、魔法のInMemoryDBUtils.query(sql)でクエリを実行します。次に、そのResultSetを使用します。残念ながら、テストを高速に作成するための魔法のInMemoryDBUtilsは見つかりませんでした:-)。 IBMの記事「Derbyを使用した永続性の分離された単体テスト」は、私が必要としているものについては問題ないようですが...

2番目のアプローチは、やや簡単で、はるかにサポートしやすいように見えます。

そのようなモックを作成するためにあなたは何をアドバイスしますか? (もちろん、医者にもかかわらず:-)?私は行方不明ですか 眉毛 いくつかの銀の弾丸?おそらく、DBUnitはこのためのツールですか?

21
DiaWorD

DBUnitは、私の知る限り、結果セットを表示しませんが、インメモリデータベースにデータを入力するのに役立ちます。

現時点では、モックフレームワークは間違ったアプローチだと思います。モックは、データを返すだけでなく、動作と相互作用をテストすることであるため、邪魔になる可能性があります。

代わりに、結果セットインターフェイスを実装するか、結果セット全体を実装することなく、関心のあるメソッドを実装するクラスへの結果セットインターフェイスの動的プロキシを作成します。クラスの保守は、メモリ内データベースの保守と同じくらい簡単であり(テスト対象のデータセットに一貫性がある場合)、おそらくデバッグが容易です。

DBUnitを使用してそのクラスをバックアップし、dbunitを使用して結果セットのスナップショットを取得し、テスト中にxmlからdbunitに読み取らせ、ダミーの結果セットにdbunitのクラスからデータを読み取らせることができます。データがやや複雑な場合、これは合理的なアプローチです。

クラスが非常に結合されていて、同じテストの一部として変更されたデータを読み取る必要がある場合は、インメモリデータベースを使用します。それでも、依存関係を引き離すことができるまで、実際のデータベースのコピーを使用することを検討します。

簡単なプロキシ生成方法:

private static class SimpleInvocationHandler implements InvocationHandler {
    private Object invokee;

    public SimpleInvocationHandler(Object invokee) {
        this.invokee = invokee;
    }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
        if (!method.isAccessible()) {
            method.setAccessible(true);
        }
        try {
            return method.invoke(invokee, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
}

public static <T> T generateProxy(Object realObject, Class... interfaces) {
    return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject));
}
11
Yishai

私はここからMockResultSetクラスで成功しました: http://mockrunner.sourceforge.net/ 。これにより、ResultSetインターフェースを実装するクラスを作成し、各列と行の値を設定できます。

メソッドが適切なサイズのResultSetを使用している場合は、必要な値をかなり簡単に返すテストを作成できるはずです。

簡単な例を次に示します。

MockResultSet rs = new MockResultSet("myMock");

rs.addColumn("columnA", new Integer[]{1});
rs.addColumn("columnB", new String[]{"Column B Value"});
rs.addColumn("columnC", new Double[]{2});

// make sure to move the cursor to the first row
try
{
  rs.next();
}
catch (SQLException sqle)
{
  fail("unable to move resultSet");
}

// process the result set
MyObject obj = processor.processResultSet(rs);

// run your tests using the ResultSet like you normally would
assertEquals(1, obj.getColumnAValue());
assertEquals("Column B Value", obj.getColumnBValue());
assertEquals(2.0d, obj.getColumnCValue());
38
matt

Mockrunner CSVまたはXMLファイルをロードして、MockResultSetを自動的に作成できます。また、ConnectionとStatementをモックすることもできるため、JDBCドライバーをクラスパスに追加しなくても、すべてのJDBCのものが単純に機能します。

6
jason

私はこれと同じケースのために何かを書いた。 Mockitoを使用して結果セットをモックできます。このコードでresultset.next()をモックすることで、結果セットのモック行をループすることもできます。

// two dimensional array mocking the rows of database.
String[][] result = { { "column1", "column2" }, { "column1", "column2" } };

@InjectMocks
@Spy
private TestableClass testableClass;

@Mock
private Connection connection;

@Mock
private Statement statement;

@Mock
private ResultSet resultSet;

@BeforeTest
public void beforeTest() {
    MockitoAnnotations.initMocks(this);
}

@BeforeMethod
public void beforeMethod() throws SQLException {
    doAnswer(new Answer<Connection>() {
        public Connection answer(InvocationOnMock invocation)
                throws Throwable {
            return connection;

        }
    }).when(testableClass).getConnection();

    when(connection.createStatement()).thenReturn(statement);
    when(statement.executeQuery(anyString())).thenReturn(resultSet);
    final AtomicInteger idx = new AtomicInteger(0);
    final MockRow row = new MockRow();

    doAnswer(new Answer<Boolean>() {

        @Override
        public Boolean answer(InvocationOnMock invocation) throws Throwable {
            int index = idx.getAndIncrement();
            if (result.length > index) {
                String[] current = result[index];
                row.setCurrentRowData(current);
                return true;
            } else
                return false;

        }

        ;
    }).when(resultSet).next();

    doAnswer(new Answer<String>() {

        @Override
        public String answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            int idx = ((Integer) args[0]).intValue();
            return row.getColumn(idx);
        }

        ;
    }).when(resultSet).getString(anyInt());
}

static class MockRow {
    String[] rowData;

    public void setCurrentRowData(String[] rowData) {
        this.rowData = rowData;
    }

    public String getColumn(int idx) {
        return rowData[idx - 1];
    }
}
5
karthik m

該当する場合は、実際のデータソースから現在の結果セットを取得してシリアル化し、ファイルを保存できます。次に、単体テストごとにその結果セットを逆シリアル化できます。これで問題ありません。

2
GWLlosa

ほとんどのResultSetメソッドを呼び出さない限り、区切られたテキストファイルを2次元配列にロードし、実際に必要なメソッドを実装し、残りはUnsupportedOperationException(デフォルト)をスローするようにします。 IDEでのスタブアウトメソッドの実装)。

1
erickson