Android 测试用例组合调用
- 新建一个SeActivity
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.se.omapi.Channel;
import android.se.omapi.Reader;
import android.se.omapi.SEService;
import android.se.omapi.Session;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.rockycore.aaccessibilityserviceautotest.R;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class SeActivity extends AppCompatActivity {
private static final String TAG = "SeTest";
private final static String ESE_READER_PREFIX = "eSE";
private final static int WAIT_TIME = 500;
private final static int BEFORE_WAIT = 500;
private static final int PRESS_COUNT = 500;
byte[] other_aid_1 = {(byte) 0xD2, 0x76, 0x00, 0x00, (byte) 0x85, 0x01, 0x01};
byte[] basic_aid_1 = {0x11, 0x22, 0x33, 0x44, 0x55, 0x11};
byte[] logic_aid_1 = {0x11, 0x22, 0x33, 0x44, 0x55, 0x22};
byte[] logic_aid_2 = {0x11, 0x22, 0x33, 0x44, 0x55, 0x44};
byte[] logic_aid_3 = {0x55, 0x44, 0x33, 0x22, 0x11, 0x55};
byte[] logic_aid_4 = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, (byte) 0x88, (byte) 0x99, 0x33};
byte[] sim_aid_1 = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
byte[] partial_aid = {0x11, 0x22, 0x33, 0x44, 0x55};
byte[] nedef_aid = {(byte) 0xD2, 0x76, 0x00, 0x00, (byte) 0x85, 0x01, 0x01};
byte[] nfc_aid = {(byte) 0x01, 0x02, 0x03, 0x04, (byte) 0x05, 0x06};
boolean StrongBoxChannelIsExsit = true;
private TextView tvResults;
private Button btnRunTests;
private Reader reader;
private SEService seService;
private ExecutorService executor;
private Handler handler;
private List<TestCase> testCases = new ArrayList<>();
private TestCaseAdapter testCaseAdapter;
private CheckBox cbSelectAll;
private Button btnSelectTests;
public static String encodeHexString(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void waitTime(long time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void cleanAll(Session session, SEService service) {
try {
session.closeChannels();
session.close();
service.shutdown();
assert service.isConnected();
} catch (Exception e) {
e.printStackTrace();
}
}
public static int getHighBitByte(byte bytes) {
return (bytes >> 4) & 0xff;
}
public static int getLowBitByte(byte bytes) {
return bytes & 0x0f;
}
public static byte[] toByteArray(String hexString) {
hexString = hexString.toLowerCase();
final byte[] byteArray = new byte[hexString.length() >> 1];
int index = 0;
for (int i = 0; i < hexString.length(); i++) {
if (index > hexString.length() - 1) return byteArray;
byte highDit = (byte) (Character.digit(hexString.charAt(index), 16) & 0xFF);
byte lowDit = (byte) (Character.digit(hexString.charAt(index + 1), 16) & 0xFF);
byteArray[i] = (byte) (highDit << 4 | lowDit);
index += 2;
}
return byteArray;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_se_test);
tvResults = findViewById(R.id.tv_results);
btnRunTests = findViewById(R.id.btn_run_tests);
handler = new Handler(Looper.getMainLooper());
executor = Executors.newSingleThreadExecutor();
btnRunTests.setOnClickListener(v -> runAllTests());
btnSelectTests = findViewById(R.id.btn_select_tests);
btnSelectTests.setOnClickListener(v -> showTestSelectionDialog());
populateTestCases();
}
private void populateTestCases() {
Method[] methods = this.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().startsWith("ZFNM_") && Modifier.isPublic(method.getModifiers()) && method.getParameterCount() == 0) {
String testName = method.getName();
String displayName = testName.replace("_", " ").replace("ZFNM", "Test");
testCases.add(new TestCase(testName, displayName));
}
}
}
private void showTestSelectionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("选择测试用例");
View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_test_selection, null);
ListView listView = dialogView.findViewById(R.id.listView);
cbSelectAll = dialogView.findViewById(R.id.cbSelectAll);
testCaseAdapter = new TestCaseAdapter(this, testCases);
listView.setAdapter(testCaseAdapter);
cbSelectAll.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (buttonView.isPressed()) { // 确保是用户操作
for (TestCase testCase : testCases) {
testCase.setSelected(isChecked);
}
testCaseAdapter.notifyDataSetChanged();
}
});
listView.setOnItemClickListener((parent, view, position, id) -> {
TestCase item = testCases.get(position);
item.setSelected(!item.isSelected());
// 更新全选状态(但不自动修改其他项)
boolean allSelected = testCases.stream().allMatch(TestCase::isSelected);
cbSelectAll.setOnCheckedChangeListener(null);
cbSelectAll.setChecked(allSelected);
cbSelectAll.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (buttonView.isPressed()) {
for (TestCase testCase : testCases) {
testCase.setSelected(isChecked);
}
testCaseAdapter.notifyDataSetChanged();
}
});
testCaseAdapter.notifyDataSetChanged();
});
builder.setView(dialogView);
builder.setPositiveButton("执行选择", (dialog, which) -> runSelectedTests());
builder.setNegativeButton("取消", null);
AlertDialog dialog = builder.create();
dialog.show();
}
// 执行选中的测试用例
private void runSelectedTests() {
List<TestCase> selectedTests = new ArrayList<>();
for (TestCase testCase : testCases) {
if (testCase.isSelected()) {
selectedTests.add(testCase);
}
}
if (selectedTests.isEmpty()) {
Toast.makeText(this, "请至少选择一个测试用例", Toast.LENGTH_SHORT).show();
return;
}
// 显示优化后的进度条
ProgressBar progressBar = findViewById(R.id.progressBar);
TextView tvProgress = findViewById(R.id.tvProgress);
progressBar.setVisibility(View.VISIBLE);
tvProgress.setVisibility(View.VISIBLE);
executor.execute(() -> {
try {
int total = selectedTests.size();
for (int i = 0; i < total; i++) {
runSingleTest(selectedTests.get(i).getMethodName());
final int progress = (i + 1) * 100 / total;
handler.post(() -> {
progressBar.setProgress(progress);
tvProgress.setText(progress + "%");
ObjectAnimator animator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.getProgress(), progress);
animator.setDuration(200);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
});
}
} finally {
handler.post(() -> {
progressBar.setVisibility(View.GONE);
tvProgress.setVisibility(View.GONE);
progressBar.setProgress(0);
tvProgress.setText("");
});
}
});
}
private void runSingleTest(String testName) {
handler.post(() -> {
appendLog("Running: " + testName, Color.BLUE);
Log.d(TAG, "Starting test: " + testName);
});
try {
Method method = this.getClass().getMethod(testName);
method.invoke(this);
handler.post(() -> {
appendLog("[PASS] " + testName, Color.GREEN);
Log.d(TAG, "Test passed: " + testName);
});
} catch (Throwable e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
handler.post(() -> {
appendLog("[FAIL] " + testName + ": " + cause.getMessage(), Color.RED);
Log.e(TAG, "Test failed: " + testName, cause);
});
}
}
private void runAllTests() {
executor.execute(() -> {
try {
runTestSequence();
} catch (Exception e) {
appendLog("Test failed: " + e.getMessage(), Color.RED);
}
});
}
private void beforeTest() {
initializeSeService();
}
private void afterTest() {
Log.i(TAG, "after test: wait time " + WAIT_TIME + "ms");
if (seService != null) {
seService.shutdown();
}
waitTime(2000);
}
public void ZFNM_338_at_test_SEService_getReadersWithConnectedService() {
beforeTest();
assert (seService.isConnected());
int s = seService.getReaders().length;
assert (s != 0);
afterTest();
}
private void runTestSequence() {
Method[] methods = this.getClass().getDeclaredMethods();
List<Method> testMethods = new ArrayList<>();
for (Method method : methods) {
if (method.getName().startsWith("ZFNM_") && Modifier.isPublic(method.getModifiers()) && method.getParameterCount() == 0) {
testMethods.add(method);
}
}
ProgressBar progressBar = findViewById(R.id.progressBar);
TextView tvProgress = findViewById(R.id.tvProgress);
runOnUiThread(() -> {
progressBar.setVisibility(View.VISIBLE);
tvProgress.setVisibility(View.VISIBLE);
progressBar.setProgress(0);
tvProgress.setText("0%");
});
AtomicInteger completedTests = new AtomicInteger(0);
int totalTests = testMethods.size();
// 使用单线程执行器确保顺序执行
ExecutorService sequentialExecutor = Executors.newSingleThreadExecutor();
sequentialExecutor.execute(() -> {
for (Method method : testMethods) {
String testName = method.getName();
runOnUiThread(() -> {
appendLog("Running: " + testName, Color.BLUE);
Log.d(TAG, "Starting test: " + testName);
});
try {
// 执行测试方法
method.invoke(SeActivity.this);
runOnUiThread(() -> {
appendLog("[PASS] " + testName, Color.GREEN);
Log.d(TAG, "Test passed: " + testName);
});
} catch (Exception e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
runOnUiThread(() -> {
appendLog("[FAIL] " + testName + ": " + cause.getMessage(), Color.RED);
Log.e(TAG, "Test failed: " + testName, cause);
});
} finally {
int progress = (int) ((completedTests.incrementAndGet() / (float) totalTests) * 100);
runOnUiThread(() -> {
ObjectAnimator animator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.getProgress(), progress);
animator.setDuration(300);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
tvProgress.setText(progress + "%");
});
}
}
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
tvProgress.setVisibility(View.GONE);
});
sequentialExecutor.shutdown();
});
}
private void initializeSeService() {
final CountDownLatch latch = new CountDownLatch(1);
seService = new SEService(this, Runnable::run, () -> {
Log.d(TAG, "SE Service connected");
latch.countDown();
});
try {
if (!latch.await(5, TimeUnit.SECONDS)) {
Log.e(TAG, "SE Service connection timeout");
}
} catch (InterruptedException e) {
Log.e(TAG, "Connection wait interrupted", e);
}
for (Reader readerIn : seService.getReaders()) {
if (readerIn.getName().startsWith(ESE_READER_PREFIX)) {
reader = readerIn;
}
}
}
private void appendLog(String message, int color) {
runOnUiThread(() -> {
SpannableString spannable = new SpannableString("\n" + message);
spannable.setSpan(new ForegroundColorSpan(color), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tvResults.append(spannable);
final int scrollAmount = tvResults.getLayout().getLineTop(tvResults.getLineCount()) - tvResults.getHeight();
tvResults.scrollTo(0, Math.max(scrollAmount, 0));
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (seService != null && seService.isConnected()) {
seService.shutdown();
}
executor.shutdown();
}
private static class TestCase {
private final String methodName;
private final String displayName;
private boolean selected;
public TestCase(String methodName, String displayName) {
this.methodName = methodName;
this.displayName = displayName;
this.selected = false;
}
public String getMethodName() {
return methodName;
}
public String getDisplayName() {
return displayName;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
// 测试用例列表适配器
private class TestCaseAdapter extends ArrayAdapter<TestCase> {
public TestCaseAdapter(Context context, List<TestCase> tests) {
super(context, 0, tests);
}
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_test_case, parent, false);
}
TestCase testCase = getItem(position);
CheckBox cbTest = convertView.findViewById(R.id.cbTestCase);
TextView tvTestName = convertView.findViewById(R.id.tvTestName);
assert testCase != null;
cbTest.setChecked(testCase.isSelected());
tvTestName.setText(testCase.getDisplayName());
cbTest.setOnCheckedChangeListener((buttonView, isChecked) -> {
testCase.setSelected(isChecked);
boolean allSelected = true;
for (int i = 0; i < getCount(); i++) {
if (!getItem(i).isSelected()) {
allSelected = false;
break;
}
}
cbSelectAll.setChecked(allSelected);
});
return convertView;
}
}
}
- 布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:orientation="vertical">
<Button
android:id="@+id/btn_run_tests"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/purple_500"
android:text="运行所有测试用例"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/btn_select_tests"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:backgroundTint="@color/purple_500"
android:text="选择测试用例"
android:textColor="#FFFFFF" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp">
<ProgressBar
android:id="@+id/progressBar"
style="@style/CustomProgressBar"
android:layout_width="match_parent"
android:layout_height="8dp"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:visibility="gone" />
<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="16dp"
android:text="0%"
android:textColor="#2196F3"
android:textSize="14sp" />
</FrameLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="测试结果:"
android:textSize="16sp"
android:textStyle="bold" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:padding="8dp">
<TextView
android:id="@+id/tv_results"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:textColor="#000000"
android:textSize="12sp" />
</ScrollView>
</LinearLayout>
本质通过java反射原理,反射出所有的方法,并异步执行返回结果到UI布局,实现对测试用例一键调用。

浙公网安备 33010602011771号