使用 robolectric 的 SQLite 现有数据库单元测试错误

SQLite existing database unit testing error using robolectric

在我的应用程序中,我将 SQLite 数据库放入资产文件夹中。我在我的应用程序中成功地使用了它,但现在我想在数据库 "provider" class 中测试方法,它有多种 CRUD 操作方法。我试图跟进本教程 https://medium.com/@elye.project/android-sqlite-database-unit-testing-is-easy-a09994701162 但我的测试 class 失败并出现异常

android.database.sqlite.SQLiteException: Cannot open SQLite connection, base error code: 14

at org.robolectric.shadows.ShadowSQLiteConnection.rethrow(ShadowSQLiteConnection.java:56)
at org.robolectric.shadows.ShadowSQLiteConnection.access0(ShadowSQLiteConnection.java:33)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.execute(ShadowSQLiteConnection.java:466)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.open(ShadowSQLiteConnection.java:353)
at org.robolectric.shadows.ShadowSQLiteConnection.nativeOpen(ShadowSQLiteConnection.java:61)
at android.database.sqlite.SQLiteConnection.nativeOpen(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
at com.education.frogtravel.ege4task.database.DBHelper.openDataBase(DBHelper.java:78)
at com.education.frogtravel.ege4task.database.DBProvider.<init>(DBProvider.kt:22)
at com.education.frogtravel.ege4task.database.DBProviderTest.setup(DBProviderTest.kt:23)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.robolectric.RobolectricTestRunner.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
at com.intellij.rt.execution.application.AppMainV2.main(AppMainV2.java:131)

Caused by: com.almworks.sqlite4java.SQLiteException: [14] unable to open database file
at com.almworks.sqlite4java.SQLiteConnection.open0(SQLiteConnection.java:1353)
at com.almworks.sqlite4java.SQLiteConnection.open(SQLiteConnection.java:258)
at com.almworks.sqlite4java.SQLiteConnection.open(SQLiteConnection.java:269)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.call(ShadowSQLiteConnection.java:360)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.call(ShadowSQLiteConnection.java:353)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.call(ShadowSQLiteConnection.java:452)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections.call(ShadowSQLiteConnection.java:446)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

方法失败

 public void openDataBase() throws SQLException {

    //Open the database
    String myPath = DB_PATH + DBScheme.DB_NAME;
    myDatabase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);

}

符合openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);

我的整个 SQLiteOpenHelper class

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class DBHelper extends SQLiteOpenHelper {
    private static String DB_PATH = "/data/data/my.package.path/databases/";
    private SQLiteDatabase myDatabase;
private final Context context;


public DBHelper(Context context) {
    super(context, DBScheme.DB_NAME, null, 1);
    this.context = context;
}

public void createDatabase() throws IOException{
    boolean dbExist = checkDataBase();

    if(!dbExist){//If database doesn't exist
        this.getReadableDatabase();

        try{
            copyDatabase();
        }catch(IOException e){
            throw new Error("Error copying database");
        }
    }
}


private boolean checkDataBase() {
    SQLiteDatabase checkDB = null;

    try{
        String myPath = DB_PATH + DBScheme.DB_NAME;
        checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
    }catch (SQLiteException e){
        //Database doesn't exist
    }

    return checkDB != null;
}

private void copyDatabase() throws IOException{
    InputStream inputStream = context.getAssets().open(DBScheme.DB_NAME);
    String outFileName = DB_PATH + DBScheme.DB_NAME;
    OutputStream outputStream = new FileOutputStream(outFileName);

    byte[] buffer = new byte[1024];
    int length;
    while((length = inputStream.read(buffer)) > 0){
        outputStream.write(buffer, 0, length);
    }

    outputStream.flush();
    outputStream.close();
    inputStream.close();
}

public void openDataBase() throws SQLException {

    //Open the database
    String myPath = DB_PATH + DBScheme.DB_NAME;
    myDatabase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);

}

