Android 测试用例组合调用

  1. 新建一个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;
        }
    }

}

  1. 布局文件如下:

<?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布局,实现对测试用例一键调用。

posted @ 2025-08-04 15:50  浮尘~若梦  阅读(11)  评论(0)    收藏  举报