测试报告导出修改
This commit is contained in:
@@ -14,6 +14,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using UIShare.GlobalVariable;
|
||||
using static UIShare.UIViewModel.ParameterVM;
|
||||
@@ -30,6 +31,7 @@ namespace UIShare
|
||||
private IContainerProvider containerProvider;
|
||||
private IEventAggregator _eventAggregator;
|
||||
private ITestReportService _testReportService;
|
||||
private ITestCheckRecordService _testCheckRecordService;
|
||||
|
||||
private readonly Dictionary<Guid, ParameterVM> tmpParameters = [];
|
||||
|
||||
@@ -39,6 +41,9 @@ namespace UIShare
|
||||
|
||||
private readonly Stack<LoopContext> loopStack = new();
|
||||
|
||||
/// <summary>测试项上下文栈:进入 IsTestItem 子程序时压栈,栈非空期间每次 OKExpression 判断都归属栈顶测试项</summary>
|
||||
private readonly Stack<TestItemContext> testItemStack = new();
|
||||
|
||||
public CancellationTokenSource stepCTS = new();
|
||||
public CancellationTokenSource errorStepCTS = new();
|
||||
private bool SubSingleStep = false;
|
||||
@@ -47,13 +52,14 @@ namespace UIShare
|
||||
private volatile bool _disposed = false;
|
||||
|
||||
public Guid TestRoundID;
|
||||
public StepRunning(ScopedContext ScopedContext, SystemConfig systemConfig,IEventAggregator eventAggregator, DeviceManager deviceManager, ITestReportService testReportService)
|
||||
public StepRunning(ScopedContext ScopedContext, SystemConfig systemConfig,IEventAggregator eventAggregator, DeviceManager deviceManager, ITestReportService testReportService, ITestCheckRecordService testCheckRecordService)
|
||||
{
|
||||
_scopedContext = ScopedContext;
|
||||
_systemConfig = systemConfig;
|
||||
_eventAggregator = eventAggregator;
|
||||
_deviceManager= deviceManager;
|
||||
_testReportService = testReportService;
|
||||
_testCheckRecordService = testCheckRecordService;
|
||||
//_devices = containerProvider.Resolve<Devices>();
|
||||
}
|
||||
public async Task<bool> ExecuteErrorSteps(ProgramVM program, int depth = 0, CancellationToken cancellationToken = default)
|
||||
@@ -259,6 +265,7 @@ namespace UIShare
|
||||
loopStopwatchStack.Clear();
|
||||
ResetAllStepStatus(program.StepCollection);
|
||||
tmpParameters.Clear();
|
||||
testItemStack.Clear();
|
||||
TestRoundID = Guid.NewGuid();
|
||||
}
|
||||
int initialLoopStackCount = loopStack.Count;
|
||||
@@ -387,14 +394,24 @@ namespace UIShare
|
||||
SubProgram = step.SubProgram,
|
||||
StepName = step.Name
|
||||
});
|
||||
bool isTestItemStep = step.IsTestItem;
|
||||
if (isTestItemStep)
|
||||
{
|
||||
testItemStack.Push(new TestItemContext { Name = step.Name ?? "未命名测试项" });
|
||||
}
|
||||
stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken);
|
||||
// 先评估本步骤自身(含自身 OKExpression 判断,归属本测试项),再写测试项汇总并弹栈
|
||||
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
||||
if (isTestItemStep)
|
||||
{
|
||||
await FinalizeTestItemAsync(depth);
|
||||
}
|
||||
// 发布退出子程序导航事件
|
||||
_eventAggregator.GetEvent<SubProgramNavigateEvent>().Publish(new SubProgramNavigatePayload
|
||||
{
|
||||
Scope = _systemConfig.Title,
|
||||
Action = NavigateAction.Exit
|
||||
});
|
||||
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
||||
if (SubSingleStep)
|
||||
{
|
||||
SubSingleStep = false;
|
||||
@@ -815,12 +832,46 @@ namespace UIShare
|
||||
paraDic.TryAdd(item.Name, item.Value!);
|
||||
}
|
||||
}
|
||||
bool re = ExpressionEvaluator.EvaluateExpression(step.OKExpression, paraDic);
|
||||
bool re;
|
||||
try
|
||||
{
|
||||
re = ExpressionEvaluator.EvaluateExpression(step.OKExpression, paraDic);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 表达式执行错误也视为 NG,并记录到测试项判断明细
|
||||
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] OKExpression 执行异常: {ex.Message}", depth: depth);
|
||||
re = false;
|
||||
}
|
||||
step.Result = re ? 1 : 2;
|
||||
if (step.Result == 2)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] NG:条件表达式验证失败", depth: depth);
|
||||
}
|
||||
// 测试项范围内:记录本次判断(重复判断逐次记录)
|
||||
if (testItemStack.Count > 0)
|
||||
{
|
||||
var ctx = testItemStack.Peek();
|
||||
bool isSelfCheck = ctx.Name == (step.Name ?? "未命名测试项") && step.SubProgram != null;
|
||||
if (!re) ctx.HasFailure = true;
|
||||
if (!isSelfCheck)
|
||||
{
|
||||
SaveCheckRecordAsync(new TestCheckRecordEntity
|
||||
{
|
||||
TestRoundId = TestRoundID,
|
||||
Scope = _systemConfig.Title,
|
||||
FileName = _systemConfig.CurrentACPFile ?? "",
|
||||
TestItemName = ctx.Name,
|
||||
StepName = step.Name ?? "",
|
||||
Depth = depth,
|
||||
OKExpression = step.OKExpression,
|
||||
Pass = re,
|
||||
Values = ExtractExpressionValues(step.OKExpression, paraDic),
|
||||
IsSummary = false,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -831,6 +882,90 @@ namespace UIShare
|
||||
}
|
||||
step.Result = 2;
|
||||
}
|
||||
|
||||
// 测试项范围内的步骤执行错误/表达式 NG 均计入当前测试项汇总(自身步骤的错误已在压栈期间计入)
|
||||
if (step.Result == 2 && testItemStack.Count > 0)
|
||||
{
|
||||
testItemStack.Peek().HasFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试项执行结束:写入汇总记录(PASS/NG)并弹栈。
|
||||
/// 测试项内无任何判断时,结果由范围内步骤执行成败决定。
|
||||
/// </summary>
|
||||
private async Task FinalizeTestItemAsync(int depth)
|
||||
{
|
||||
if (testItemStack.Count == 0) return;
|
||||
var ctx = testItemStack.Pop();
|
||||
SaveCheckRecordAsync(new TestCheckRecordEntity
|
||||
{
|
||||
TestRoundId = TestRoundID,
|
||||
Scope = _systemConfig.Title,
|
||||
FileName = _systemConfig.CurrentACPFile ?? "",
|
||||
TestItemName = ctx.Name,
|
||||
StepName = ctx.Name,
|
||||
Depth = depth,
|
||||
OKExpression = null,
|
||||
Pass = !ctx.HasFailure,
|
||||
Values = null,
|
||||
IsSummary = true,
|
||||
CreateTime = DateTime.Now
|
||||
});
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存测试项判断记录(不阻塞执行主流程)
|
||||
/// </summary>
|
||||
private void SaveCheckRecordAsync(TestCheckRecordEntity entity)
|
||||
{
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _testCheckRecordService.InsertAsync(entity);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
LoggerHelper.Error($"保存测试项判断记录失败 [{entity.TestItemName}]: {result.Msg}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"保存测试项判断记录失败 [{entity.TestItemName}]: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从表达式中提取实际出现的变量并取其当前值,拼接为 "变量=值; " 格式(长名优先,避免子串误匹配)
|
||||
/// </summary>
|
||||
private static string? ExtractExpressionValues(string expression, Dictionary<string, object> paraDic)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var matchedSpans = new List<(int Start, int End)>();
|
||||
foreach (var name in paraDic.Keys.OrderByDescending(k => k.Length))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||
foreach (Match m in Regex.Matches(expression, $@"\b{Regex.Escape(name)}\b"))
|
||||
{
|
||||
bool overlaps = matchedSpans.Any(s => m.Index < s.End && m.Index + m.Length > s.Start);
|
||||
if (!overlaps)
|
||||
{
|
||||
matchedSpans.Add((m.Index, m.Index + m.Length));
|
||||
sb.Append($"{name}={paraDic[name]}; ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.Length > 0 ? sb.ToString() : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -864,6 +999,7 @@ namespace UIShare
|
||||
tmpParameters.Clear();
|
||||
loopStack.Clear();
|
||||
loopStopwatchStack.Clear();
|
||||
testItemStack.Clear();
|
||||
stepStopwatch.Stop();
|
||||
}
|
||||
|
||||
@@ -879,6 +1015,13 @@ namespace UIShare
|
||||
public StepVM? LoopStartStep { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>测试项执行上下文:记录测试项名称与范围内是否出现过失败</summary>
|
||||
private class TestItemContext
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public bool HasFailure { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user