OnecardDemo 服务器一键稳定性测试实施计划

For agentic workers: REQUIRED SUB-SKILL: Use executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在服务器单点功能页面实现固定 100 轮、轮间隔 1000 毫秒的一键连接稳定性测试,实时展示进度并保存完整本地报告。

Architecture: 将轮次执行、统计、报告格式化和文件存储拆成无 Android 依赖的 Java 类,由 ServerOperationsActivity 负责页面状态和真实 OnecardWebServiceClient 探测。每轮创建独立客户端,失败只记录不终止;统计与文件逻辑通过 JVM 测试验证,页面控件通过 instrumentation 测试验证。

Tech Stack: Java 8、AndroidX AppCompat、ExecutorServiceHttpURLConnection、JUnit 4、Espresso。


文件结构

Task 1:单轮结果与统计

Files:

覆盖 100 个成功样本的最短、最长、平均和 nearest-rank P95,以及成功/失败计数和零样本。

@Test
public void calculatesSuccessRateAndNearestRankP95() {
    List<ServerStabilityRound> rounds = new ArrayList<>();
    for (int i = 1; i <= 100; i++) {
        rounds.add(ServerStabilityRound.success(i, 1_000L + i, i, 25965));
    }
    ServerStabilityResult result = new ServerStabilityResult(100, 1_000L,
            1_000L, 102_000L, false, rounds);
    assertEquals(100, result.getSuccessCount());
    assertEquals(0, result.getFailureCount());
    assertEquals(100.0d, result.getSuccessRate(), 0.001d);
    assertEquals(1L, result.getMinimumSuccessDurationMillis());
    assertEquals(100L, result.getMaximumSuccessDurationMillis());
    assertEquals(50.5d, result.getAverageSuccessDurationMillis(), 0.001d);
    assertEquals(95L, result.getP95SuccessDurationMillis());
}

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityResultTest'
Expected: 编译失败,提示稳定性结果类型不存在。

实现成功/失败工厂方法、不可变字段、防御性列表复制、成功率和成功耗时统计;无成功样本时耗时访问器返回 -1,平均值返回 -1.0d

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityResultTest'
Expected: BUILD SUCCESSFUL

Task 2:稳定性执行器

Files:

测试三轮中第二轮抛错后第三轮仍执行,并测试停止信号生成部分结果。

@Test
public void continuesAfterFailureAndWaitsOnlyBetweenRounds() {
    FakeTime time = new FakeTime();
    AtomicInteger calls = new AtomicInteger();
    ServerStabilityRunner runner = new ServerStabilityRunner(time, time::sleep);
    ServerStabilityResult result = runner.run(3, 1000L, () -> {
        int call = calls.incrementAndGet();
        time.advance(call == 2 ? 40L : 20L);
        if (call == 2) throw new IOException("temporary");
        return 25965;
    }, () -> false, round -> { });
    assertEquals(3, calls.get());
    assertEquals(2, result.getSuccessCount());
    assertEquals(1, result.getFailureCount());
    assertEquals(2000L, time.getSleptMillis());
}

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityRunnerTest'
Expected: 编译失败,提示 ServerStabilityRunner 不存在。

定义 ProbeStopSignalProgressListenerTimeSourceSleeper 接口;每轮捕获 Exception 并生成失败结果,轮间检查停止信号,最后返回 ServerStabilityResult。默认时间源使用 System.currentTimeMillis()System.nanoTime()

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityRunnerTest'
Expected: BUILD SUCCESSFUL

Task 3:报告格式与存储

Files:

@Test
public void fullReportContainsSummaryRoundsAndGroupedErrors() {
    ServerStabilityResult result = fixtureWithOneSuccessAndTwoSameFailures();
    String report = ServerStabilityReportFormatter.fullReport(
            "http://server/service.asmx", 6L, result);
    assertTrue(report.contains("成功率:33.33%"));
    assertTrue(report.contains("第 1 轮:成功"));
    assertTrue(report.contains("IOException:timeout × 2"));
}

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityReport*Test'
Expected: 编译失败,提示格式化器或存储类不存在。

格式化器用 yyyy-MM-dd HH:mm:ss.SSSLocale.CHINA 输出摘要、逐轮详情和错误分组。存储类在传入目录创建 server-stability-yyyy-MM-dd-HH-mm-ss-SSS.log,使用 StandardCharsets.UTF_8 覆盖写入并返回文件。

Run: ./gradlew testDebugUnitTest --tests '*ServerStabilityReport*Test'
Expected: BUILD SUCCESSFUL,临时目录中存在 UTF-8 报告。

Task 4:页面交互和真实探测

Files:

将以下 ID 加入 requiredViews

R.id.button_server_stability_test,
R.id.text_server_stability_progress,
R.id.text_server_stability_result

Run: ./gradlew compileDebugAndroidTestJavaWithJavac
Expected: 编译失败,提示新增资源 ID 不存在。

在连接状态下增加标题、固定参数说明、按钮、进度和结果文本。结果文本允许选择,初始内容为“尚未执行稳定性测试”。

Activity 固定使用:

private static final int STABILITY_ROUNDS = 100;
private static final long STABILITY_INTERVAL_MS = 1000L;

启动时快照 URL/AppID、清除普通连接状态并提交单线程任务。探测闭包每轮新建 OnecardWebServiceClientfinally 调用 disconnect();进度回调切换主线程更新 已完成 n/100。停止只设置 volatile 标记,当前请求完成或超时后生成部分报告。

使用 getExternalFilesDir("server_stability");为空时退回 new File(getFilesDir(), "server_stability")。页面结果显示摘要和报告绝对路径;写入失败时保留摘要并显示异常。

Run: ./gradlew testDebugUnitTest assembleDebugAndroidTest
Expected: BUILD SUCCESSFUL

Task 5:文档、构建和真机验证

Files:

写明固定参数、成功标准、失败继续、停止语义、统计口径、报告路径和不适用于生产每秒重连的边界。

CHANGELOG.md 顶部记录实现文件、TDD RED/GREEN 证据、构建结果和真机状态;不进行 Git 提交、暂存或推送。

Run: ./gradlew testDebugUnitTest assembleDebug assembleDebugAndroidTest lintDebug --rerun-tasks
Expected: BUILD SUCCESSFUL,Lint 0 error。

Run: adb install -r app/build/outputs/apk/debug/app-debug.apk
Expected: Success。启动 ServerOperationsActivity 后确认新增区域可见、测试按钮可点击,且不自动发起 100 轮真实请求。

由用户在确认现场服务器允许测试后点击开始。现场已连续完成三轮,每轮 100 次;三轮原始报告、独立记录和 300 次综合对比报告均已归档。

执行约束