将 Google Analytics 与 Robolectric 结合使用时出现 SQLException(或“尝试在 Robolectric 中使用 SQLite3”)

发布于 2024-11-14 22:40:32 字数 4145 浏览 1 评论 0原文

我正在使用 robolectric 来测试利用 Google Analytics 的活动。不幸的是,每当我尝试启动该活动时,我都会收到以下异常。

android.database.SQLException
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteDatabase.execSQL(ShadowSQLiteDatabase.java:149)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java)
        at com.google.android.apps.analytics.PersistentEventStore.<init>(Unknown Source)
        at com.google.android.apps.analytics.GoogleAnalyticsTracker.start(Unknown Source)
        at com.google.android.apps.analytics.GoogleAnalyticsTracker.start(Unknown Source)
        ...
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "CREATE TABLE EVENTS ( 'event_id'[*] BIGINT(19) PRIMARY KEY AUTO_INCREMENT NOT NULL, 'user_id' BIGINT(19) NOT NULL, 'account_id' CHAR(256) NOT NULL, 'random_val' BIGINT(19) NOT NULL, 'timestamp_first' BIGINT(19) NOT NULL, 'timestamp_previous' BIGINT(19) NOT NULL, 'timestamp_current' BIGINT(19) NOT NULL, 'visits' BIGINT(19) NOT NULL, 'category' CHAR(256) NOT NULL, 'action' CHAR(256) NOT NULL, 'label' CHAR(256),  'value' BIGINT(19), 'screen_width' BIGINT(19), 'screen_height' BIGINT(19)); "; expected "identifier"; SQL statement:
CREATE TABLE events ( 'event_id' bigint(19) PRIMARY KEY auto_increment NOT NULL, 'user_id' bigint(19) NOT NULL, 'account_id' CHAR(256) NOT NULL, 'random_val' bigint(19) NOT NULL, 'timestamp_first' bigint(19) NOT NULL, 'timestamp_previous' bigint(19) NOT NULL, 'timestamp_current' bigint(19) NOT NULL, 'visits' bigint(19) NOT NULL, 'category' CHAR(256) NOT NULL, 'action' CHAR(256) NOT NULL, 'label' CHAR(256),  'value' bigint(19), 'screen_width' bigint(19), 'screen_height' bigint(19)); [42001-147]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
        at org.h2.message.DbException.get(DbException.java:167)
        at org.h2.message.DbException.getSyntaxError(DbException.java:192)
        at org.h2.command.Parser.readColumnIdentifier(Parser.java:2694)
        at org.h2.command.Parser.parseCreateTable(Parser.java:4975)
        at org.h2.command.Parser.parseCreate(Parser.java:3705)
        at org.h2.command.Parser.parsePrepared(Parser.java:320)
        at org.h2.command.Parser.parse(Parser.java:275)
        at org.h2.command.Parser.parse(Parser.java:247)
        at org.h2.command.Parser.prepare(Parser.java:201)
        at org.h2.command.Parser.prepareCommand(Parser.java:214)
        at org.h2.engine.Session.prepareLocal(Session.java:425)
        at org.h2.engine.Session.prepareCommand(Session.java:374)
        at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1056)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:165)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:153)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteDatabase.execSQL(ShadowSQLiteDatabase.java:147)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.xtremelabs.robolectric.bytecode.ShadowWrangler.methodInvoked(ShadowWrangler.java:87)
        at com.xtremelabs.robolectric.bytecode.RobolectricInternals.methodInvoked(RobolectricInternals.java:110)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)

问题是 Android 使用 SQLite 数据库,但 robolectric 使用 H2,它支持稍微不同的 SQL 风格。

解决这个问题最简单的方法是什么?

I'm using robolectric to test an activity that makes use of Google Analytics. Unfortunately, whenever I try to start up the activity I get the following exception

