【问题标题】:Android unit tests with multiple threadsAndroid 多线程单元测试
【发布时间】:2011-10-10 16:42:28
【问题描述】:

我对 Android 中的单元测试有疑问。

我的对象 MyObject 有一个方法 start() 像这样:

public void start() {
    final Handler onStartHandler = new Handler();
    new Thread() {
        @Override
        public void run() {
            super.run();

            onStartHandler.post(new Runnable() {
                @Override
                public void run() {
                    mIsRunning = true;
                    onStart();
                }
            });
        }
    }.start();
}

我想测试是否调用了 onStart()。 所以我尝试了类似的方法:

public void testOnStartIsCalled() {
    assertFalse("onStart() should not be called", mMyObject.isRunning());
    mMyObject.start();
    assertTrue("onStart() should be called", mMyObject.isRunning());
    mMyObject.stop();
    assertFalse("onStop() should be called", mMyObject.isRunning());
}

但它不起作用,我猜是因为它在一个Handler和一个新线程中。

我的测试类扩展了 AndroidTestCase。 我该怎么办 ?这种情况下的最佳做法是什么?

问候。

【问题讨论】:

    标签: android multithreading unit-testing handler


    【解决方案1】:

    当我处理测试一些多线程代码时,我尝试让程序尽可能多地利用其自然流程。此外,我避免使用 sleep 语句,因为您无法保证您选择的睡眠间隔足以让您的测试对象完成它正在做的事情;您通常最终不得不选择太大的睡眠间隔,这会导致测试用例的执行速度变慢。

    我建议您尝试将一些代码添加到您正在测试的类中,在本例中为 MyObject,它会在发生任何事情时调用侦听器。似乎您已经有了onStart()onStop() 的回调方法(如果这些是事件/回调),所以应该调用这些方法,您应该使用它们来控制测试流程。当您收到onStart() 事件时,您应该调用stop() 并等待onStop() 事件。

    更新

    首先,你有多余的代码:

    public void start() {
        final Handler onStartHandler = new Handler();
        new Thread() {
            @Override
            public void run() {
                super.run();
    
                onStartHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mIsRunning = true;
                        onStart();
                    }
                });
            }
        }.start();
    }
    

    要么启动一个新线程来调用onStart(),要么将runnable调度到Handler的线程队列中。

    版本 1- 删除处理程序,让代码在新线程中执行:

    public void start() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                mIsRunning = true;
                onStart();
            }
        }.start();
    }
    

    版本 2- 仅使用处理程序异步执行回调:

    public void start() {
        final Handler onStartHandler = new Handler();
    
        onStartHandler.post(new Runnable() {
            @Override
            public void run() {
                mIsRunning = true;
                onStart();
            }
        });
    }
    

    其次:我注意到如果您没有Looper,那么您使用Handler 发布的任何内容都将被忽略(因此它永远不会被调用)。有关 Looper-Handler 模式的更多信息,请参阅文章:Android Guts: Intro to Loopers and HandlersLooperHandler 应该附加到同一个线程(通常是主线程)。此外,如果您在单独的线程上创建Handler 作为Looper,那么您将遇到同样的问题:您使用Handler 发布的任何内容都将被忽略。

    这里还有一些关于 loopers 和 handlers 的好问题和文章:

    Looper、Handler和MessageQueue的关系如下图所示:

    【讨论】:

    • 是的,但事实是我想测试如果回调 onStart() 和 onStop() 被调用。而且我认为修改代码以进行测试并不是一个好主意。似乎 Runnable 从未运行过..
    • 你不需要修改实际的类,你已经有了回调方法,所以只需添加另一个Handler,它将在引发onStart 事件时接收回调。该处理程序将通过调用onStartSignal.notify() 对信号对象(例如onStartSignal)调用notify()。测试用例将调用onStartSignal.wait(timeoutMilliseconds),它只会等待指定的超时时间;如果您在分配的超时时间内没有收到回调,那么您的测试用例将失败。
    • 我想它可以工作,但onStart() 从未被调用过,Runnable 从未运行过......但我的代码没问题,它在实际情况下工作......
    • onStart() 永远不会被调用,因为没有任何东西会调用它。 Handler 不执行 Runnable,它只将其排队等待执行,Looper 应该循环遍历队列并执行所有排队的可运行对象。有关更多信息,请参阅我的更新。
    • @Lirik “如果您没有 Looper,那么您使用 Handler 发布的任何内容都将被忽略” - 这也是我注意到的。但是我找不到任何说明为什么发给处理程序的帖子在 ActivityUnitTestCase 中不起作用的文档。我仍在寻找文档,以便确定这是否属实,或者我的代码中某处有问题。
    【解决方案2】:

    这里的问题是你正在调用 onStart() ,它调用了一个新线程,然后立即询问它是否已启动。新线程有启动时间,在这种情况下,您的测试会询问它是否已启动——它还没有启动。

    我敢打赌,如果您使用 Thread.sleep() 或循环等待,您会发现它“最终”启动了。

    你真正想要测试的是什么?

    如果您需要新线程,您可能需要阅读线程、同步等。 http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html

    【讨论】:

    • 我正在尝试测试是否调用了回调(我编辑了我的问题并添加了回调)。我不想使用 Thread.sleep() 因为我真的不知道什么时候会好。但是,Runnable 从未运行过
    猜你喜欢
    • 2010-09-30
    • 1970-01-01
    • 2017-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多