| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- # -*- coding: utf-8 -*-
- """订单链路写接口端到端自测(订单事件 / 发货 / 售后)。
- 策略:本地库现状 + 现场造订单/售后单 → 调 Java 接口 → 直接查库断言落库结果 → 清理恢复。
- 写接口无法用线上 PHP 读回校验(本地库与线上是 PHP→本地单向同步),因此以库内断言为准。
- 用法:python test_order_writes.py
- 前置:python tools/backup_order_tables.py
- """
- import json
- import re
- import time
- from pathlib import Path
- import pymysql
- import requests
- from _root import ROOT
- JAVA_BASE = "http://127.0.0.1:18080"
- TOKEN = "testsession123"
- STORE_ID = 10001
- YML = Path(str(ROOT / "yoshop-java/src/main/resources/application.yml"))
- SESSION = requests.Session()
- SESSION.trust_env = False
- passed = failed = 0
- created = {"order_ids": [], "delivery_ids": [], "refund_ids": []}
- def check(name, cond, extra=""):
- global passed, failed
- if cond:
- passed += 1
- print(f" [ok] {name}")
- else:
- failed += 1
- print(f" [FAIL] {name} {extra}")
- def db():
- pwd = re.findall(r"password:\s*(.+)", YML.read_text(encoding="utf-8"))[0].strip().strip('"')
- return pymysql.connect(host="192.168.1.177", port=3306, user="root", password=pwd,
- database="shop", charset="utf8mb4", autocommit=True,
- cursorclass=pymysql.cursors.DictCursor)
- def java(path, method="get", params=None, body=None):
- url = JAVA_BASE + path
- headers = {"Access-Token": TOKEN}
- if method == "get":
- r = SESSION.get(url, headers=headers, params=params or {}, timeout=60)
- else:
- r = SESSION.post(url, headers=headers, json=body, timeout=60)
- try:
- return r.json()
- except Exception:
- return {"status": -1, "message": r.text[:200], "data": None}
- def one(cur, sql, args=None):
- cur.execute(sql, args or ())
- return cur.fetchone()
- def dec(value):
- return float(value) if value is not None else None
- def now():
- return int(time.time())
- # ---------------------------------------------------------------- 造数 / 清理
- def create_order(cur, items):
- """items: [(goods_sku_id, total_num, goods_price)],返回 (order_id, [order_goods_id...])"""
- order_no = "TESTJ%d" % int(time.time() * 1000)
- total = sum(round(p * n, 2) for _, n, p in items)
- cur.execute(
- "insert into yoshop_order (order_no,order_type,total_price,order_price,coupon_id,coupon_money,"
- "points_money,points_num,pay_price,update_price,buyer_remark,pay_method,pay_status,pay_time,"
- "trade_id,delivery_type,express_price,delivery_status,delivery_time,sync_weixin_shipping,"
- "receipt_status,receipt_time,order_status,points_bonus,merchant_remark,is_settled,settled_time,"
- "is_comment,order_source,order_source_id,order_source_data,platform,user_id,is_delete,store_id,"
- "create_time,update_time) values "
- "(%s,10,%s,%s,0,0,0,0,%s,0,'','balance',20,%s,0,10,0,10,0,0,10,0,10,0,'',0,0,0,10,0,'',"
- "'H5',10001,0,10001,%s,%s)",
- (order_no, total, total, total, now() - 60, now() - 60, now() - 60))
- order_id = cur.lastrowid
- created["order_ids"].append(order_id)
- goods_ids = []
- for sku_id, num, price in items:
- cur.execute(
- "insert into yoshop_order_goods (goods_id,goods_type,goods_name,image_id,deduct_stock_type,"
- "spec_type,goods_sku_id,goods_props,content,goods_no,goods_price,line_price,goods_weight,"
- "is_user_grade,grade_ratio,grade_goods_price,grade_total_money,coupon_money,points_money,"
- "points_num,points_bonus,total_num,total_price,total_pay_price,delivery_status,delivery_num,"
- "is_comment,order_id,user_id,goods_source_id,store_id,create_time) values "
- "(10004,10,'测试商品-百香果',10012,10,20,%s,'','','',%s,%s,1.5,0,0,0,0,0,0,0,0,%s,%s,%s,"
- "10,0,0,%s,10001,0,10001,%s)",
- (sku_id, price, price, num, round(price * num, 2), round(price * num, 2), order_id, now() - 60))
- goods_ids.append(cur.lastrowid)
- # 收货地址
- cur.execute(
- "insert into yoshop_order_address (name,phone,province_id,city_id,region_id,detail,order_id,"
- "user_id,store_id,create_time) values ('测试收货人','13700000000',1,2,3,'测试路1号',%s,10001,10001,%s)",
- (order_id, now() - 60))
- return order_id, goods_ids
- def create_refund(cur, order_id, order_goods_id, refund_type=10):
- cur.execute(
- "insert into yoshop_order_refund (order_id,order_goods_id,user_id,type,apply_desc,audit_status,"
- "refuse_desc,refund_money,is_user_send,send_time,express_id,express_no,is_receipt,status,"
- "store_id,create_time,update_time) values "
- "(%s,%s,10001,%s,'测试售后原因',0,'',0,0,0,'','',0,0,10001,%s,%s)",
- (order_id, order_goods_id, refund_type, now() - 30, now() - 30))
- refund_id = cur.lastrowid
- created["refund_ids"].append(refund_id)
- return refund_id
- def cleanup(cur, base_balance):
- for refund_id in created["refund_ids"]:
- cur.execute("delete from yoshop_order_refund where order_refund_id=%s", (refund_id,))
- cur.execute("delete from yoshop_order_refund_address where order_refund_id=%s", (refund_id,))
- for order_id in created["order_ids"]:
- cur.execute("delete from yoshop_order_delivery where order_id=%s", (order_id,))
- cur.execute("delete from yoshop_order_delivery_goods where order_id=%s", (order_id,))
- cur.execute("delete from yoshop_order_goods where order_id=%s", (order_id,))
- cur.execute("delete from yoshop_order_address where order_id=%s", (order_id,))
- cur.execute("delete from yoshop_order where order_id=%s", (order_id,))
- # 清理测试产生的余额流水(describe 带 TESTJ 订单号或标记)
- cur.execute("delete from yoshop_user_balance_log where `describe` like '%%TESTJ%%'")
- cur.execute("delete from yoshop_user_points_log where `describe` like '%%TESTJ%%'")
- cur.execute("update yoshop_user set balance=%s where user_id=10001", (base_balance,))
- # ---------------------------------------------------------------- 用例
- def case_update_price(cur):
- print("\n== 改价 / 备注 / 改地址 / 删除(订单 10002)")
- before = one(cur, "select * from yoshop_order where order_id=10002")
- base_price = dec(before["order_price"])
- r = java("/store/order.event/updatePrice", "post",
- body={"orderId": 10002, "form": {"order_price": 100, "express_price": 5}})
- check("改价返回成功", r.get("status") == 200 and r.get("message") == "操作成功", r)
- row = one(cur, "select * from yoshop_order where order_id=10002")
- check("order_price=100.00", dec(row["order_price"]) == 100.0, row["order_price"])
- check("express_price=5.00", dec(row["express_price"]) == 5.0, row["express_price"])
- check("pay_price=105.00", dec(row["pay_price"]) == 105.0, row["pay_price"])
- check("update_price=100-原价", round(dec(row["update_price"]), 2) == round(100 - base_price, 2),
- f'{row["update_price"]} 期望 {round(100 - base_price, 2)}')
- r = java("/store/order.event/updatePrice", "post",
- body={"orderId": 10001, "form": {"order_price": 10, "express_price": 0}})
- check("已支付订单改价被拒", r.get("status") == 500 and r.get("message") == "该订单不合法", r)
- r = java("/store/order.event/updatePrice", "post",
- body={"orderId": 10002, "form": {"order_price": 0, "express_price": 0}})
- check("实付款为 0 被拒", r.get("status") == 500 and r.get("message") == "订单实付款价格不能为0.00元", r)
- # 备注
- r = java("/store/order.event/updateRemark", "post",
- body={"orderId": 10002, "form": {"content": "测试商家备注888"}})
- check("备注返回成功", r.get("status") == 200, r)
- row = one(cur, "select merchant_remark from yoshop_order where order_id=10002")
- check("merchant_remark 落库", row["merchant_remark"] == "测试商家备注888", row)
- r = java("/store/order.event/updateRemark", "post", body={"orderId": 10002, "form": {}})
- row = one(cur, "select merchant_remark from yoshop_order where order_id=10002")
- check("空备注写为空串", row["merchant_remark"] == "", row)
- # 改地址
- r = java("/store/order.event/updateAddress", "post",
- body={"orderId": 10002,
- "form": {"name": "新收货人", "phone": "13611112222",
- "cascader": [310000, 310100, 310101], "detail": "新地址88号"}})
- check("改地址返回成功", r.get("status") == 200, r)
- addr = one(cur, "select * from yoshop_order_address where order_id=10002")
- check("收货人已更新", addr["name"] == "新收货人", addr)
- check("省市区已更新", (addr["province_id"], addr["city_id"], addr["region_id"]) == (310000, 310100, 310101), addr)
- check("详细地址已更新", addr["detail"] == "新地址88号", addr)
- # 恢复订单 10002 原值
- cur.execute("update yoshop_order set order_price=%s,pay_price=%s,update_price=%s,express_price=%s,"
- "merchant_remark=%s where order_id=10002",
- (before["order_price"], before["pay_price"], before["update_price"],
- before["express_price"], before["merchant_remark"]))
- cur.execute("update yoshop_order_address set name=%s,phone=%s,province_id=%s,city_id=%s,region_id=%s,"
- "detail=%s where order_id=10002",
- ("檀先生", "18587721340", 1, 2, 3, "长安大街111号"))
- def case_delete(cur):
- print("\n== 软删除(订单 10002)")
- r = java("/store/order.event/delete", "post", body={"orderId": 10002})
- check("删除返回成功", r.get("status") == 200 and r.get("message") == "删除成功", r)
- row = one(cur, "select is_delete from yoshop_order where order_id=10002")
- check("is_delete=1", row["is_delete"] == 1, row)
- r = java("/store/order/detail", "get", params={"orderId": 10002})
- check("删除后详情不可见", r.get("status") == 500 and r.get("message") == "未找到该订单记录", r)
- r = java("/store/order/list", "get", params={"dataType": "all", "page": 1})
- ids = [o["order_id"] for o in r["data"]["list"]["data"]]
- check("删除后列表不含该订单", 10002 not in ids, ids)
- cur.execute("update yoshop_order set is_delete=0 where order_id=10002")
- row = one(cur, "select is_delete from yoshop_order where order_id=10002")
- check("已恢复未删除状态", row["is_delete"] == 0, row)
- def case_confirm_cancel(cur):
- print("\n== 审核取消(订单 10001,余额支付)")
- base_user = one(cur, "select balance,points from yoshop_user where user_id=10001")
- base_stock = one(cur, "select stock_total from yoshop_goods where goods_id=10004")["stock_total"]
- base_sku = one(cur, "select stock_num from yoshop_goods_sku where goods_id=10004 and goods_sku_id='10001'")["stock_num"]
- # 拒绝取消:订单回到进行中,不退款
- cur.execute("update yoshop_order set order_status=21 where order_id=10001")
- r = java("/store/order.event/confirmCancel", "post",
- body={"orderId": 10001, "form": {"status": 0}})
- check("拒绝取消返回成功", r.get("status") == 200, r)
- row = one(cur, "select order_status from yoshop_order where order_id=10001")
- check("拒绝后 order_status=10", row["order_status"] == 10, row)
- user = one(cur, "select balance from yoshop_user where user_id=10001")
- check("拒绝取消不动余额", dec(user["balance"]) == dec(base_user["balance"]), user)
- # 同意取消:退款 + 取消事件
- cur.execute("update yoshop_order set order_status=21 where order_id=10001")
- r = java("/store/order.event/confirmCancel", "post",
- body={"orderId": 10001, "form": {"status": 1}})
- check("同意取消返回成功", r.get("status") == 200, r)
- row = one(cur, "select order_status from yoshop_order where order_id=10001")
- check("同意后 order_status=20", row["order_status"] == 20, row)
- user = one(cur, "select balance from yoshop_user where user_id=10001")
- check("余额已退回 31 元", round(dec(user["balance"]) - dec(base_user["balance"]), 2) == 31.0,
- f'{base_user["balance"]} -> {user["balance"]}')
- log = one(cur, "select * from yoshop_user_balance_log where scene=40 order by log_id desc limit 1")
- check("写入退款流水 scene=40", log is not None and dec(log["money"]) == 31.0, log)
- check("流水描述含订单号", log is not None and "369583349062174103" in (log["describe"] or ""), log)
- stock = one(cur, "select stock_total from yoshop_goods where goods_id=10004")["stock_total"]
- sku = one(cur, "select stock_num from yoshop_goods_sku where goods_id=10004 and goods_sku_id='10001'")["stock_num"]
- check("商品总库存已回退 +1", stock == base_stock + 1, f"{base_stock} -> {stock}")
- check("SKU 库存已回退 +1", sku == base_sku + 1, f"{base_sku} -> {sku}")
- # 恢复
- cur.execute("update yoshop_order set order_status=10 where order_id=10001")
- cur.execute("update yoshop_user set balance=%s where user_id=10001", (base_user["balance"],))
- cur.execute("update yoshop_goods set stock_total=%s where goods_id=10004", (base_stock,))
- cur.execute("update yoshop_goods_sku set stock_num=%s where goods_id=10004 and goods_sku_id='10001'", (base_sku,))
- cur.execute("delete from yoshop_user_balance_log where scene=40")
- def case_delivery(cur):
- print("\n== 发货(新建订单)")
- order_id, goods_ids = create_order(cur, [("10001", 2, 28.00), ("10002", 1, 28.00)])
- print(f" 测试订单 order_id={order_id} goods={goods_ids}")
- # 部分发货
- r = java("/store/order.delivery/delivery", "post",
- body={"orderId": order_id,
- "form": {"deliveryMethod": 10, "expressId": 10003, "expressNo": "TEST123456",
- "isAllPack": False,
- "packGoodsData": [{"orderGoodsId": goods_ids[0], "deliveryNum": 1}]}})
- check("部分发货返回成功", r.get("status") == 200 and r.get("message") == "发货成功", r)
- dl = one(cur, "select * from yoshop_order_delivery where order_id=%s order by delivery_id desc limit 1", (order_id,))
- check("发货单已写入", dl is not None and dl["express_no"] == "TEST123456", dl)
- check("发货方式=10", dl is not None and dl["delivery_method"] == 10, dl)
- check("发货单 store_id=10001", dl is not None and dl["store_id"] == STORE_ID, dl)
- dg = one(cur, "select * from yoshop_order_delivery_goods where delivery_id=%s", (dl["delivery_id"],))
- check("发货商品写入", dg is not None and dg["delivery_num"] == 1 and dg["goods_id"] == 10004, dg)
- g1 = one(cur, "select delivery_num,delivery_status from yoshop_order_goods where order_goods_id=%s", (goods_ids[0],))
- check("订单商品已发 1 件/部分发货", (g1["delivery_num"], g1["delivery_status"]) == (1, 30), g1)
- g2 = one(cur, "select delivery_num,delivery_status from yoshop_order_goods where order_goods_id=%s", (goods_ids[1],))
- check("未发商品状态不变", (g2["delivery_num"], g2["delivery_status"]) == (0, 10), g2)
- order = one(cur, "select delivery_status,delivery_time from yoshop_order where order_id=%s", (order_id,))
- check("订单=部分发货(30)", order["delivery_status"] == 30, order)
- check("订单发货时间已写", order["delivery_time"] > 0, order)
- # 整单发货 + 无需物流
- r = java("/store/order.delivery/delivery", "post",
- body={"orderId": order_id,
- "form": {"deliveryMethod": 20, "isAllPack": True}})
- check("整单发货返回成功", r.get("status") == 200, r)
- dl2 = one(cur, "select * from yoshop_order_delivery where order_id=%s order by delivery_id desc limit 1", (order_id,))
- check("无需物流时单号为空", dl2["express_no"] == "", dl2)
- check("发货方式=20", dl2["delivery_method"] == 20, dl2)
- g1 = one(cur, "select delivery_num,delivery_status from yoshop_order_goods where order_goods_id=%s", (goods_ids[0],))
- check("商品1全部发完", (g1["delivery_num"], g1["delivery_status"]) == (2, 20), g1)
- g2 = one(cur, "select delivery_num,delivery_status from yoshop_order_goods where order_goods_id=%s", (goods_ids[1],))
- check("商品2整单发完", (g2["delivery_num"], g2["delivery_status"]) == (1, 20), g2)
- order = one(cur, "select delivery_status from yoshop_order where order_id=%s", (order_id,))
- check("订单=已发货(20)", order["delivery_status"] == 20, order)
- # 重复发货
- r = java("/store/order.delivery/delivery", "post",
- body={"orderId": order_id, "form": {"deliveryMethod": 10, "isAllPack": True}})
- check("已发货订单再次发货被拒", r.get("status") == 500 and "不满足发货条件" in (r.get("message") or ""), r)
- # 未支付订单发货
- r = java("/store/order.delivery/delivery", "post",
- body={"orderId": 10002, "form": {"deliveryMethod": 10, "isAllPack": True}})
- check("未支付订单发货被拒", r.get("status") == 500 and "不满足发货条件" in (r.get("message") or ""), r)
- # 发货记录列表能查到新发货单
- r = java("/store/order.delivery/list", "get", params={"page": 1})
- found = [d for d in r["data"]["list"]["data"] if d["order_id"] == order_id]
- check("发货列表含新发货单", len(found) >= 1, r["data"]["list"]["total"])
- if found:
- item = found[0]
- check("列表含 orderData/orderData.user", "orderData" in item and item["orderData"].get("user") is not None, list(item.keys()))
- check("列表 goods 含订单商品", item["goods"] and item["goods"][0].get("goods") is not None, item.get("goods"))
- check("列表含物流公司", item.get("express") is not None and item["express"]["express_name"] == "圆通速递", item.get("express"))
- check("列表不含 eorder_html", "eorder_html" not in item, list(item.keys()))
- # 详情
- dl_id = found[0]["delivery_id"] if found else dl2["delivery_id"]
- r = java("/store/order.delivery/detail", "get", params={"deliveryId": dl_id})
- det = r.get("data", {}).get("detail") or {}
- check("发货单详情含 eorder_html", "eorder_html" in det, list(det.keys()))
- check("发货单详情无关联键", "goods" not in det and "orderData" not in det, list(det.keys()))
- return order_id, goods_ids
- def case_refund(cur, order_id, goods_ids):
- print("\n== 售后(审核 / 确认收货退款)")
- base_user = one(cur, "select balance from yoshop_user where user_id=10001")
- refund_id = create_refund(cur, order_id, goods_ids[0], refund_type=10)
- # 拒绝无原因
- r = java("/store/order.refund/audit", "post",
- body={"orderRefundId": refund_id, "form": {"audit_status": 20}})
- check("拒绝未填原因被拦", r.get("status") == 500 and r.get("message") == "请输入拒绝原因", r)
- # 同意无地址
- r = java("/store/order.refund/audit", "post",
- body={"orderRefundId": refund_id, "form": {"audit_status": 10}})
- check("同意未选地址被拦", r.get("status") == 500 and r.get("message") == "请选择退货地址", r)
- # 同意 + 退货地址
- r = java("/store/order.refund/audit", "post",
- body={"orderRefundId": refund_id, "form": {"audit_status": 10, "address_id": 10001}})
- check("同意售后返回成功", r.get("status") == 200, r)
- row = one(cur, "select * from yoshop_order_refund where order_refund_id=%s", (refund_id,))
- check("audit_status=10", row["audit_status"] == 10, row)
- check("退货退款单仍为进行中", row["status"] == 0, row)
- addr = one(cur, "select * from yoshop_order_refund_address where order_refund_id=%s", (refund_id,))
- check("退货地址已写入", addr is not None and addr["name"] == "仓库", addr)
- check("退货地址 region_id 来自商城地址", addr is not None and addr["region_id"] == 5, addr)
- # 退款金额超限
- r = java("/store/order.refund/receipt", "post",
- body={"orderRefundId": refund_id, "form": {"refund_money": 9999}})
- check("超额退款被拦", r.get("status") == 500 and r.get("message") == "退款金额不能大于商品实付款金额", r)
- # 正常收货退款
- r = java("/store/order.refund/receipt", "post",
- body={"orderRefundId": refund_id, "form": {"refund_money": 10}})
- check("确认收货退款成功", r.get("status") == 200, r)
- row = one(cur, "select * from yoshop_order_refund where order_refund_id=%s", (refund_id,))
- check("refund_money=10.00", dec(row["refund_money"]) == 10.0, row)
- check("is_receipt=1", row["is_receipt"] == 1, row)
- check("status=20 已完成", row["status"] == 20, row)
- user = one(cur, "select balance from yoshop_user where user_id=10001")
- check("余额增加 10 元", round(dec(user["balance"]) - dec(base_user["balance"]), 2) == 10.0,
- f'{base_user["balance"]} -> {user["balance"]}')
- log = one(cur, "select * from yoshop_user_balance_log where scene=40 order by log_id desc limit 1")
- check("退款流水已写入", log is not None and dec(log["money"]) == 10.0, log)
- # 换货:同意即完成
- exch = create_refund(cur, order_id, goods_ids[1], refund_type=20)
- r = java("/store/order.refund/audit", "post",
- body={"orderRefundId": exch, "form": {"audit_status": 10, "address_id": 10001}})
- row = one(cur, "select * from yoshop_order_refund where order_refund_id=%s", (exch,))
- check("换货同意后直接完成", r.get("status") == 200 and row["status"] == 20, row)
- # 拒绝
- rej = create_refund(cur, order_id, goods_ids[1], refund_type=10)
- r = java("/store/order.refund/audit", "post",
- body={"orderRefundId": rej, "form": {"audit_status": 20, "refuse_desc": "不符合退货条件"}})
- row = one(cur, "select * from yoshop_order_refund where order_refund_id=%s", (rej,))
- check("拒绝售后 status=10", r.get("status") == 200 and row["status"] == 10, row)
- check("拒绝原因已落库", row["refuse_desc"] == "不符合退货条件", row)
- check("拒绝不写退货地址",
- one(cur, "select id from yoshop_order_refund_address where order_refund_id=%s", (rej,)) is None)
- # 列表 / 详情读回
- r = java("/store/order.refund/list", "get", params={"page": 1})
- rows = [x for x in r["data"]["list"]["data"] if x["order_refund_id"] == refund_id]
- check("售后列表能查到该单", len(rows) == 1, r["data"]["list"]["total"])
- if rows:
- item = rows[0]
- check("列表含订单号", item.get("order_no") is not None, item.get("order_no"))
- check("列表含 orderGoods.orderData.user",
- item.get("orderGoods") is not None and item.get("orderData") is not None and item.get("user") is not None,
- list(item.keys()))
- r = java("/store/order.refund/detail", "get", params={"orderRefundId": refund_id})
- det = r.get("data", {}).get("detail") or {}
- check("详情含 images/address/orderGoods",
- all(k in det for k in ("images", "address", "orderGoods", "orderData", "user", "express")), list(det.keys()))
- check("详情退货地址含 region", det.get("address") and "region" in det["address"], det.get("address"))
- check("详情不含 order_no", "order_no" not in det, list(det.keys()))
- cur.execute("update yoshop_user set balance=%s where user_id=10001", (base_user["balance"],))
- def main():
- cur = db().cursor()
- base_balance = one(cur, "select balance from yoshop_user where user_id=10001")["balance"]
- try:
- case_update_price(cur)
- case_delete(cur)
- case_confirm_cancel(cur)
- order_id, goods_ids = case_delivery(cur)
- case_refund(cur, order_id, goods_ids)
- finally:
- cleanup(cur, base_balance)
- print("\n已清理测试数据")
- print(f"\n通过 {passed} / 失败 {failed}")
- if __name__ == "__main__":
- main()
|