Как я должен проверить функциональность действия Android, содержащего ListFragment? - PullRequest
4 голосов
/ 25 января 2012

Я хочу проверить функциональность действия, содержащего ListFragment, но я не уверен, как это сделать. Я много пробовал, но ничего не получается.

Таким образом, задание, которое я хочу проверить, содержит ListFragment, и это ListFragment заполняется с помощью LoaderManager.LoaderCallbacks и CursorLoader. Этот CursorLoader запрашивает ContentProvider, а метод onLoadFinished() заменяет Cursor на ListAdapter ListView.

Чего я хочу добиться с помощью своего теста, так это запустить упражнение, а затем проверить, заполнен ли ListView правильными данными. Поскольку содержимое моего ContentProvider основано на содержимом, извлеченном из веб-службы с Service, я подумал, что должен высмеять ContentProvider, чтобы убедиться, что данные теста соответствуют ожиданиям теста. Но это легче сказать, чем сделать. Я столкнулся с всевозможными проблемами.

Я полагаю, что большинство проблем связано с тем, что мои данные загружаются через AsyncTask CursorLoade r. Я запускаю свой загрузчик в методе onCreate() моего ListFragment, но после завершения onCreate() мой тест выполняется, потому что он не ожидает завершения загрузки AsyncTask. И поскольку загрузка не заканчивается до выполнения теста, мой тест не пройден.

Это мой тестовый класс:

public class TopscorersActivityTest extends ActivityUnitTestCase<TopscorersActivity> {

    public static final int TEST_POSITION = 1;
    public static final String TEST_NAME = "name";
    public static final String TEST_CLUB = "club";
    public static final int TEST_GOALS = 2;

    private Intent mStartIntent;
    private ListView mListView;
    private Context mContext;
    private ContentResolver mContentResolver;

    public TopscorersActivityTest() {
        super(TopscorersActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mStartIntent = new Intent();

        mContext = new RenamingDelegatingContext(getInstrumentation().getTargetContext(), "test");
        mContentResolver = mContext.getContentResolver();
        setActivityContext(mContext);

        // Setup database fixture
        ContentValues values = new ContentValues();
        values.put(Topscorers.TOPSCORER_POSITION, TEST_POSITION);
        values.put(Topscorers.TOPSCORER_NAME, TEST_NAME);
        values.put(Topscorers.TOPSCORER_CLUB, TEST_CLUB);
        values.put(Topscorers.TOPSCORER_GOALS, TEST_GOALS);

        mContentResolver.delete(Topscorers.CONTENT_URI, null, null);
        mContentResolver.insert(Topscorers.CONTENT_URI, values);
    }

    public void testPreConditions() {
        startActivity(mStartIntent, null, null);
        assertNotNull(getActivity());

        mListView = (ListView) getActivity().findViewById(android.R.id.list);
        assertNotNull(mListView);

        Cursor cursor = mContentResolver.query(Topscorers.CONTENT_URI, null, null, null, null);
        assertEquals(1, cursor.getCount());
    }

    public void testListPopulatedCorrectly() {
        startActivity(mStartIntent, null, null);
        getInstrumentation().waitForIdleSync();

        ListView listView = (ListView) getActivity().findViewById(android.R.id.list);
        assertEquals(1, listView.getCount());
    }
}

Тест testPreConditions() успешен, но testListPopulatedCorrectly() не пройден, потому что listView.getCount() возвращает 0.

Как мне достичь того, чего я хочу? Я даже иду в правильном направлении с моим тестовым кодом? Или я должен выбрать другой подход? Если да, то что?

Ответы [ 2 ]

4 голосов
/ 25 октября 2012

Вместо использования Thread.wait(500) вызова, упомянутого @ Jan-Henk, в модульном тесте я использовал DataSetObserver , выполнив:

mListView.getAdapter().registerDataSetObserver(new DataSetObserver() {
        @Override
        public void onChanged() {
            assertTrue(mListView.getCount() > 0);
        }
    });

Это позволитполучить изменение именно тогда, когда оно произойдет.

2 голосов
/ 26 января 2012

Поскольку я не получил никакого ответа на свой вопрос, я отвечу на свой вопрос.Я решил пойти с другим подходом, чем в примере кода моего вопроса.Основная схема моего нового подхода:

