【问题标题】:How to mock a Android NFC Tag object for unit testing如何模拟 Android NFC Tag 对象以进行单元测试
【发布时间】:2015-08-30 18:00:03
【问题描述】:

我目前正在开发一个需要 NFC 集成的 Android 项目。现在我想编写一些 (j) 单元测试来查看应用程序是否可以接收 NFC 意图(特别是 ACTION_TECH_DISCOVERED)并将给定的标签(在 NfcAdapter.EXTRA_TAG 中)放在总线系统上。但是令我惊讶的是,我无法创建 Tag 实例或模拟实例。有人可以向我解释如何(单元)测试吗?

在这一点上,我什至会接受一种集成测试形式,即:

  1. 检测 NFC 意图
  2. 获取Tag 对象
  3. 把它放在公共汽车上用CardDetectedEvent包裹起来。

我有一部启用 NFC 的手机和几张卡用于测试。

Android SDK 版本:19
使用的库:robolectric、junit 和 mockito

【问题讨论】:

    标签: android unit-testing android-intent mocking nfc


    【解决方案1】:

    可以使用反射创建模拟标记对象实例(请注意,这不是公共 Android SDK 的一部分,因此在未来的 Android 版本中可能会失败)。

    1. 通过反射获取createMockTag() 方法:

      Class tagClass = Tag.class;
      Method createMockTagMethod = tagClass.getMethod("createMockTag", byte[].class, int[].class, Bundle[].class);
      
    2. 定义一些常量来准备模拟标签实例:

      final int TECH_NFC_A = 1;
      final String EXTRA_NFC_A_SAK = "sak";    // short (SAK byte value)
      final String EXTRA_NFC_A_ATQA = "atqa";  // byte[2] (ATQA value)
      
      final int TECH_NFC_B = 2;
      final String EXTRA_NFC_B_APPDATA = "appdata";    // byte[] (Application Data bytes from ATQB/SENSB_RES)
      final String EXTRA_NFC_B_PROTINFO = "protinfo";  // byte[] (Protocol Info bytes from ATQB/SENSB_RES)
      
      final int TECH_ISO_DEP = 3;
      final String EXTRA_ISO_DEP_HI_LAYER_RESP = "hiresp";  // byte[] (null for NfcA)
      final String EXTRA_ISO_DEP_HIST_BYTES = "histbytes";  // byte[] (null for NfcB)
      
      final int TECH_NFC_F = 4;
      final String EXTRA_NFC_F_SC = "systemcode";  // byte[] (system code)
      final String EXTRA_NFC_F_PMM = "pmm";        // byte[] (manufacturer bytes)
      
      final int TECH_NFC_V = 5;
      final String EXTRA_NFC_V_RESP_FLAGS = "respflags";  // byte (Response Flag)
      final String EXTRA_NFC_V_DSFID = "dsfid";           // byte (DSF ID)
      
      final int TECH_NDEF = 6;
      final String EXTRA_NDEF_MSG = "ndefmsg";              // NdefMessage (Parcelable)
      final String EXTRA_NDEF_MAXLENGTH = "ndefmaxlength";  // int (result for getMaxSize())
      final String EXTRA_NDEF_CARDSTATE = "ndefcardstate";  // int (1: read-only, 2: read/write, 3: unknown)
      final String EXTRA_NDEF_TYPE = "ndeftype";            // int (1: T1T, 2: T2T, 3: T3T, 4: T4T, 101: MF Classic, 102: ICODE)
      
      final int TECH_NDEF_FORMATABLE = 7;
      
      final int TECH_MIFARE_CLASSIC = 8;
      
      final int TECH_MIFARE_ULTRALIGHT = 9;
      final String EXTRA_MIFARE_ULTRALIGHT_IS_UL_C = "isulc";  // boolean (true: Ultralight C)
      
      final int TECH_NFC_BARCODE = 10;
      final String EXTRA_NFC_BARCODE_BARCODE_TYPE = "barcodetype";  // int (1: Kovio/ThinFilm)
      
    3. 为您的标签类型创建技术附加包。例如,对于带有 NDEF 消息的 NFC-A 标签:

      Bundle nfcaBundle = new Bundle();
      nfcaBundle.putByteArray(EXTRA_NFC_A_ATQA, new byte[]{ (byte)0x44, (byte)0x00 }); //ATQA for Type 2 tag
      nfcaBundle.putShort(EXTRA_NFC_A_SAK , (short)0x00); //SAK for Type 2 tag
      
      Bundle ndefBundle = new Bundle();
      ndefBundle.putInt(EXTRA_NDEF_MAXLENGTH, 48); // maximum message length: 48 bytes
      ndefBundle.putInt(EXTRA_NDEF_CARDSTATE, 1); // read-only
      ndefBundle.putInt(EXTRA_NDEF_TYPE, 2); // Type 2 tag
      NdefMessage myNdefMessage = ...; // create an NDEF message
      ndefBundle.putParcelable(EXTRA_NDEF_MSG, myNdefMessage);  // add an NDEF message
      
    4. 为您的标签准备一个防冲突标识符/UID(参见Tag.getId() 方法)。例如。类型 2 标记的 7 字节 UID:

      byte[] tagId = new byte[] { (byte)0x3F, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x90, (byte)0xAB };
      
    5. 然后你可以通过调用createMockTag()方法来创建一个mock标签实例

      Tag mockTag = (Tag)createMockTagMethod.invoke(null,
              tagId,                                     // tag UID/anti-collision identifier (see Tag.getId() method)
              new int[] { TECH_NFC_A, TECH_NDEF },       // tech-list
              new Bundle[] { nfcaBundle, ndefBundle });  // array of tech-extra bundles, each entry maps to an entry in the tech-list
      

    创建该模拟标签对象后,您可以将其作为 NFC 发现意图的一部分发送。例如。对于TECH_DISCOVERED 意图:

    Intent techIntent = new Intent(NfcAdapter.ACTION_TECH_DISCOVERED);
    techIntent.putExtra(NfcAdapter.EXTRA_ID, tagId);
    techIntent.putExtra(NfcAdapter.EXTRA_TAG, mockTag);
    techIntent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[]{ myNdefMessage });  // optionally add an NDEF message
    

    然后您可以将此意图发送到您的活动:

    techIntent.setComponent(...); // or equivalent to optionally set an explicit receiver
    startActivity(techIntent);
    

    接收者甚至可以使用模拟标签对象来检索技术类的实例。但是,任何需要 IO 操作的方法都会失败。

    【讨论】:

    • 什么是createMockTag
    • @igorganapolsky 它是(或者曾经是?)android api 中的一个隐藏方法,它可以创建一个标签对象以在没有底层 NFC 发现事件的情况下进行测试。
    • 有人知道这段代码是否仍然适用于 SdkVersion >= 26?我收到以下错误:java.lang.NoSuchMethodException: android.nfc.Tag.createMockTag [class [B, class [I, class [Landroid.os.Bundle;]
    • @Johan Ferreira 我遇到了同样的错误。您找到解决方法了吗?
    • @StefanTo 不,不幸的是没有!最后,我创建了一个 NFC 帮助程序类,并通过测试向该类发送一个 tagId(字符串值)来模拟标签扫描。希望有帮助!?
    【解决方案2】:

    我认为不可能模拟这些意图,因为这些意图是由 NFCService 触发的,并且没有系统权限就无法触发这些意图,并且目前 android 框架中不支持模拟 nfc 标签。

    【讨论】:

    • 可以触发这些模拟的 Intent。只需使用正确的 Intent 调用 context.startActivity()。
    • 这是解决 OP 问题的方法吗?
    猜你喜欢
    • 2019-08-22
    • 2013-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    • 2016-04-04
    • 2018-04-04
    • 2021-09-27
    相关资源
    最近更新 更多