使用 Spock 和 Robospock 创建 SQLite 数据库的单元测试

2022-09-02 21:45:17
spock-core:0.7-groovy-2.0
robospock:0.5.0
Android Studio 0.8.2
Fedora release 20 (Heisenbug)

这是完整的解决方案。现在它编译并成功运行单元测试,并且目录结构与预览编辑相同。请随时对任何看起来不对劲的东西发表评论。

编辑解决方案 =====

build.gradle:

apply plugin: 'java'
apply plugin: 'groovy'

repositories {
    mavenCentral()

    maven {
        // Location of Android SDK for compiling otherwise get this error:
        /* Could not find com.android.support:support-v4:19.0.1.
           Required by:
           :testSQLite:unspecified > org.robospock:robospock:0.5.0 > org.robolectric:robolectric:2.3 */
        url "/home/steve/local/android-studio/sdk/extras/android/m2repository/"
    }
}

dependencies {
    // just compile so we can use the sqlite API
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    testCompile 'org.codehaus.groovy:groovy:2.3.+'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

SnapzClientTest.groovy:

package com.example.DataAccess

import com.example.DataAccess.SnapzAndroidDB

import org.robolectric.Robolectric
import pl.polidea.robospock.RoboSpecification

class SnapClientTest extends RoboSpecification {

    /* Create Sqlite database for Android */
    def 'Create a sqlite database for Android'() {
        setup:
        def androidDB = new SnapzAndroidDB(Robolectric.application)

        expect:
        androidDB != null
    }
}

SnapzAndroidDB.java,与8月5日编辑相比没有变化

Edit 5 August ================

基本上,我正在尝试创建一个JAR文件,该文件将用于具有SQLite功能的Android应用程序中,因此我可以将此JAR文件用于许多应用程序。

我从头开始,并创建了一个更容易修复错误的较小应用程序。这是目录结构,只有三个文件:

testSQLite/build.gradle
testSQLite/src/main/java/com/example/sqltest/SnapzAndroidDB.java
testSQLite/src/test/groovy/SnapzClientTest.groovy

build.gradle

apply plugin: 'java'
apply plugin: 'groovy'

repositories {
    mavenCentral()

    maven {
        // Location of Android SDK for compiling otherwise get this error:
        /* Could not find com.android.support:support-v4:19.0.1.
           Required by:
           :testSQLite:unspecified > org.robospock:robospock:0.5.0 > org.robolectric:robolectric:2.3 */
        url "/home/steve/local/android-studio/sdk/extras/android/m2repository/"
    }
}

dependencies {
    // Just compile so we can use the sqlite API
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    testCompile 'org.codehaus.groovy:groovy:2.3.+'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

SnapzAndroidDB.java

package com.example.DataAccess;

import java.util.logging.ConsoleHandler;
import java.util.logging.SimpleFormatter;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.logging.Level;

import android.content.Context;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteException;
import android.database.Cursor;

public class SnapzAndroidDB extends SQLiteOpenHelper {
    /**
     * Logger for displaying log messages
     */
    private static final Logger log = Logger.getLogger("SnapzAndroidDB");

    private SQLiteDatabase mDb;

    public SnapzAndroidDB(Context context) {
        super(context, "DB_NAME", null, 1);

        /* Create logger */
        ConsoleHandler consoleHandler = new ConsoleHandler();
        log.addHandler(consoleHandler);
        log.setLevel(Level.FINE);
        consoleHandler.setFormatter(new SimpleFormatter());
        consoleHandler.setLevel(Level.ALL);

        log.log(Level.INFO, "SnapzAndroidDB()");
    }

    /* Called only once first time the database is created */
    @Override
    public void onCreate(SQLiteDatabase mDb) {
        log.log(Level.INFO, "onCreate(SQLiteDatabase db)");

        String createConfig = String.format("create table %s (%s int primary key, %s text, %s text)",
                                         "TABLE_CONFIG",
                                         "ID",
                                         "NAME",
                                         "VALUE");

        log.log(Level.INFO, "onCreate with SQL: " + createConfig);
        mDb.execSQL(createConfig);
    }

    @Override
    public void onUpgrade(SQLiteDatabase mDb, int oldVersion, int newVersion) {
        log.log(Level.INFO, "onUpgrade()");
        /* Only if there is some schema changes to the database */
    }
}

SnapzClientTest.groovy

package com.example.DataAccess

import com.example.DataAccess.SnapzAndroidDB

import spock.lang.Specification
import org.robolectric.Robolectric

class SnapClientTest extends Specification {


    /* Create SQLite database for Android */
    def 'Create an SQLite database for Android'() {
        setup:
        def androidDB = new SnapzAndroidDB(Robolectric.application)

        expect:
        androidDB != null
    }
}

我仍然得到的错误如下:

com.example.DataAccess.SnapClientTest > Create an SQLite database for Android FAILED
    java.lang.RuntimeException: Stub!
        at android.database.sqlite.SQLiteOpenHelper.<init>(SQLiteOpenHelper.java:4)
        at com.example.DataAccess.SnapzAndroidDB.<init>(SnapzAndroidDB.java:26)
        at com.example.DataAccess.SnapClientTest.Create a sqlite database for Android(SnapzClientTest.groovy:15)

编辑 4 八月 ======

这是我更新的测试规范,它使用Robolectric来生成一个可以在SQLiteOpenHelper(...)的构造函数中使用的上下文。

import org.robolectric.Robolectric

def 'Create an SQLite database for Android'() {
    setup:
    def androidDB = new SnapzAndroidDB(Robolectric.application)

    expect:
    androidDB != null
}

我实际测试的函数是一个扩展的类。我的构造函数调用构造函数,如您所见,上下文是从测试规范传递的第一个参数:SQLiteOpenHelperSnapzAndroidDB(...)SQLiteOpenHelper()

public class SnapzAndroidDB extends SQLiteOpenHelper
    public SnapzAndroidDB(Context context) {
        super(context, SnapzContract.DB_NAME, null, SnapzContract.DB_VERSION);       
    }
    .
    .
}

当我运行测试时,我得到这个错误:

com.sunsystem.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
    java.lang.RuntimeException: Stub!
        at android.database.sqlite.SQLiteOpenHelper.<init>(SQLiteOpenHelper.java:4)
        at com.sunsystem.DataAccess.SnapzAndroidDB.<init>(SnapzAndroidDB.java:33)
        at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:168)

