Robolectric访问数据库时出现错误。

4
我有一个测试,创建了一个活动并试图从数据库中获取一些数据。然而这个操作失败了,出现了SQLiteException错误。
17:40:40.528 [DEBUG] [TestEventLogger]     android.database.sqlite.SQLiteException: Cannot open SQLite connection, base error code: 14
17:40:40.528 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.rethrow(ShadowSQLiteConnection.java:53)
17:40:40.528 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.access$600(ShadowSQLiteConnection.java:30)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection$Connections.execute(ShadowSQLiteConnection.java:443)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection$Connections.open(ShadowSQLiteConnection.java:345)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.nativeOpen(ShadowSQLiteConnection.java:58)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.nativeOpen(SQLiteConnection.java)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:1142)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:267)
17:40:40.531 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)
17:40:40.531 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)

这在我将数据库类移动到单例模型之前很好地工作。请问有什么建议可以处理Robolectric吗? 我找不到任何关于这个的文档或样例。
编辑: 运行Robolectric 3.0 RC-2。 Robolectric正在驱动我的活动,尝试对数据库进行一些操作。 在我的应用程序DB类中,从下面的实例检查中删除instance == null的检查'修复'了此问题(即,如果MySQLiteOpenHelper每次重新创建,则Robolectric运行测试时没有问题)。
public static synchronized MyDataManager getInstance(Context context){
    if (sInstance == null) {
        sInstance = new MyDataManager(context.getApplicationContext());
    }
    return sInstance;
}

private MyDataManager(Context context) {
    dbHelper = new MySQLiteOpenHelper(context);
}

MySQLiteOpenHelper是SQLiteOpenHelper的一个简单扩展。
故障发生在(再次提醒,这是在我的数据库类内部):
        database = dbHelper.getWritableDatabase();

很显然,我不想在我的应用程序中每次重新创建连接 - 我想没有人会希望这样做?这使我认为应该有一种正确的方法在Robolectric中做到这一点,只是我漏了什么技巧?

编辑:

此外,测试在隔离状态下成功运行,这使我认为它与Robolectric在测试用例之间移动并重用数据库连接有关?

两个测试都没有针对数据库或任何DB类执行具体操作。第一个测试启动一个片段,该片段将访问数据库以写入一些数据。第二个测试在尝试打开上述数据库时失败。


展示给我们代码。使用的 Robolectric 版本是哪个? - Jared Burrows
它在上面的代码中 - context.getApplicationContext() - vkislicins
不,那是你的“代码”,而不是你的“测试”。你是如何测试你的代码的?你的代码在哪里(例如 MyDataManagerTest)? - Jared Burrows
好的,其实并没有。我不是在测试MyDataManager - 我正在测试活动的一小部分。所有这些都是副作用,因为该活动具有加载器,该加载器在数据获取后直接进入DB。我会在一秒钟内发布我的测试。 - vkislicins
@nenick在Robolectric中有没有一种方法可以在测试之间重置所有内容?此外,对于SQLiteOpenHelper的弱引用 - 这难道不会导致每次进行垃圾回收时重新连接资源吗? - vkislicins
显示剩余8条评论
2个回答

9

在每个测试之间重置所有单例实例,否则您将遇到像你这样的副作用。

@After
public void finishComponentTesting() {
    resetSingleton(YourSQLiteOpenHelper.class, "sInstance");
}

private void resetSingleton(Class clazz, String fieldName) {
    Field instance;
    try {
        instance = clazz.getDeclaredField(fieldName);
        instance.setAccessible(true);
        instance.set(null, null);
    } catch (Exception e) {
        throw new RuntimeException();
    }
}

你会去柏林的Droidcon吗?见到你会很高兴。至于答案 - 你知道这是错误的黑魔法 :) - Eugen Martynov
我还没有门票,但我(或我的雇主)会订购一些。是的,见到你也很好;)不,那不是“错误”的黑魔法,这只是单例的本质,迫使我们使用这种魔法;) - nenick
1
很酷,太棒了!看看Dagger,你就会忘记这黑魔法。基本上客户端代码不必知道自己的依赖项在哪里、如何、由谁提供以及是否为单例。 - Eugen Martynov
我从未关注过Dagger中的单例机制,因为我更喜欢AndroidAnnotations。有没有一个网站可以解释一下这个问题?我们计划使用带有弱引用的单例模式,但我不确定这是否会避免在测试之间进行手动重置。 - nenick
让我们进行会议讨论。简短地说 - 我不喜欢 AA “魔法”生成的带有 _ 的活动。你没有这段代码,但应该在清单中引用它。当然,来自 Hugo 的 apt 包括源代码,它们可以在 AS 中查看。但是仍然存在问题。使用 Dagger 单例模式并不是真正的单例模式,但接近于单例模式。弱引用无法避免手动重置。 - Eugen Martynov

1

我认为你需要使用依赖注入替换数据库使用/单例模式,并在测试中进行模拟。这样你就不需要实例化代码/测试中未使用的东西。

听起来像是一个笨拙的建议,需要比“修复当前状态”更多的工作。但是我的经验表明,这样做是值得的,它将为整个应用程序提供清晰的设计和测试。

对于我来说,这与以下情况相当(再次抱歉提供如此明显的例子):

  1. 调试 vs 单元测试
  2. 预防内存泄漏的OEM修复

你的意思是将 SQLiteOpenHelper 注入到类中吗?那样不会导致多个连接到数据库吗?我认为应该只有一个 SQLiteOpenHelper。 - vkislicins
我的意思是将模型从SQL中抽象出来,并将这种抽象注入到UI中。 - Eugen Martynov
我不确定这会起作用,因为底层仍然会有一个单独的SQLiteOpenHelper。或者我可能误解了你的意思 - 你能提供一个例子吗? - vkislicins
也许这篇文章会给你带来灵感:https://medium.com/@artem_zin/m-model-from-mvc-mvp-in-android-flow-and-mortar-bd1e50c45395 - Eugen Martynov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接