  • Я переключил свой базовый тестовый класс на ActivityInstrumentationTestCase2

  • Я создал MockHttpClient класс, которыйЯ внедряю в свой код, и этот MockHttpClient возвращает успешный HttpResponse с объектом ответа, содержащим мои данные JSON.Класс MockHttpClient реализует интерфейс HttpClient и возвращает null для всех методов, кроме методов execute(), которые должны возвращать объект HttpResponse.

  • Поскольку ListFragment Я проверяю регистры BroadcastReceiver, чтобы определить, что служба извлечения данных завершена, я также регистрирую BroadcastReceiver в своем тесте.Я блокирую свой тест с помощью CountDownLatch до получения трансляции.

  • Когда трансляция получена, я использую Thread.sleep(500), чтобы позволить моей активности обновить ListView.После этого я выполняю свои утверждения против ListView.

  • Я аннотировал свой тест с помощью FlakyTest(tolerance=5), который выполняет тест до 5 раз, когда утверждения не выполняются.

Я не уверен, что это хороший подход, поэтому, пожалуйста, не стесняйтесь оставлять комментарии.Но пока это работает.Чтобы завершить мой ответ, новый код для моего теста:

TEST CLASS

public class TopscorersActivityTest extends ActivityInstrumentationTestCase2<TopscorersActivity> {

    public static final String JSON = "[" 
        +   "{\"position\": 1, \"name\": \"Bas Dost\", \"club\": \"sc Heerenveen\", \"goals\": \"16\" },"
        +   "{\"position\": 2, \"name\": \"Dries Mertens\", \"club\": \"PSV\", \"goals\": \"13\"},"
        +   "{\"position\": 3, \"name\": \"Luuk de Jong\", \"club\": \"FC Twente\", \"goals\": \"12\"}"
        + "]";

    private TopscorersActivity mActivity;
    private ListView mListView;

    public TopscorersActivityTest() {
        super("com.example.package", TopscorersActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ConnectivityUtils.setHttpClient(MockHttpClient.createInstance(JSON));
        mActivity = getActivity();
        mListView = (ListView) getActivity().findViewById(android.R.id.list);
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        ConnectivityUtils.setHttpClient(null);
    }

    @MediumTest
    public void testPreconditions() {
        assertNotNull(mActivity);
        assertNotNull(mListView);
        assertEquals(0, mListView.getFirstVisiblePosition());
    }

    @FlakyTest(tolerance=5)
    @LargeTest
    public void testListItemsPopulatedCorrectly() throws InterruptedException {
        waitForBroadcast(mActivity, TopscorersService.BROADCAST_ACTION, Intent.CATEGORY_DEFAULT);

        assertEquals(3, mListView.getCount());

        // First list item
        View view = mListView.getChildAt(0);
        assertNotNull(view);

        TextView positionTextView = (TextView) view.findViewById(R.id.topscorerPositionTextView);
        TextView nameTextView = (TextView) view.findViewById(R.id.topscorerNameTextView);
        TextView goalsTextView = (TextView) view.findViewById(R.id.topscorerGoalsTextView);

        assertEquals("1", positionTextView.getText());
        assertEquals("16", goalsTextView.getText());
        assertEquals(
            Html.fromHtml("Bas Dost<br /><i>sc Heerenveen</i>").toString(), 
            nameTextView.getText().toString()
        );

        // Second list item
        view = mListView.getChildAt(1);
        assertNotNull(view);

        positionTextView = (TextView) view.findViewById(R.id.topscorerPositionTextView);
        nameTextView = (TextView) view.findViewById(R.id.topscorerNameTextView);
        goalsTextView = (TextView) view.findViewById(R.id.topscorerGoalsTextView);

        assertEquals("2", positionTextView.getText());
        assertEquals("13", goalsTextView.getText());
        assertEquals(
                Html.fromHtml("Dries Mertens<br /><i>PSV</i>").toString(), 
                nameTextView.getText().toString()
        );

        // Third list item
        view = mListView.getChildAt(2);
        assertNotNull(view);

        positionTextView = (TextView) view.findViewById(R.id.topscorerPositionTextView);
        nameTextView = (TextView) view.findViewById(R.id.topscorerNameTextView);
        goalsTextView = (TextView) view.findViewById(R.id.topscorerGoalsTextView);

        assertEquals("3", positionTextView.getText());
        assertEquals("12", goalsTextView.getText());
        assertEquals(
                Html.fromHtml("Luuk de Jong<br /><i>FC Twente</i>").toString(), 
                nameTextView.getText().toString()
        );
    }

    private void waitForBroadcast(Context context, String action, String category) throws InterruptedException {
        final CountDownLatch signal = new CountDownLatch(1);

        IntentFilter intentFilter = new IntentFilter(action);
        intentFilter.addCategory(category);
        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                signal.countDown();
            }
        };

