是否可以在测试用例中注册接收器?

Is it possible to register a receiver in a test case?

我想在单元测试中测试使用 AlarmManager 编写的警报是否被触发,如果是,是否在正确的时间段内被触发。

这里是要测试的接收器class。 我在我的测试项目中创建了它。 (注意:它没有在清单中注册)

public class MockBroadcastReceiver extends BroadcastReceiver {

    private static int numTimesCalled = 0;

    MockBroadcastReceiver(){
        numTimesCalled = 0;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        numTimesCalled++;           
    }

    public static int getNumTimesCalled() {
        return numTimesCalled;
    }

    public static void setNumTimesCalled(int numTimesCalled) {
        MockBroadcastReceiver.numTimesCalled = numTimesCalled;
    }
}

这是单元测试。 programReceiver方法实际上属于主项目中的一个class,但我已经将它包含在测试中,这样你就不需要阅读那么多代码了。

public class ATest extends AndroidTestCase {

    MockBroadcastReceiver mockReceiver;

    @Override
    protected void setUp() throws Exception {
        mockReceiver = new MockBroadcastReceiver();
        getContext().registerReceiver(mockReceiver, new IntentFilter());
    }

    @Override
    protected void tearDown() {     
        getContext().unregisterReceiver(mockReceiver);
        mockReceiver = null;
    }


    public void test(){
        //We're going to program twice and check that only the last
        //programmed alarm should remain active.

        final Object flag = new Object();
        MockBroadcastReceiver.setNumTimesCalled(0);

        new Thread (){
            @Override
            public void run(){
                programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000);

                SystemClock.sleep(20000);

                programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000);

                SystemClock.sleep(90000);

                synchronized(flag){
                    flag.notifyAll();
                }
            }
        }.start();

        synchronized(flag){
            try {
                flag.wait();
            } catch (InterruptedException e) {
            }
        }

        assertEquals(1, MockBroadcastReceiver.getNumTimesCalled()); //should have been called at least once, but its 0.
    }


    private static void programReceiver(Context context, Class<? extends BroadcastReceiver> receiverClass, long initialDelay, long period){
        Intent intent = new Intent(context, receiverClass);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);     

        alarmManager.cancel(pendingIntent); //Cancel any previous alarm

        alarmManager.setInexactRepeating (
            AlarmManager.RTC_WAKEUP,
            System.currentTimeMillis() + initialDelay,
            period,
            pendingIntent
        );
    }   
}

当我执行test方法时,接收者应该已经在setUp中动态注册了。然后我对同一个闹钟编程两次。我的目的是测试是否只有最后一个警报保持活动状态,但我根本无法呼叫接收器。测试失败,因为它预计被调用一次(或至少多次 >= 1),但模拟接收器中的计数器为 0。我在 onReceive 方法中设置了一个断点并且它永远不会被击中。我还添加了日志记录,但 logcat 中没有显示任何内容。所以我 100% 确定接收器没有被调用。我也试过增加线程中的睡眠时间,因为 setInexactRepeating 非常不准确地触发,但我可以等待很长时间,它仍然没有被调用。

我也试过在测试项目的清单中注册它而不是以编程方式注册,结果是一样的。

为什么收件人没有被调用?


更新
我可以确认 AlarmManager 不是问题 。根据adb dumpsys alarm正确注册了告警。

我现在正尝试通过调用 sendBroadcast 将接收者设置为 运行,但我已无路可走。接收者不会被调用。我尝试了主应用上下文、测试用例上下文,甚至 ActivityInstrumentationTestCase2。还尝试添加 WakeLocks 但什么也没有。只是无法调用它。我认为这可能是由意图或意图过滤器中的某些标志引起的(android 似乎对标志非常挑剔)。

我不确定为什么您的测试没有按预期进行。几个建议spring要记住:

  1. 我见过的大多数 doco,例如。 [1],建议 Object.wait() 应始终在基于不成立的条件的循环中调用。您的代码不会这样做。或许您可以尝试重新加工它以使其发挥作用。
  2. 为了完整起见,也许您应该为 InterruptedException 输出一些可以被 flag.wait() 抛出的东西,以防万一。

[1] https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--

在 android 源代码中有一个 alarmManagerTest 使用广播接收器执行 alarmManager.setInexactRepeating。

主要区别在于工作 android 测试有 15 分钟的延迟,而您的测试使用 1 分钟的延迟。

AlarmManager 的 Android 文档说:

 public void setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation)

... intervalMillis 后续重复警报之间的间隔(以毫秒为单位)。在 API 19 之前,如果这是 INTERVAL_FIFTEEN_MINUTES、INTERVAL_HALF_HOUR、INTERVAL_HOUR、INTERVAL_HALF_DAY 或 INTERVAL_DAY 之一,则警报将相- 与其他警报对齐以减少唤醒次数。否则,警报将被设置为好像应用程序已调用 setRepeating(int, long, long, PendingIntent)。从 API 19 开始,所有重复警报都将不准确,并且会与其他警报进行批处理,而不管它们规定的重复间隔如何。

我不确定这是否意味着只允许 15 分钟的倍数。

在我的 Android 2.2 手机上,不精确的计时器仅在 15 分钟的倍数时才有效。

所以答案是肯定的,这是可能的,但接收器只能使用隐式(基于操作的)Intents 和 IntentFilters。

显式意图(基于class,没有过滤器)不适用于动态注册的接收器。为了使明确的意图起作用,您需要在清单中注册接收者,而不是像我尝试做的那样动态注册。

这是 Android 中最黑暗、记录最少的功能之一。根据我的意图经验,您永远无法确定。而且它也很难诊断,因为 logcat 中没有打印警告可以帮助理解问题。感谢@d2vid 在这里的回答:

因此,为了修复我的代码,我必须在清单中的应用程序标记内添加接收器元素。由于接收器是在测试项目中创建的模拟接收器 class,我必须编辑测试项目清单,而不是主项目清单。但另一方面,在 Eclipse 测试项目中,添加到清单中的接收者标签似乎不起作用。 Android Studio 看起来更适合清单合并,但这是在 Eclipse 中启动的遗留项目,我们不会移植它。

总而言之:对于主项目中不存在的那些接收器,Eclipse 中的显式意图测试已中断。 class 我需要测试的仅使用显式意图,因此无法在 Eclipse 中编写我的测试。