fix: 验收对照表按子项记结果不再虚报通过
This commit is contained in:
+160
-11
@@ -17,13 +17,26 @@ const (
|
||||
StatusPass Status = "pass"
|
||||
StatusFail Status = "fail"
|
||||
StatusUntested Status = "untested"
|
||||
StatusPartial Status = "partial"
|
||||
)
|
||||
|
||||
// SubItem 是 PRD 一句话里的一个子项。
|
||||
type SubItem struct {
|
||||
ID string `json:"id"`
|
||||
Summary string `json:"summary,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
Evidence string `json:"evidence,omitempty"`
|
||||
Note string `json:"note,omitempty"`
|
||||
}
|
||||
|
||||
// Item 单条验收结果。
|
||||
type Item struct {
|
||||
ID string `json:"id"`
|
||||
Status Status `json:"status"`
|
||||
Note string `json:"note,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Status Status `json:"status"`
|
||||
Note string `json:"note,omitempty"`
|
||||
Subs []SubItem `json:"subs,omitempty"`
|
||||
Passed int `json:"passed,omitempty"`
|
||||
Total int `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
// Results 结果文件格式。
|
||||
@@ -89,6 +102,16 @@ func Normalize(r Results) ([]Item, error) {
|
||||
}
|
||||
it.ID = id
|
||||
it.Status = st
|
||||
for i, sub := range it.Subs {
|
||||
subSt, subErr := parseStatus(sub.Status)
|
||||
if subErr != nil {
|
||||
return nil, fmt.Errorf("%s/%s: %w", id, sub.ID, subErr)
|
||||
}
|
||||
it.Subs[i].Status = subSt
|
||||
}
|
||||
if len(it.Subs) > 0 {
|
||||
it = Rollup(it)
|
||||
}
|
||||
byID[id] = it
|
||||
}
|
||||
out := make([]Item, 0, len(Features))
|
||||
@@ -102,12 +125,73 @@ func Normalize(r Results) ([]Item, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Rollup 根据子项计算整项状态:全部通过才算通过,否则部分通过或失败或未测。
|
||||
func Rollup(it Item) Item {
|
||||
if len(it.Subs) == 0 {
|
||||
return it
|
||||
}
|
||||
pass, fail, untested := 0, 0, 0
|
||||
for _, s := range it.Subs {
|
||||
switch s.Status {
|
||||
case StatusPass:
|
||||
pass++
|
||||
case StatusFail:
|
||||
fail++
|
||||
default:
|
||||
untested++
|
||||
}
|
||||
}
|
||||
it.Passed = pass
|
||||
it.Total = len(it.Subs)
|
||||
switch {
|
||||
case fail > 0:
|
||||
it.Status = StatusFail
|
||||
case untested == 0 && pass == len(it.Subs):
|
||||
it.Status = StatusPass
|
||||
case pass == 0 && fail == 0:
|
||||
it.Status = StatusUntested
|
||||
default:
|
||||
it.Status = StatusPartial
|
||||
}
|
||||
it.Note = rollupNote(it)
|
||||
return it
|
||||
}
|
||||
|
||||
func rollupNote(it Item) string {
|
||||
var parts []string
|
||||
for _, s := range it.Subs {
|
||||
switch s.Status {
|
||||
case StatusPass:
|
||||
ev := s.Evidence
|
||||
if ev == "" {
|
||||
ev = "已测"
|
||||
}
|
||||
parts = append(parts, s.ID+" 通过("+ev+")")
|
||||
case StatusFail:
|
||||
msg := s.Note
|
||||
if msg == "" {
|
||||
msg = s.Evidence
|
||||
}
|
||||
parts = append(parts, s.ID+" 失败("+msg+")")
|
||||
default:
|
||||
msg := s.Note
|
||||
if msg == "" {
|
||||
msg = "未测"
|
||||
}
|
||||
parts = append(parts, s.ID+" 未测("+msg+")")
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ";")
|
||||
}
|
||||
|
||||
func parseStatus(s Status) (Status, error) {
|
||||
switch Status(strings.ToLower(string(s))) {
|
||||
case StatusPass, "通过", "ok", "passed":
|
||||
return StatusPass, nil
|
||||
case StatusFail, "失败", "failed":
|
||||
return StatusFail, nil
|
||||
case StatusPartial, "部分通过", "partial_pass":
|
||||
return StatusPartial, nil
|
||||
case StatusUntested, "未测", "skip", "skipped", "":
|
||||
return StatusUntested, nil
|
||||
default:
|
||||
@@ -115,12 +199,17 @@ func parseStatus(s Status) (Status, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func statusCN(s Status) string {
|
||||
switch s {
|
||||
func statusCN(it Item) string {
|
||||
switch it.Status {
|
||||
case StatusPass:
|
||||
return "通过"
|
||||
case StatusFail:
|
||||
return "失败"
|
||||
case StatusPartial:
|
||||
if it.Total > 0 {
|
||||
return fmt.Sprintf("部分通过 (%d/%d)", it.Passed, it.Total)
|
||||
}
|
||||
return "部分通过"
|
||||
default:
|
||||
return "未测"
|
||||
}
|
||||
@@ -140,7 +229,11 @@ func WriteMarkdown(w io.Writer, items []Item) error {
|
||||
if _, err := fmt.Fprintf(w, "生成时间:%s\n\n", time.Now().UTC().Format(time.RFC3339)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "汇总:通过 %d,失败 %d,未测 %d\n\n", summary[StatusPass], summary[StatusFail], summary[StatusUntested]); err != nil {
|
||||
if _, err := fmt.Fprintf(w, "汇总:通过 %d,部分通过 %d,失败 %d,未测 %d\n\n",
|
||||
summary[StatusPass], summary[StatusPartial], summary[StatusFail], summary[StatusUntested]); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "整项「通过」表示该行 PRD 一句话里的子项均有测试证据。部分覆盖不得标通过。\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "| 编号 | 一句话 | 结果 | 备注 |\n|---|---|---|---|\n"); err != nil {
|
||||
@@ -148,17 +241,73 @@ func WriteMarkdown(w io.Writer, items []Item) error {
|
||||
}
|
||||
for _, f := range Features {
|
||||
it := byID[f.ID]
|
||||
note := it.Note
|
||||
if note == "" {
|
||||
note = "—"
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "| %s | %s | %s | %s |\n", f.ID, f.Summary, statusCN(it.Status), note); err != nil {
|
||||
note := compactNote(it)
|
||||
if _, err := fmt.Fprintf(w, "| %s | %s | %s | %s |\n", f.ID, f.Summary, statusCN(it), note); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "\n## 子项\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprintf(w, "| 子项 | 摘要 | 结果 | 证据或原因 |\n|---|---|---|---|\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range Features {
|
||||
it := byID[f.ID]
|
||||
for _, s := range it.Subs {
|
||||
detail := s.Evidence
|
||||
if s.Status != StatusPass {
|
||||
if s.Note != "" {
|
||||
detail = s.Note
|
||||
}
|
||||
}
|
||||
if detail == "" {
|
||||
detail = "—"
|
||||
}
|
||||
subStatus := statusCN(Item{Status: s.Status})
|
||||
if _, err := fmt.Fprintf(w, "| %s | %s | %s | %s |\n", s.ID, s.Summary, subStatus, detail); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compactNote(it Item) string {
|
||||
if it.Status == StatusPass {
|
||||
if it.Total > 0 {
|
||||
return fmt.Sprintf("子项 %d/%d 通过", it.Passed, it.Total)
|
||||
}
|
||||
return "通过"
|
||||
}
|
||||
var bits []string
|
||||
for _, s := range it.Subs {
|
||||
if s.Status == StatusPass {
|
||||
continue
|
||||
}
|
||||
label := "未测"
|
||||
if s.Status == StatusFail {
|
||||
label = "失败"
|
||||
}
|
||||
msg := s.Note
|
||||
if msg == "" {
|
||||
msg = s.Evidence
|
||||
}
|
||||
if msg == "" {
|
||||
bits = append(bits, s.ID+" "+label)
|
||||
continue
|
||||
}
|
||||
bits = append(bits, s.ID+" "+label+":"+msg)
|
||||
}
|
||||
if len(bits) == 0 {
|
||||
if it.Note != "" {
|
||||
return it.Note
|
||||
}
|
||||
return "—"
|
||||
}
|
||||
return strings.Join(bits, ";")
|
||||
}
|
||||
|
||||
// EmptyResults 返回全部未测的样例。
|
||||
func EmptyResults() Results {
|
||||
items := make([]Item, 0, len(Features))
|
||||
|
||||
Reference in New Issue
Block a user