@Override
public synchronized void close() {

    if(myDatabase != null)
        myDatabase.close();

    super.close();

}


@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {

}

@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

}

}

我的测试class

@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(LOLLIPOP), packageName = "my.pachage.path.database")

class DBProviderTest {
    lateinit var dbHelper: DBProvider

    @Before
    fun setup() {
        dbHelper = DBProvider(RuntimeEnvironment.application)
        dbHelper.clearStatistics()
    }

//commented to test because crashed in this line before
//    @After
//    fun tearDown(){
//        dbHelper.clearStatistics()
//    }

    @Test
    fun testDBUpdate(){
        dbHelper = DBProvider(RuntimeEnvironment.application)

        val wordId = 1
        val isRight = true

        dbHelper.updateWordStatistics(wordId, isRight)

        assertEquals(dbHelper.getStatisticsForWord(wordId), Statistics(wordId, 1, 1))
    }
}

DBHelper 在 Java 中,测试 class 在 Kotlin 中。我想发生这种情况是因为我没有在旅途中制作数据库,而是使用现有的数据库。我可以为测试目的编写额外的逻辑,但据我所知,仅仅为了测试而更改应用程序的逻辑是错误的。所以我的问题是如何测试未创建但只是从 android 资产文件夹打开的数据库。\?

很高兴把整个文件都拿下来项目很大,所以需要把它放在一个驱动器或Google驱动器上这个应用程序动态创建 SQLite 数据库,它还动态创建表

public class ManageTables extends AppCompatActivity {

Button btnMakeTable;
Button btnAddTableData;
Button btnToDetails;
Button btnDelete;
EditText etQuizTable;
EditText etTableDes;

public static String strIDT;
public static String NEW_TABLE ;

DBHelper dbHelper = new DBHelper(this);
public SQLiteDatabase db;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_manage_tables);

    setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_PORTRAIT );
    this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);

    btnMakeTable = findViewById(R.id.btnMakeTable);
    btnAddTableData = findViewById(R.id.btnAddTableData);
    btnToDetails = findViewById(R.id.btnToDetails);
    btnDelete = findViewById(R.id.btnDelete);
    etQuizTable = findViewById(R.id.etQuizTable);
    etTableDes = findViewById(R.id.etTableDes);

    // Brig values over from ManageTablesListView
    etQuizTable.setText(MT_QUIZ_TABLE);
    etTableDes.setText(MT_QUIZ_NAME);

}// END onCreate

