Android 活动单元测试中模拟数据源的技术

发布于 2024-09-18 07:46:23 字数 1274 浏览 4 评论 0原文

我对单元测试还很陌生,我一直在学习如何使用 android 的 jUnit 框架(使用 ActivityInstrumentationTestCase2),但我在弄清楚如何将模拟数据源注入到和活动,例如:

在活动中,

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState,R.layout.market_screen);
        ListView products = (ListView)findViewById(R.id.product_list);
        MarketsAdapter adapter = new  MarketsAdapter(this, new ProductDataSource());
        products.setAdapter(adapter);

}

我当前将 ProductDataSource 传递到适配器中,该适配器连接到 Web 服务以拉取适配器的产品。在我的测试中,我不想连接到网络服务。将模拟数据源注入活动进行测试的最佳技术是什么?我是否应该在应用程序实例中创建 ProductDataSource,然后在测试中使用 MockApplication 来创建模拟数据源?

谢谢,

我通过在测试类 setUp() 方法中执行以下操作解决了问题:获取对 ListView 的引用并使用 setAdapter(MockDataSource) 设置模拟数据源。这必须使用 runOnUiThread() 方法在 UI 线程上运行。

mActivity = getActivity();
mDataSource = new FakeDataSource();     
mMarketsListView = (ListView)mActivity.findViewById(R.id.product_list);
mActivity.runOnUiThread(
      new Runnable() {
        public void run() {
          mMarketsListView.setAdapter(new MarketsAdapter(mActivity,mDataSource));

        } // end of run() method definition
   } // end of anonymous Runnable object instantiation
); // 

I'm fairy new to unit testing and I've been learning how to use the jUnit framework for android(using ActivityInstrumentationTestCase2) but I'm having trouble working out how to I inject a mock data source into and activity, example:

In the activiy I have this

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState,R.layout.market_screen);
        ListView products = (ListView)findViewById(R.id.product_list);
        MarketsAdapter adapter = new  MarketsAdapter(this, new ProductDataSource());
        products.setAdapter(adapter);

}

I currently pass a ProductDataSource into the adapter which connects to a webservice to pull in products for the adapter. In my tests I don't want to connect to the webservice. What is the best technique to inject a mock data source into the activity for testing? Should I create the ProductDataSource in an Application instance and then use a MockApplication in my tests to create a mock data source?

Thanks

I solved by doing the following in the test class setUp() method: Grab a reference to the ListView and set the Mock Data Source using setAdapter(MockDataSource). This has to be run on the UI thread using runOnUiThread() method.

mActivity = getActivity();
mDataSource = new FakeDataSource();     
mMarketsListView = (ListView)mActivity.findViewById(R.id.product_list);
mActivity.runOnUiThread(
      new Runnable() {
        public void run() {
          mMarketsListView.setAdapter(new MarketsAdapter(mActivity,mDataSource));

        } // end of run() method definition
   } // end of anonymous Runnable object instantiation
); // 

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

娇纵 2024-09-25 07:46:23

从您的分辨率来看,您更多地将“模拟”称为删除一些测试数据。当您更关心功能而不真正关心细节时,这始终是进一步推进开发的好方法。

所以我只是向您提供这个答案,因为您说您是单元测试的新手。因此,如果您正在编写依赖于ProductsDatasource的单元测试,您还可以使用模拟框架来插入“模拟”对象,而不是删除具体的类。我更多地使用 .Net,而不是 Java,但我的所有代码示例都将使用 JUnit 和 JMock 来描述我正在谈论的内容。

模拟的工作原理是为接口创建模拟“具体”对象。请记住,接口只是一个契约,表明您的类将提供指定的方法。

假设您有一个 ProductsDatasource 接口实现,如下所示:

public interface IProductsDatasource {

    public List<Product> getProducts();

}

以及具体类型:

public class ProductsDatasource implements IProductsDatasource {

    private List<Product> mProducts;

    public ProductsDatasource(List<Product> products) {
        mProducts = products;
    }

    public List<Product> getProducts() {
        return mProducts;
    }

}

现在,假设您正在对某些内容进行单元测试,例如 TargetAdapter ,它接受 ProductsDatasource。如果您创建一个新的ProductsDatasource,那么您将具有依赖项。您的单元测试现在取决于您正在测试的类和ProductsDatasource。也许您已经在另一个套件中测试了ProductsDatasource

public class TargetAdapter {

    private IProductsDatasource mDatasource;

    public TargetAdapter(IProductsDatasource datasource) {
        mDatasource = datasource;
    }

    public List<Product> products() {
        return mDatasource.getProducts();
    }

}

这是一个测试用例,没有模拟,它详细说明了我正在谈论的内容。

@Test
public void TargetAdapterReturnsProducts() {

    List<Product> data = new ArrayList<Product>();
    data.add(new Product("Sample Product 1"));
    data.add(new Product("Sample Product 2"));
    data.add(new Product("Sample Product 3"));


    TargetAdapter adapter = new TargetAdapter(new ProductsDatasource(data)); // See the dependency
    List<Product> products = adapter.products();

    Assert.assertNotNull(adapter);
    Assert.assertTrue(products.size() == 3);
}