结束编辑 ==========================

编辑 ====

当我尝试使用getBaseContext()时,我得到以下错误:

com.sunsystem.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
    groovy.lang.MissingMethodException: No signature of method: com.sunsystem.HttpSnapClient.SnapClientTest.getBaseContext() is applicable for argument types: () values: []
        at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:159)

我的规范 spock 函数是这样的:

def 'Create an SQLite database for Android'() {
    setup:
    def androidDB = new SnapzAndroidDB(getBaseContext())

    expect:
    androidDB != null
}

以下是依赖项:

dependencies {
    compile "com.googlecode.json-simple:json-simple:1.1.1", {
        // Exclude junit as we don't want this include in our JAR file as it will add hamcast and other dependencies as well
        exclude group:'junit', module: 'junit'
    }

    // Just compile so we can use the SQLite API. This won't be included in the JAR
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    // Compile for unit testing only
    testCompile "org.codehaus.groovy:groovy:2.3.4"
    testCompile "org.spockframework:spock-core:0.7-groovy-2.0"
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'com.google.android:android-test:4.1.1.4'
    testCompile 'com.android.tools.build:gradle:0.12.2'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

====

我正在为我的Java编写的库进行Spock单元测试,这些库将在我的Android应用程序中使用。

将部署到 Android 应用程序中以执行数据库操作的 Java JAR 文件。我正在测试的就是这个JAR文件。

我已经编写了一个Spock规范来测试SQLite数据库的创建。

在我的Java JAR文件中,我有一个创建SQLite数据库的类,我想在我的Spock单元测试中对其进行测试。

但是,问题在于构造函数需要使用上下文调用,并且我试图在我的Spock单元测试中使用该上下文来模拟该上下文。SQLiteOpenHelperimport android.text.mock.MockContext

public class SnapzAndroidDB extends SQLiteOpenHelper implements SnapzDAO {
    public SnapzAndroidDB(Context context) {
        super(context, SnapzContract.DB_NAME, null, SnapzContract.DB_VERSION);    
    }

    /* Called only once first time the database is created */
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sqlCreate = String.format("create table %s (%s int primary key, %s text, %s text, %s text)",
                                         SnapzContract.TABLE,
                                         SnapzContract.GetConfigColumn.ID,
                                         SnapzContract.GetConfigColumn.NAME,
                                         SnapzContract.GetConfigColumn.VALUE,
                                         SnapzContract.GetConfigColumn.CFG_TYPE);
        db.execSQL(sqlCreate);
    }
    .
    .
}