android.database.SQLException
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteDatabase.execSQL(ShadowSQLiteDatabase.java:149)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java)
        at com.google.android.apps.analytics.PersistentEventStore.<init>(Unknown Source)
        at com.google.android.apps.analytics.GoogleAnalyticsTracker.start(Unknown Source)
        at com.google.android.apps.analytics.GoogleAnalyticsTracker.start(Unknown Source)
        ...
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "CREATE TABLE EVENTS ( 'event_id'[*] BIGINT(19) PRIMARY KEY AUTO_INCREMENT NOT NULL, 'user_id' BIGINT(19) NOT NULL, 'account_id' CHAR(256) NOT NULL, 'random_val' BIGINT(19) NOT NULL, 'timestamp_first' BIGINT(19) NOT NULL, 'timestamp_previous' BIGINT(19) NOT NULL, 'timestamp_current' BIGINT(19) NOT NULL, 'visits' BIGINT(19) NOT NULL, 'category' CHAR(256) NOT NULL, 'action' CHAR(256) NOT NULL, 'label' CHAR(256),  'value' BIGINT(19), 'screen_width' BIGINT(19), 'screen_height' BIGINT(19)); "; expected "identifier"; SQL statement:
CREATE TABLE events ( 'event_id' bigint(19) PRIMARY KEY auto_increment NOT NULL, 'user_id' bigint(19) NOT NULL, 'account_id' CHAR(256) NOT NULL, 'random_val' bigint(19) NOT NULL, 'timestamp_first' bigint(19) NOT NULL, 'timestamp_previous' bigint(19) NOT NULL, 'timestamp_current' bigint(19) NOT NULL, 'visits' bigint(19) NOT NULL, 'category' CHAR(256) NOT NULL, 'action' CHAR(256) NOT NULL, 'label' CHAR(256),  'value' bigint(19), 'screen_width' bigint(19), 'screen_height' bigint(19)); [42001-147]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:327)
        at org.h2.message.DbException.get(DbException.java:167)
        at org.h2.message.DbException.getSyntaxError(DbException.java:192)
        at org.h2.command.Parser.readColumnIdentifier(Parser.java:2694)
        at org.h2.command.Parser.parseCreateTable(Parser.java:4975)
        at org.h2.command.Parser.parseCreate(Parser.java:3705)
        at org.h2.command.Parser.parsePrepared(Parser.java:320)
        at org.h2.command.Parser.parse(Parser.java:275)
        at org.h2.command.Parser.parse(Parser.java:247)
        at org.h2.command.Parser.prepare(Parser.java:201)
        at org.h2.command.Parser.prepareCommand(Parser.java:214)
        at org.h2.engine.Session.prepareLocal(Session.java:425)
        at org.h2.engine.Session.prepareCommand(Session.java:374)
        at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1056)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:165)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:153)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteDatabase.execSQL(ShadowSQLiteDatabase.java:147)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.xtremelabs.robolectric.bytecode.ShadowWrangler.methodInvoked(ShadowWrangler.java:87)
        at com.xtremelabs.robolectric.bytecode.RobolectricInternals.methodInvoked(RobolectricInternals.java:110)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)

The problem is that Android uses SQLite databases, but robolectric is using H2 which support a slightly different flavor of SQL.

What's the easiest way to get around this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陌上青苔 2024-11-21 22:40:33

通过为 SQLiteDatabase 实现我自己的 Shadow 类并将其绑定到自定义测试运行器中,我能够完成大部分工作。如果您不使用自定义测试运行程序,则可以将其绑定在测试设置方法中。

public class MyRobolectricTestRunner extends RobolectricTestRunner {

    public InjectedTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected void bindShadowClasses() {
        super.bindShadowClasses();
        Robolectric.bindShadowClass(ShadowRealSQLiteDatabase.class);
    }


@Implements(SQLiteDatabase.class)
public static class ShadowRealSQLiteDatabase extends ShadowSQLiteDatabase {
    protected static Connection connection;