因此,为了测试我的适配器,我必须创建一个新的适配器和一个新的数据源。我并不真正关心数据源,我只需要确保我的适配器执行我想要它执行的操作。模拟允许我通过指定接口类型并配置我希望它的行为方式来单独测试我的适配器。现在我不再受限于具体的类实现来测试我的适配器。

这里是一个例子,我使用 JMock 创建一个模拟数据源:

@Test
public void MockingTest() {

    final Mockery context = new Mockery();

    final List<Product> mockData = new ArrayList<Product>();
    mockData.add(new Product("Sample Product 1"));
    mockData.add(new Product("Sample Product 2"));
    mockData.add(new Product("Sample Product 3"));

    final IProductsDatasource mockDatasource = context.mock(IProductsDatasource.class);

    context.checking(new Expectations(){{
        oneOf (mockDatasource).getProducts(); will(returnValue(mockData));  // This is where I tell JMock to return my test data when getProducts() is called
    }});

    TargetAdapter adapter = new TargetAdapter(mockDatasource); // No dependency ;)
    List<Product> products = adapter.products();

    Assert.assertNotNull(adapter);
    Assert.assertTrue(products.size() == 3);

}

既然您说您是单元测试的新手,我想指出单元测试中 Mock 对象的强大功能以及如何利用它们来编写更好的代码。您还可以设置模拟对象以确保您的目标对象调用模拟上的方法。我经常使用它,我不关心方法的实现或结果,我只是想确保我的类在应该调用它时调用它。现在要使所有这些工作正常进行,您必须使用接口,但是只需进行重构 -> 就非常容易了。提取接口

我在发布之前在 Eclipse 中运行了所有这些,因此如果您想使用它,代码可以工作。希望这有帮助!

Judging by your resolution, you are more referring to "Mocking" as stubbing out some test data. That's always a great way to more forward with development when you're more concerned with functionality and don't really care about the specifics.

So I'm just providing you with this answer because you said you were new to unit testing. So if you were writing a unit test that was dependent on a ProductsDatasource you could also use a Mocking framework to plug in a "mock" object instead of stubbing out a concrete class. I work more with .Net than Java, but all of my code examples will use JUnit and JMock to describe what I'm talking about.

Mocking works by creating mock "Concrete" objects for interfaces. Remember, an interface is just a contract that says your class will provide the specified methods.

So say you had a ProductsDatasource interface implementation like so:

public interface IProductsDatasource {

    public List<Product> getProducts();

}

And a concrete type of:

public class ProductsDatasource implements IProductsDatasource {

    private List<Product> mProducts;

    public ProductsDatasource(List<Product> products) {
        mProducts = products;
    }

    public List<Product> getProducts() {
        return mProducts;
    }

}

Now, say you're unit testing something, say TargetAdapter, that takes in a ProductsDatasource. If you create a new ProductsDatasource then you would have a dependency. Your unit test would now depend on the class you are testing, and ProductsDatasource. Maybe you've already tested ProductsDatasource in another suite.

public class TargetAdapter {

    private IProductsDatasource mDatasource;

    public TargetAdapter(IProductsDatasource datasource) {
        mDatasource = datasource;
    }

    public List<Product> products() {
        return mDatasource.getProducts();
    }

}

So here is a test case, without mocking, that details what I'm talking about.

@Test
public void TargetAdapterReturnsProducts() {

    List<Product> data = new ArrayList<Product>();
    data.add(new Product("Sample Product 1"));
    data.add(new Product("Sample Product 2"));
    data.add(new Product("Sample Product 3"));


    TargetAdapter adapter = new TargetAdapter(new ProductsDatasource(data)); // See the dependency
    List<Product> products = adapter.products();

    Assert.assertNotNull(adapter);
    Assert.assertTrue(products.size() == 3);
}

So to test my adapter, I have to create a new adapter AND a new datasource. I don't really care about the datasource, I just need to make sure my adapter does what I intended for it to do. Mocking allows me to test my adapter in isolation by specifying the interface type and configuring how I want it to behave. Now I'm not tied down to a concrete class implementation to test my adapter.

So here is an example, where I use JMock to create a mock datasource:

@Test
public void MockingTest() {

    final Mockery context = new Mockery();

    final List<Product> mockData = new ArrayList<Product>();
    mockData.add(new Product("Sample Product 1"));
    mockData.add(new Product("Sample Product 2"));
    mockData.add(new Product("Sample Product 3"));

    final IProductsDatasource mockDatasource = context.mock(IProductsDatasource.class);

    context.checking(new Expectations(){{
        oneOf (mockDatasource).getProducts(); will(returnValue(mockData));  // This is where I tell JMock to return my test data when getProducts() is called
    }});

    TargetAdapter adapter = new TargetAdapter(mockDatasource); // No dependency ;)
    List<Product> products = adapter.products();

    Assert.assertNotNull(adapter);
    Assert.assertTrue(products.size() == 3);

}

Since you stated you were new to unit testing, I wanted to point out the power of Mock objects in unit testing and how you can leverage them to write better code. You can also setup mock objects to make sure your target object calls a method on your mock. I use that a lot where I'm not concerned with the implementation of a method or the result, I just want to make sure my class calls it when it should. Now to make all this work, you have to use interfaces but it's pretty easy to just do a refactor -> extract interface

I ran all this in eclipse before I posted it so the code works if you want to play around with it. Hope this helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文