问题
In my app, I have the SQLite database which I have put into assets folder. I successfully use it in my app, but now I want to test methods in database "provider" class which has several methods for CRUD operations. I tried to follow up this tutorial https://medium.com/@elye.project/android-sqlite-database-unit-testing-is-easy-a09994701162 but my test class fails with the exception
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.access$500(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$1.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$2.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$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.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$1.call(ShadowSQLiteConnection.java:360)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections$1.call(ShadowSQLiteConnection.java:353)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections$6.call(ShadowSQLiteConnection.java:452)
at org.robolectric.shadows.ShadowSQLiteConnection$Connections$6.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)
It fails in the method
public void openDataBase() throws SQLException {
//Open the database
String myPath = DB_PATH + DBScheme.DB_NAME;
myDatabase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
}
in line with openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
My whole 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) {
}
}
My testing 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 is in Java and testing class is in Kotlin. I guess this happens because I don't make the DB on the go, but use already existing one. I can write additional logic for testing purposes, but as I understood it will be wrong, to change the logic of application just for testing. So my question is how to test DB which is not created but simply opened from android assets folder.\?
回答1:
Happy to sip the whole file up project is big so need to put it on ONE Drive or Google Drive This app creates SQLite Databases on the FLY it also creates the tables on the fly
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);
}
}
来源:https://stackoverflow.com/questions/52576283/sqlite-existing-database-unit-testing-error-using-robolectric