        context.registerReceiver(broadcastReceiver, intentFilter);
        signal.await(1500, TimeUnit.MILLISECONDS);
        context.unregisterReceiver(broadcastReceiver);
        Thread.sleep(500);
    }
}

MOCK HTTP CLIENT CLASS

public class MockHttpClient implements HttpClient {
    private HttpResponse mHttpResponse;

    /**
     * A MockHttpClient with an HTTP 1.1 200 OK response
     * 
     * @param response
     * @return
     * @throws UnsupportedEncodingException
     */
    public static HttpClient createInstance(String response) 
            throws UnsupportedEncodingException {

        return createInstance(200, "OK", response);
    }

    /**
     * A MockHttpClient with an HTTP 1.1 response
     * 
     * @param statusCode
     * @param reasonPhrase
     * @param response
     * @return
     * @throws UnsupportedEncodingException
     */
    public static HttpClient createInstance(int statusCode, String reasonPhrase, String response) 
        throws UnsupportedEncodingException {

        return createInstance(HttpVersion.HTTP_1_1, statusCode, reasonPhrase, response);
    }

    /**
     * 
     * @param version
     * @param statusCode
     * @param reasonPhrase
     * @param response
     * @return
     * @throws UnsupportedEncodingException
     */
    public static HttpClient createInstance(ProtocolVersion version, int statusCode, String reasonPhrase, String response)
            throws UnsupportedEncodingException {

        StatusLine statusLine = new BasicStatusLine(version, statusCode, reasonPhrase);
        HttpResponse httpResponse = new BasicHttpResponse(statusLine);
        HttpEntity httpEntity = new StringEntity(response);
        httpResponse.setEntity(httpEntity);
        return new MockHttpClient(httpResponse);
    }

    /**
     * Constructor.
     * 
     * @param httpResponse
     */
    private MockHttpClient(HttpResponse httpResponse) {
        mHttpResponse = httpResponse;
    }

    /**
     * 
     * @param request
     * @return
     */
    public HttpResponse execute(HttpUriRequest request) {
        return mHttpResponse;
    }

    @Override
    public HttpResponse execute(HttpUriRequest request, HttpContext context)
            throws IOException, ClientProtocolException {
        return mHttpResponse;
    }

    @Override
    public HttpResponse execute(HttpHost target, HttpRequest request)
            throws IOException, ClientProtocolException {
        return mHttpResponse;
    }

    @Override
    public <T> T execute(HttpUriRequest arg0,
            ResponseHandler<? extends T> arg1) throws IOException,
            ClientProtocolException {
        return null;
    }

    @Override
    public HttpResponse execute(HttpHost target, HttpRequest request,
            HttpContext context) throws IOException,
            ClientProtocolException {
        return mHttpResponse;
    }

    @Override
    public <T> T execute(HttpUriRequest arg0,
            ResponseHandler<? extends T> arg1, HttpContext arg2)
            throws IOException, ClientProtocolException {
        return null;
    }

    @Override
    public <T> T execute(HttpHost arg0, HttpRequest arg1,
            ResponseHandler<? extends T> arg2) throws IOException,
            ClientProtocolException {
        return null;
    }

    @Override
    public <T> T execute(HttpHost arg0, HttpRequest arg1,
            ResponseHandler<? extends T> arg2, HttpContext arg3)
            throws IOException, ClientProtocolException {
        return null;
    }

    @Override
    public ClientConnectionManager getConnectionManager() {
        return null;
    }

    @Override
    public HttpParams getParams() {
        return null;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...