如何模拟 Android NFC 标签对象进行单元测试
How to mock a Android NFC Tag object for unit testing
我目前正在处理一个需要 NFC 集成的 Android 项目。现在我想编写一些 (j) 单元测试来查看应用程序是否可以接收 NFC 意图(特别是 ACTION_TECH_DISCOVERED
)并将给定的标签(在 NfcAdapter.EXTRA_TAG
中)放在总线系统上。但是令我惊讶的是,我无法创建 Tag
实例或模拟实例。有人可以向我解释我如何进行(单元)测试吗?
此时我什至会接受一种形式的集成测试,过程为:
- 检测 NFC Intent
- 获取
Tag
对象
- 把它包裹在
CardDetectedEvent
. 里上车
我有一个 phone 启用了 NFC 和一些用于测试的卡。
AndroidSDK版本:19
使用的库:robolectric、junit 和 mockito
我认为不可能模拟这些意图,因为它们是由 NFCService 触发的,没有系统权限就不可能触发这些意图,目前 android 框架不支持模拟 nfc 标签。
可以使用反射创建模拟标签对象实例(请注意,这不是 public Android SDK 的一部分,因此在未来的 Android 版本中可能会失败) .
通过反射获取createMockTag()
方法:
Class tagClass = Tag.class;
Method createMockTagMethod = tagClass.getMethod("createMockTag", byte[].class, int[].class, Bundle[].class);
定义一些常量来准备模拟标签实例:
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)
为您的标签类型创建技术附加包。例如,对于带有 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
为您的标签准备防冲突 identifier/UID(参见 Tag.getId()
方法)。例如。类型 2 标签的 7 字节 UID:
byte[] tagId = new byte[] { (byte)0x3F, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x90, (byte)0xAB };
然后你可以通过调用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
然后您可以将此意图发送到您的 activity:
techIntent.setComponent(...); // or equivalent to optionally set an explicit receiver
startActivity(techIntent);
接收方甚至可以使用模拟标签对象来检索技术实例 类。但是,任何需要IO操作的方法都会失败。
我目前正在处理一个需要 NFC 集成的 Android 项目。现在我想编写一些 (j) 单元测试来查看应用程序是否可以接收 NFC 意图(特别是 ACTION_TECH_DISCOVERED
)并将给定的标签(在 NfcAdapter.EXTRA_TAG
中)放在总线系统上。但是令我惊讶的是,我无法创建 Tag
实例或模拟实例。有人可以向我解释我如何进行(单元)测试吗?
此时我什至会接受一种形式的集成测试,过程为:
- 检测 NFC Intent
- 获取
Tag
对象 - 把它包裹在
CardDetectedEvent
. 里上车
我有一个 phone 启用了 NFC 和一些用于测试的卡。
AndroidSDK版本:19
使用的库:robolectric、junit 和 mockito
我认为不可能模拟这些意图,因为它们是由 NFCService 触发的,没有系统权限就不可能触发这些意图,目前 android 框架不支持模拟 nfc 标签。
可以使用反射创建模拟标签对象实例(请注意,这不是 public Android SDK 的一部分,因此在未来的 Android 版本中可能会失败) .
通过反射获取
createMockTag()
方法:Class tagClass = Tag.class; Method createMockTagMethod = tagClass.getMethod("createMockTag", byte[].class, int[].class, Bundle[].class);
定义一些常量来准备模拟标签实例:
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)
为您的标签类型创建技术附加包。例如,对于带有 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
为您的标签准备防冲突 identifier/UID(参见
Tag.getId()
方法)。例如。类型 2 标签的 7 字节 UID:byte[] tagId = new byte[] { (byte)0x3F, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x90, (byte)0xAB };
然后你可以通过调用
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
然后您可以将此意图发送到您的 activity:
techIntent.setComponent(...); // or equivalent to optionally set an explicit receiver
startActivity(techIntent);
接收方甚至可以使用模拟标签对象来检索技术实例 类。但是,任何需要IO操作的方法都会失败。