    // Specify the SQLite JDBC driver
    @Implementation
    public static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags) {
        try {
            Class.forName("org.sqlite.JDBC").newInstance();
            final Field f = ShadowSQLiteDatabase.class.getDeclaredField("connection");
            f.setAccessible(true);
            connection = DriverManager.getConnection("jdbc:sqlite:mem:");
            f.set(null, connection );
        } catch (Exception e) {
            throw new RuntimeException("SQL exception in openDatabase", e);
        }
        return newInstanceOf(SQLiteDatabase.class);
    }


    // Bypass TYPE_FORWARD_ONLY by calling next instead of first: http://stackoverflow.com/questions/2260575/mysql-java-exception-before-start-of-result-set/2260592#2260592
    @Implementation
    public long insert(String table, String nullColumnHack, ContentValues values) {
        SQLite.SQLStringAndBindings sqlInsertString = buildInsertString(table, values);
        try {
            PreparedStatement statement = connection.prepareStatement(sqlInsertString.sql, Statement.RETURN_GENERATED_KEYS);
            Iterator<Object> columns = sqlInsertString.columnValues.iterator();
            int i = 1;
            while (columns.hasNext()) {
                statement.setObject(i++, columns.next());
            }

            statement.executeUpdate();

            ResultSet resultSet = statement.getGeneratedKeys();
            if (resultSet.next()) {
                return resultSet.getLong(1);
            }
        } catch (SQLException e) {
            throw new RuntimeException("SQL exception in insert", e);
        }
        return -1;
    }


    // Bypass ShadowSQLiteDatabase.execSQL auto_increment hack
    @Implementation
    public void execSQL(String sql) throws android.database.SQLException {
        if (!isOpen()) {
            throw new IllegalStateException("database not open");
        }

        try {
            connection.createStatement().execute(sql);
        } catch (java.sql.SQLException e) {
            android.database.SQLException ase = new android.database.SQLException();
            ase.initCause(e);
            throw ase;
        }
    }
}
}

您的类路径中需要 com.zentus v056 sqlite-jdbc 驱动程序。

我已经验证这使用 SQLite 而不是 H2,尽管我在使用此解决方案时遇到了新错误:

Caused by: java.sql.SQLException: NYI
        at org.sqlite.Conn.prepareStatement(Conn.java:217)
        at com.groupon.InjectedTestRunner$ShadowRealSQLiteDatabase.insert(InjectedTestRunner.java:94)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.xtremelabs.robolectric.bytecode.ShadowWrangler.methodInvoked(ShadowWrangler.java:87)
        at com.xtremelabs.robolectric.bytecode.RobolectricInternals.methodInvoked(RobolectricInternals.java:110)
        at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.createCustomVariableTables(Unknown Source)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)

I was able to get most of the way there by implementing my own Shadow class for the SQLiteDatabase and binding it in a custom test runner. You can probably bind it in your test setup methods instead if you're not using a custom test runner.

public class MyRobolectricTestRunner extends RobolectricTestRunner {

    public InjectedTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected void bindShadowClasses() {
        super.bindShadowClasses();
        Robolectric.bindShadowClass(ShadowRealSQLiteDatabase.class);
    }


@Implements(SQLiteDatabase.class)
public static class ShadowRealSQLiteDatabase extends ShadowSQLiteDatabase {
    protected static Connection connection;

    // Specify the SQLite JDBC driver
    @Implementation
    public static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags) {
        try {
            Class.forName("org.sqlite.JDBC").newInstance();
            final Field f = ShadowSQLiteDatabase.class.getDeclaredField("connection");
            f.setAccessible(true);
            connection = DriverManager.getConnection("jdbc:sqlite:mem:");
            f.set(null, connection );
        } catch (Exception e) {
            throw new RuntimeException("SQL exception in openDatabase", e);
        }
        return newInstanceOf(SQLiteDatabase.class);
    }


