我想为以下方法编写测试:
public void addItem(Item item) {
items.add(0, item);
DatabaseHelper.getInstance().writeOneItem(item);
}
该类称为ItemManager,它的职责是管理用户可以保存到列表或从列表中删除的项目。它应与保留列表中项目的Sqlite数据库保持同步。
当DatabaseHelper
(ormlite)没有init(Context context)
时(通常在我的Andoid应用启动时播放,但在我的测试中未完成),它的{{1} }}方法将返回getInstance()
,并且从上面执行的方法将崩溃。
我该怎么办?我可以从我的测试中调用null
,或者在调用任何内容之前检查init(Context context)
是否为null。但这似乎更像是一种解决方法。在我看来,我不应该在这个方法中做任何数据库的东西,并尝试尽可能地从数据库中分离ItemManager。
关于理想解决方案的外观的任何想法,不是从具体实施的形式,而是从一般的设计角度来看?
我是单位测试的新手,并且很难将相互之间的东西分离。
答案 0 :(得分:5)
在我看来,您的班级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
您的单元测试应该专注于测试ONE方法而不是它使用的类的行为。
在我的示例中,我将DatabaseHelper
注入ItemManager
via构造函数,但您可以使用任何方法:constructor,setter,dependency injection framework等。
答案 1 :(得分:3)
打破静态依赖并使用模拟框架(如mockito)
class ItemManager {
...
// decoupling
private DatabaseHelper instance = DatabaseHelper.getInstance();
public void addItem(Item item) {
items.add(0, item);
instance.writeOneItem(item);
}
}
使用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);
}
答案 2 :(得分:1)
您可以创建自己的包装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
}
}
您可以在测试和生产中的OrmLiteDBLayer中注入FakeDBLayer。
答案 3 :(得分:1)
我想你想测试方法的行为,即items
实际上是否包含新项,而不是数据是否写入数据库。
我使用依赖注入和模拟对象。
使用像DatabseHelper.getInstance()
这样的东西绝对方便,但很难测试。我将测试改为
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);
}
}
接下来,我将使用方法IDatabaseHelper
引入接口void writeOneItem(Item item)
,并让DatabaseHelper实现此接口。另外,我还创建了一个MockDatabaseHelper,它也实现了接口。
在常规代码中,您可以使用
ClassToTest myClass = new ClassToTest();
myClass.setDatabaseHelper(DatabaseHelper.getInstance());
在您的测试中,您使用
ClassToTest myClass = new ClassToTest();
myClass.setDatabaseHelper(new MockDatabaseHelper());
MockDatabaseHelper
中的实现可以清空或是一个简单的日志语句。如果您的类使用DatabaseHelper
中的其他方法,您还需要添加这些接口,并向MockDatabaseHelper添加一个实现模拟真实DatabaseHelper行为的实现。
正如其他人已经提到的那样,有一些模拟框架可以为您节省编写MockObjects的一些工作。 另外,我建议一般查看Dependency Injection。