我想为下面的方法写一个测试
public void addItem(Item item) {
items.add(0, item);
DatabaseHelper.getInstance().writeOneItem(item);
}
类被称为ItemManager,它的职责是管理用户可以保存到列表或从列表中删除的项目。它应该与持久化列表项的Sqlite数据库保持同步。
当DatabaseHelper
(ormlite)没有使用init(Context context)
(这通常是我的android应用程序启动时的一些,但在我的测试中没有完成)时,它的getInstance()
方法将返回null
,并且从上面执行的方法将崩溃。
我该怎么做?我可以从我的测试中调用init(Context context)
,或者在调用它之前检查DatabaseManager.getInstance()
是否为空。但这似乎更像是一种变通办法。在我看来,我不应该在这个方法中做任何数据库的东西,并尽可能地将ItemManager与数据库分开。
从一般设计的角度来看,理想的解决方案不是具体实现的形式,有什么想法吗?
我是单元测试的新手,很难将彼此之间的东西解耦。
在我看来,你的类ItemManager
必须调用DatabaseHelper
来编写项目,但你的单元测试只是想确保它这样做。您不希望测试DatabaseHelper
是否实际将条目写入数据库,这将是另一个测试。
我会修改你的类的设计:DatabaseHelper.getInstance()
不应该直接在方法中完成。您的ItemManager
应该具有DatabaseHelper
实例的私有字段。这样您就可以模拟它并验证它是否被调用。
使用Mockito举例:
public void addItem(Item item) {
items.add(0, item);
this.databaseHelper.writeOneItem(item);
}
@Test
public void my_test() {
// GIVEN
DatabaseHelper databaseHelper = mock(DatbaseHelper.class);
ItemManager manager = new ItemManager(databaseHelper);
Item item = new Item()
// WHEN
manager.addItem(item);
// THEN
verify(databaseHelper).writeOneItem(item); // This verifies that the method writeOneItem of the "mock" is called with the "item" parameter
}
// Another test would check that the item is added to the "items" collection
你的单元测试应该集中在测试一个方法,而不是它使用的类的行为。
在我的例子中,我通过构造函数在ItemManager
中注入DatabaseHelper
,但你可以使用任何方法:构造函数,setter,依赖注入框架等
打破静态依赖并使用模拟框架(如mockito)
class ItemManager {
...
// decoupling
private DatabaseHelper instance = DatabaseHelper.getInstance();
public void addItem(Item item) {
items.add(0, item);
instance.writeOneItem(item);
}
}
With mockito:
class ItemManagerTest{
// declare mock service
@Mock
DatabaseHelper instance;
// inject mock service into your about to be tested class
@InjectMocks
ItemManager manager;
@Test
public void test() {
// Given
Item item = new Item();
...
// When
manager.addItem(item);
// Then
// assert that the service has been called with the right parameters
verify(instance).writeOneItem(item);
}
你可以创建自己的类来包装DatabaseHelper,比如把它命名为MyDBLayer
class abstract MyDBLayer {
public void writeOneItem(Item item);
}
class OrmLiteDBLayer {
public void writeOneItem(Item item) {
DatabaseHelper.getInstance().writeOneItem(item);
}
}
class FakeDBLayer {
public void writeOneItem(Item item) {
// do nothing
}
}
那么你可以在测试中注入FakeDBLayer,在生产中注入OrmLiteDBLayer
我想你想测试方法的行为,即如果items
实际上包含新项目,而不是如果数据被写入数据库。
我将使用依赖注入和模拟对象。
使用像DatabseHelper.getInstance()
这样的东西绝对是方便的,但很难测试。我将把to测试改为
public class ClassToTest {
private DatabaseHelper databaseHelper;
public void setDatabaseHelper(DatabaseHelper databaseHelper) {
this.databaseHelper = databaseHelper;
}
public void addItem(Item item) {
items.add(0, item);
databaseHelper.writeOneItem(item);
}
}
接下来,我将引入一个带有方法void writeOneItem(Item item)
的接口IDatabaseHelper
,并让DatabaseHelper实现这个接口。此外,我将创建一个MockDatabaseHelper,它也实现了接口。
ClassToTest myClass = new ClassToTest();
myClass.setDatabaseHelper(DatabaseHelper.getInstance());
在你的测试中你会使用
ClassToTest myClass = new ClassToTest();
myClass.setDatabaseHelper(new MockDatabaseHelper());
MockDatabaseHelper
中的实现可以为空,也可以是一个简单的日志语句。如果您的类使用DatabaseHelper
中的其他方法,则需要将这些方法也添加到接口中,并向MockDatabaseHelper添加模仿真实DatabaseHelper行为的实现。
正如其他人已经提到的,有一些模拟框架可以为您节省编写mockobject的一些工作。另外,我建议您检查一下依赖注入。