现在在我的单元测试规范中,我在我的SnapClientTest.groovy中有这个:

import android.test.mock.MockContext

def 'Create an SQLite database for Android'() {
    setup:
    def context = new MockContext()
    def androidDB = new SnapzAndroidDB(context.getApplicationContext())

    expect:
    androidDB != null
}

从这里你可以看到我正在模拟上下文,并将其作为参数发送到我的类的构造函数,该构造函数将调用SQLiteOpenHelper构造函数。

当我运行单元测试时,我得到的错误是这样的:

com.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
11:05:27.062 [DEBUG] [TestEventLogger]     java.lang.RuntimeException: Stub!
11:05:27.063 [DEBUG] [TestEventLogger]         at android.content.Context.<init>(Context.java:4)
11:05:27.063 [DEBUG] [TestEventLogger]         at android.test.mock.MockContext.<init>(MockContext.java:5)
11:05:27.063 [DEBUG] [TestEventLogger]         at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:155)
11:05:27.065 [QUIET] [system.out] 11:05:27.064 [DEBUG] [org.gradle.process.internal.child.ActionExecutionWorker] Stopping client connection.

作为Spock的新手,我不确定这是否可能,因为我只是在测试我的JAR文件。


答案 1

Spock是Groovy和Java生态系统中使用最广泛的框架之一,它允许以非常直观的语言创建BDD测试,并促进一些常见任务,如模拟和可扩展性。使它从人群中脱颖而出的是其美丽且富有表现力的规范语言。得益于其JUnit运行器,Spock与大多数IDE,构建工具和持续集成服务器兼容。要使用Spock,您基本上需要执行一组步骤,例如遵循配方,这将使您能够有效地实现单元测试和Web集成。

您当前的错误消息如下:

Create a sqlite database for Android FAILED

请尝试以下步骤,看看结果如何:

包括到你的代码 getWriteableDatabasegetReadableDatabase 应该会有所帮助:

jokesHelper dbHelper = new jokesHelper(getBaseContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();

这样做,Android将能够管理和缓存连接

然而,如果你从getBaseContext收到任何错误消息,尝试卸载测试插件并使用inducate-with --eclipse重新创建STS资源(.classpath和.project),那么它应该可以工作

如果您在getSpecificationContext上遇到任何问题,这意味着遗漏了一些细节,您需要仔细检查您的规格

如果您没有使用Eclipse,要使用spock创建Java Jar文件,您可以像往常一样通过命令行Java开发工具将其与Emacs连接,例如Sun的JDK或任何其他企业开发方法。若要仅运行 SampleTest,必须使用 Java 系统属性从命令行调用测试任务:

gradle -Dtest.single=Sample test

或者

gradle -Dtest.single=SoapTest clean test

还要检查正在使用的目录的权限。如果您尚未完成,请记住包含依赖项

dependencies {
    classpath 'com.android.tools.build:gradle:0.8.+'
    classpath 'org.robospock:robospock-plugin:0.4.0'
}

并通知您正在使用的测试目录,例如srcDirs。请记住,(“Es ist wichtig das man hier die richtige Resources-Klasse importiert”)导入类所需的正确资源非常重要。因此,在“defaultConfig”中也包括“build.gradle”:

testPackageName "com.yourpackage.test"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
testFunctionalTest true

SpockRobospock 是创新工具,可以帮助开发单元测试的有用资源。或者,您也可以使用TCL测试等工具。TCL测试是SQLite最古老的一组测试,是您可以采取的最佳方法。事实上,SQLite最初是作为Tcl的扩展开始的。SQLite的大部分测试和开发工具都是用Tcl编写的。除了原生的C API之外,Tcl扩展是核心SQLite团队支持的唯一API。

要启用 Tcl 绑定,请从 SQLite 网站下载 SQLite 源代码的 TEA(Tcl 扩展体系结构)发行版。此版本的代码本质上是将 Tcl 绑定附加到末尾的合并分发。这将构建到一个 Tcl 扩展中,然后可以将其导入到任何 Tcl 环境中。

enter image description here

应该遵循非常具体的步骤,并且注意每一个细节都是必不可少的,因为它可以使您的测试成功运行与否。

检测框架是测试框架的基础。检测控制被测应用程序,并允许注入应用程序运行所需的模拟组件。例如,您可以在应用程序启动之前创建模拟上下文,并让应用程序使用它们。

enter image description here

应用程序与周围环境的所有交互都可以使用此方法进行控制。您还可以将应用程序隔离在受限制的环境中,以便能够预测结果,强制某些方法返回的值,或者模拟 ContentProvider、数据库甚至文件系统内容的持久性和未更改的数据。因此,在活动中指定正在运行测试的信息也很重要:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.aatg.sample.test"
        android:versionCode="1" android:versionName="1.0">
        <application android:icon="@drawable/icon"
            android:label="@string/app_name">
            <uses-library android:name="android.test.runner" />
        </application>
        <uses-sdk android:minSdkVersion="7" />
        <instrumentation
            android:targetPackage="com.example.aatg.sample
            android:name="android.test.InstrumentationTestRunner"
            android:label="Sample Tests" />
        <uses-permission android:name="
            android.permission.INJECT_EVENTS" />
    </manifest>

如果您运行 JNI 以使用本机代码操作数据库,则有两种方法可以使用 SQLite 加载扩展。一种是通过 C API 调用,另一种是通过 SQL 函数向下调用与 C API 函数相同的代码。在这两种情况下,您都需要提供文件名和入口点函数的名称(可选):

int sqlite3_load_extension( sqlite3 *db, const char *ext_name,
    const char *entry_point, char **error )

加载可加载扩展的另一种方法是使用内置的 SQL 函数

load_extension( 'ext_name' )
load_extension( 'ext_name', 'entry_point' )

此函数类似于 C sqlite3_load_extension() 调用,但有一个主要限制。由于这是一个 SQL 函数,因此根据定义,在调用它时,加载扩展时将执行一个 SQL 语句。这意味着任何加载了load_extension() SQL 函数的扩展都将完全无法重新定义或删除自定义函数,包括一组专用的 like() 函数。如前所述,使用合适的语法加载数据的方法与 Java 类似。

enter image description here

调试指令仅用于测试和开发目的,因为它们会增加大量开销,并使所有内容的运行速度明显变慢,就像包含 throws Exception 类似。在运行单元测试时,需要相应地设置它们,并检查以避免数据库未损坏。基本上,实现调试设置的最佳调整将得到改进,并帮助您顺利地以最佳方式运行测试

除了所有其他构建指令之外,SQLite 还有相当数量的 SQLITE_OMIT_* 编译时指令。这些旨在从构建中删除核心功能,以使核心数据库库尽可能小而紧凑。为了使用其中大多数省略指令,您需要从源代码管理树中找到的开发源构建 SQLite。大多数省略指令在应用于源代码分发版或预构建的合并时无法正常工作。还要注意,这些编译时指令不受官方支持,因为它们不是官方测试链的一部分。对于任何给定版本的 SQLite,如果启用了任意一组省略标志,则可能同时存在编译问题和运行时问题。

enter image description here

当然,你不需要成为武就可以在Android上运行SQLite的单元测试,尽管它可能会有所帮助。


答案 2

您面临的问题是获取用于创建数据库的正确上下文。

你第一次尝试getBaseContext()不起作用,因为它在SnapClientTest中没有找到这样的函数:“没有方法的签名”

在第二次尝试中,您将创建一个 MockContext 实例 - 这是一个您无法直接使用的存根实现。

http://developer.android.com/reference/android/test/mock/MockContext.html“一个模拟的上下文类。所有方法都是不起作用的,并抛出不支持的操作异常。

尝试:

def androidDB = new SnapzAndroidDB(Robolectric.application)

根据 http://robospock.org/ Robolactric.application应该给出一个工作环境。

更新我刚刚注意到你不是在扩展RoboSpecification,而是在扩展Promission:

import pl.polidea.robospock.RoboSpecification

class SnapClientTest extends RoboSpecification

推荐