    // Bypass TYPE_FORWARD_ONLY by calling next instead of first: http://stackoverflow.com/questions/2260575/mysql-java-exception-before-start-of-result-set/2260592#2260592
    @Implementation
    public long insert(String table, String nullColumnHack, ContentValues values) {
        SQLite.SQLStringAndBindings sqlInsertString = buildInsertString(table, values);
        try {
            PreparedStatement statement = connection.prepareStatement(sqlInsertString.sql, Statement.RETURN_GENERATED_KEYS);
            Iterator<Object> columns = sqlInsertString.columnValues.iterator();
            int i = 1;
            while (columns.hasNext()) {
                statement.setObject(i++, columns.next());
            }

            statement.executeUpdate();

            ResultSet resultSet = statement.getGeneratedKeys();
            if (resultSet.next()) {
                return resultSet.getLong(1);
            }
        } catch (SQLException e) {
            throw new RuntimeException("SQL exception in insert", e);
        }
        return -1;
    }


    // Bypass ShadowSQLiteDatabase.execSQL auto_increment hack
    @Implementation
    public void execSQL(String sql) throws android.database.SQLException {
        if (!isOpen()) {
            throw new IllegalStateException("database not open");
        }

        try {
            connection.createStatement().execute(sql);
        } catch (java.sql.SQLException e) {
            android.database.SQLException ase = new android.database.SQLException();
            ase.initCause(e);
            throw ase;
        }
    }
}
}

You'll need com.zentus v056 sqlite-jdbc driver in your classpath.

I've verified that this uses SQLite instead of H2, although I'm getting a new error with this solution:

Caused by: java.sql.SQLException: NYI
        at org.sqlite.Conn.prepareStatement(Conn.java:217)
        at com.groupon.InjectedTestRunner$ShadowRealSQLiteDatabase.insert(InjectedTestRunner.java:94)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.xtremelabs.robolectric.bytecode.ShadowWrangler.methodInvoked(ShadowWrangler.java:87)
        at com.xtremelabs.robolectric.bytecode.RobolectricInternals.methodInvoked(RobolectricInternals.java:110)
        at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.createCustomVariableTables(Unknown Source)
        at com.google.android.apps.analytics.PersistentEventStore$DataBaseHelper.onCreate(Unknown Source)
        at com.xtremelabs.robolectric.shadows.ShadowSQLiteOpenHelper.getWritableDatabase(ShadowSQLiteOpenHelper.java:52)
溺深海 2024-11-21 22:40:32

Robolectric 现在提供 @UsingDatabaseMap 注释,以便其他数据库可以轻松地与 Roboelectric 一起使用。 (目前这需要 Robolectric 的 SNAPSHOT 版本,Robolectric-1.0-RC5-SNAPSHOT)

如果有人希望将 SQLite 与 Robolectric 一起使用,我建议他们在 github 上查看我的项目,该项目提供了 SQLiteMap 类,以便可以将 SQLite 与 Robolectric 一起使用。

https://github.com/cessationoftime/robolectric-sqlite/wiki

结果是测试注释为使用 SQLite 数据库而不是 H2 的类:

@UsingDatabaseMap(SQLiteMap.class)
@RunWith(RobolectricTestRunner.class)
public class DaoTest {
    @Test
    public void RobolectricSQLiteTest() {
    }
}

Robolectric now provides the @UsingDatabaseMap annotation so that other databases can easily be used with Roboelectric. (Currently this requires the SNAPSHOT version of Robolectric, Robolectric-1.0-RC5-SNAPSHOT)

If anyone wishes to use SQLite with Robolectric, I recommend they check out my project on github which provides the SQLiteMap class so one can use SQLite with Robolectric.

https://github.com/cessationoftime/robolectric-sqlite/wiki

The result is test classes annotated to use the SQLite database instead of H2:

@UsingDatabaseMap(SQLiteMap.class)
@RunWith(RobolectricTestRunner.class)
public class DaoTest {
    @Test
    public void RobolectricSQLiteTest() {
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文