public void makeTABLE(View view){
    if(etQuizTable.getText().length() < 5 || etQuizTable.getText().length() >14){
        Toast.makeText(getApplicationContext(), "Quiz Name Max Length is 14 Characters\n"
                +"\nQuiz Name Min Length is 5 Characters", Toast.LENGTH_LONG ).show();
        return;
    }

    String tstr = "^(?!.*\s)^(?!.*\W)^(?!.*\d)([a-zA-Z])";
    String astr = etQuizTable.getText().toString().trim();
    Pattern regex = Pattern.compile(tstr);
    Matcher regexMatcher = regex.matcher(astr);

    boolean foundMatch = regexMatcher.find();

    if(foundMatch == false){

        Toast.makeText( getApplicationContext(),"Upper & Lower Case Letters ONLY\n"
                + "\nNO - Numbers in Quiz Name\n"
                + "\nNO - Special Character in Quiz Name\n"
                +"\nNO - Spaces in the Quiz Name", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();

        return ;
    }

    // Make Table Button
    if(etQuizTable.getText().toString().isEmpty()){
        Toast.makeText(getApplicationContext(), "Enter Quiz Name", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();
        return;
    }
    if(etTableDes.getText().toString().isEmpty()){
        Toast.makeText(getApplicationContext(), "Enter Quiz Description", Toast.LENGTH_LONG ).show();
        etTableDes.requestFocus();
        return;
    }

    if(etTableDes.getText().length() < 5 || etTableDes.getText().length() >26){
        Toast.makeText(getApplicationContext(), "Description Max Length is 26 Characters\n"
                +"\nDescription Min Length is 5 Characters", Toast.LENGTH_LONG ).show();
        return;
    }

    NEW_TABLE = etQuizTable.getText().toString().trim();

    db = dbHelper.getWritableDatabase();
    ArrayList<String> arrTblNames = new ArrayList<>();
    Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);

    if (c.moveToFirst()) {
        while ( !c.isAfterLast() ) {
            arrTblNames.add( c.getString( c.getColumnIndex("name")) );
            c.moveToNext();
        }
    }
    c.close();
    db.close();

    for(int i=0;i<arrTblNames.size();i++) {
        if(arrTblNames.get(i).equals(NEW_TABLE)) {
            Toast.makeText(getApplicationContext(), "That Quiz Exists\n\n"
                    +"Choose a New Quiz Name", Toast.LENGTH_LONG ).show();
            etQuizTable.requestFocus();
            return;
        }
    }

    String tablename = NEW_TABLE;
    String tabledes = etTableDes.getText().toString().trim();
    dbHelper.insertIntoTABLE_TRACKER(tablename,tabledes);

    // Create NEW_TABLE and show Toast Message
    // First check for duplicate NEW_TABLE name ABOVE
    NEW_TABLE = etQuizTable.getText().toString().trim();

    dbHelper.onCreateNewTable();
    Toast.makeText(getApplicationContext(), "Quiz Created NOW\n\n"
            +"Add the First Question", Toast.LENGTH_LONG ).show();
    Intent intent = new Intent(ManageTables.this, TableCreate.class );
    startActivity( intent );
}

public void addTABLEDATA(View view){

    chkENTRY();

    // add NEW_TABLE data (records) questions and answers
    if(etQuizTable.getText().toString().equals("")){
        Toast.makeText(getApplicationContext(), "Enter Quiz Name\n\n"
                +"           OR"+"\n\nCreate Quiz First", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();
        return;
    }

    NEW_TABLE = etQuizTable.getText().toString().trim();

    db = dbHelper.getWritableDatabase();
    ArrayList<String> arrTblNames = new ArrayList<>();
    Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);

    if (c.moveToFirst()) {
        while ( !c.isAfterLast() ) {
            arrTblNames.add( c.getString( c.getColumnIndex("name")) );
            c.moveToNext();
        }
    }
    c.close();
    db.close();


    boolean matchFound = false;
    for(int i=0;i<arrTblNames.size();i++) {
        if(arrTblNames.get(i).equals(NEW_TABLE)) {
            Intent intent = new Intent(ManageTables.this, TableCreate.class );
            startActivity( intent );
            matchFound = true;
        }
    }
    if (!matchFound) {
        Toast.makeText(getApplicationContext(), "No Such Quiz\n\n"
                +"           OR"+"\n\nCreate Quiz First", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();
    }
}

public void toDetails(View view){

    chkENTRY();

    // show detail view
    if(etQuizTable.getText().toString().equals("")) {
        Toast.makeText(getApplicationContext(), "Enter Quiz Name\n\n"
                + "           OR" + "\n\nCreate Quiz First", Toast.LENGTH_LONG).show();
        etQuizTable.requestFocus();
        return;
    }

    NEW_TABLE = etQuizTable.getText().toString().trim();

    db = dbHelper.getWritableDatabase();
    ArrayList<String> arrTblNames = new ArrayList<>();
    Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);

    if (c.moveToFirst()) {
        while ( !c.isAfterLast() ) {
            arrTblNames.add( c.getString( c.getColumnIndex("name")) );
            c.moveToNext();
        }
        c.close();
        db.close();
    }

    boolean matchFound = false;
    for(int i=0;i<arrTblNames.size();i++) {

        if (arrTblNames.get(i).equals(NEW_TABLE)) {
            Intent intent = new Intent(ManageTables.this, DetailsActivity.class );
            startActivity( intent );
            matchFound = true;
        }
    }
    if (!matchFound) {
        Toast.makeText(getApplicationContext(), "No Such Quiz\n\n"
                +"           OR"+"\n\nCreate Quiz First", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();
    }
}

// this is btnDelete
public void onDELETE(View view) {

    if(etQuizTable.length() == 0){
        Toast.makeText(getApplicationContext(), "Enter Quiz Name", Toast.LENGTH_SHORT).show();
        return;
    }
    callDIALOG();
}

private void callDIALOG(){

    final Dialog openDialog = new Dialog(this);
    openDialog.setContentView(R.layout.delete_dialog);
    TextView tvDDT = openDialog.findViewById(R.id.tvDDT);
    tvDDT.setText("Your DELETING "+etQuizTable.getText().toString().trim());
    Button btnYES = openDialog.findViewById(R.id.btnYES);
    Button btnNO = openDialog.findViewById(R.id.btnNO);
    openDialog.setCancelable(false);

    btnYES.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            NEW_TABLE = etQuizTable.getText().toString().trim();

            db = dbHelper.getWritableDatabase();
            ArrayList<String> arrTblNames = new ArrayList<>();
            Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);

            if (c.moveToFirst()) {
                while ( !c.isAfterLast() ) {
                    arrTblNames.add( c.getString( c.getColumnIndex("name")) );
                    c.moveToNext();
                }
                c.close();
                db.close();
            }

            boolean matchFound = false;
            for(int i=0;i<arrTblNames.size();i++) {

                if (arrTblNames.get(i).equals(NEW_TABLE)) {
                    Intent intent = new Intent(ManageTables.this, DetailsActivity.class );
                    startActivity( intent );
                    matchFound = true;
                }
            }
            if (!matchFound) {
                Toast.makeText(getApplicationContext(), "NO MATCH\n\n"
                        +"CLICK NO AND"+"\n\nCHECK QUIZ NAME", Toast.LENGTH_LONG ).show();
                etQuizTable.requestFocus();
                return;
            }

            //chkENTRY(null);
            // THIS method deletes the TABLE NAME from TABLE_TRACKER
            // THEN DROPS the corresponding CREATED TABLE from the DB
            // doDrop makes 4 calls to DBHelper
            dbHelper.deleteFROM_TABLE_RESPONSE();
            strIDT = dbHelper.getCol_IDT();
            dbHelper.deleteTABLE_FROM_TABLE_TRACKER();

            dbHelper.dropTABLE();
            Intent intent = new Intent(ManageTables.this,ManageTablesListView.class);
            startActivity(intent);

            Toast.makeText(getApplicationContext(), "Quiz Data Deleted ", Toast.LENGTH_SHORT).show();
            openDialog.dismiss();
        }
    });
    btnNO.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            openDialog.dismiss();
        }
    });
    openDialog.show();
}

public void chkENTRY(){

    String tstr = "^(?!.*\s)^(?!.*\W)^(?!.*\d)([a-zA-Z])";
    String astr = etQuizTable.getText().toString().trim();
    Pattern regex = Pattern.compile(tstr);
    Matcher regexMatcher = regex.matcher(astr);

    boolean foundMatch = regexMatcher.find();

    if(foundMatch == false){

        Toast.makeText( getApplicationContext(),"Upper & Lower Case Letters ONLY\n"
                + "\nNO - Numbers in Quiz Name\n"
                + "\nNO - Special Character in Quiz Name\n"
                +"\nNO - Spaces in the Quiz Name", Toast.LENGTH_LONG ).show();
        etQuizTable.requestFocus();

        return ;
    }
}

public void onBackPressed() {
    Intent intent = new Intent(ManageTables.this,ManageTablesListView.class);
    startActivity(intent);
}

}