今天写了个小程序,针对SQLite数据库的相关操作写了一个单元测试类,然后发现 insert 方法一直报错报错报错: java.lang.NullPointerException。经过慢慢的调试,终于知道错误之处了,太让人忽视的一个细节。
1) 单元测试类 TestBaseDao 部分代码:
/** * 测试 BaseDao 是否可用 * @author johnnie * */ public class TestBaseDao extends AndroidTestCase { private static final String TAG = "TestBaseDao"; private BaseDaoImpl dao = new BaseDaoImpl(getContext()); public void testInsert(){ String table = "tb_url_down"; ContentValues values = new ContentValues(); values.put("url", "http://www.baidu.com"); values.put("dirName", "baidu"); boolean rs = this.dao.insert(table, values); if(rs){ Log.i(TAG, "插入成功!"); } else { Log.i(TAG, "插入失败!"); } } }
2) BaseDaoImpl 部分代码:
public class BaseDaoImpl implements BaseDao { public DBHelper helper; public BaseDaoImpl(Context context) { this.helper = new DBHelper(context); } @Override public boolean insert(String table, ContentValues values) { SQLiteDatabase db = this.helper.getWritableDatabase(); long rs = db.insert(table, null, values); this.close(db); return (rs == 0) ? false : true; } }
刚开始,我还以为我是我 insert 代码有问题,然后仔细查看了下,没发现问题。打印 dao 以及 helper 发现其值也不为 null,那么为什么会出现空指针异常呢?
然后,我将 insert() 中的逻辑抽取出来,放入单元测试类中。如下:
public void test(){ DBHelper helper = new DBHelper(getContext()); SQLiteDatabase db = helper.getWritableDatabase(); String table = "tb_url_down"; ContentValues values = new ContentValues(); values.put("url", "http://www.baidu.com"); values.put("dirName", "baidu"); long rs = db.insert(table, null, values); Log.i(TAG, rs + ""); }执行,结果显示:这样的结果,让我顿时要抓狂了,这是搞莫子鬼罗?明明一样的逻辑,不同的运行结果。再次细细的对比,还是没发现问题啊!没办法,既然报错,肯定是有 bug 的,通过不断的测试,我发现,程序运行在 insert() 方法的第一行就开始报“空指针异常”了。这是什么情况?再次debug,调试代码如下:
public void test(){ DBHelper helper = new DBHelper(getContext()); SQLiteDatabase db = helper.getWritableDatabase(); <span style="color:#ff6666;">// 我在此处加了断点</span> <span style="color:#ff6666;">int i = 1 /0; // 为了抛出异常以便调试,不然直接就执行完毕了</span> String table = "tb_url_down"; ContentValues values = new ContentValues(); values.put("url", "http://www.baidu.com"); values.put("dirName", "baidu"); long rs = db.insert(table, null, values); Log.i(TAG, rs + ""); }还是一样的代码,仅仅是加了一行错误语句而已。debug 显示结果如下:然后,在 debug 一下 testInsert() 方法,显示如下:
对比二者刚刚画红线的 helper,发现第二次调试的 mContext 居然是 null!这是为什么?不是应该在单元测试类 BaseDaoImpl dao = new BaseDaoImpl(getContext()); 时已经传递进去了吗?然后就着程序猿认真的态度,改了下代码试了一下,代码如下:
public void testInsert(){ // <span style="color:#3366ff;">注意:一定注意,这个不能放在外面初始化,否则一直出现空指针错误</span> <span style="color:#ff0000;">dao = new BaseDaoImpl(getContext());</span> String table = "tb_url_down"; ContentValues values = new ContentValues(); values.put("url", "http://www.baidu.com"); values.put("dirName", "baidu"); boolean rs = this.dao.insert(table, values); if(rs){ Log.i(TAG, "插入成功!"); } else { Log.i(TAG, "插入失败!"); } }仅仅是增加了一行代码,然后执行结果截然不同了,运行如下:
成功了,成功了!太心酸了。被这个错误折磨好久,终于解决了,太不容易了。
至此得出了一个结论,在Android单元测试时,外部普通变量的初始化,在测试方法中使用时,容易出错,且错误很隐蔽(此处dao 不为 null,dao.helper 也不为 null,但是传递到 insert 中时, helper 中的 mContext 却是 null)。因此,在编写Android测试类时,尽量在方法体内进行变量的初始化和声明。不要再体外声明普通变量,然后用于测试方法中。