5.6日报
完成软件测试实验一:
实验一 黑盒测试
一、实验目的
1、 掌握黑盒测试的基础知识;
2、 掌握黑盒测试的检查内容及测试目的;
3、 掌握黑盒测试的几种基本测试方法:等价类划分方法、边界值分析方法、因果图法和决策表法;
二、实验要求
1、 复习有关内容,理解黑盒测试;
2、 掌握等价类划分、边界值分析方法、因果图法和决策表法,并能设计出测试用例;
3、 对具体软件,能分别使用相应的黑盒测试方法设计测试用例,并实施测试、分析测试结果。
三、实验内容
1、设计函数实现输入日期显示星期几,并用等价类及边界值法测试
实验步骤:
① 设计程序
② from flask import Flask, render_template, request
import datetime
app = Flask(__name__)
def get_weekday_from_date(date_str):
try:
date_obj =
datetime.datetime.strptime(date_str, "%Y-%m-%d")
weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
return weekdays[date_obj.weekday()]
except ValueError:
return None
@app.route('/', methods=['GET', 'POST'])
def index():
weekday = None
if request.method == 'POST':
date_str = request.form['date']
weekday =
get_weekday_from_date(date_str)
return render_template('date_converter.html', weekday=weekday)
if __name__ == '__main__':
app.run(debug=True)
③ <!DOCTYPE html>
<html>
<head>
<title>日期转星期几</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
input[type="date"] {
padding: 8px;
width: 100%;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.result {
margin-top: 20px;
padding: 10px;
background-color: #e9f7ef;
border-radius: 4px;
display: {% if weekday %}block{% else %}none{% endif %};
}
.error {
color: red;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>日期转星期几</h1>
<form method="POST">
<label for="date">请输入日期:</label>
<input type="date" id="date" name="date" required>
<button type="submit">查询</button>
</form>
{% if weekday %}
<div class="result">
<p>您输入的日期是:{{ request.form['date'] }}</p>
<p>对应的星期是:{{ weekday }}</p>
</div>
{% elif request.method == 'POST' %}
<div class="error">请输入有效的日期格式(YYYY-MM-DD)</div>
{% endif %}
</div>
</body>
</html>
④ 划分等价类,得到等价类表。等价类表格式如下:
输入条件 |
有效等价类 |
唯一标识 |
无效等价类 |
唯一标识 |
日期格式 |
格式为 |
E1 |
格式不为 |
I1 |
年份范围 |
年份在 1900-2100 之间 |
E2 |
年份小于 1900 或大于 2100 |
I2 |
月份范围 |
月份在 1-12 之间 |
E3 |
月份小于 1 或大于 12 |
I3 |
日期范围 |
日期在 1-31 之间(根据月份) |
E4 |
日期小于 1 或大于 31 |
I4 |
闰年处理 |
闰年 2 月有 29 天 |
E5 |
非闰年 2 月有 29 天 |
I5 |
非闰年处理 |
非闰年 2 月有 28 天 |
E6 |
非闰年 2 月有 29 天 |
I6 |
30 天月份 |
4、6、9、11 月有 30 天 |
E7 |
4、6、9、11 月有 31 天 |
I7 |
31 天月份 |
1、3、5、7、8、10、12 月有 31 天 |
E8 |
1、3、5、7、8、10、12 月有 30 天 |
I8 |
⑤ 运用等价类划分法设计测试用例,得到测试用例表。测试用例表格式如下:
序号 |
输入数据 |
覆盖等价类 |
输出 |
1 |
"2023-10-05" |
E1, E2, E3, E4, E8 |
Thursday |
2 |
"2000-02-29" |
E1, E2, E3, E4, E5 |
Tuesday |
3 |
"2023-04-30" |
E1, E2, E3, E4, E7 |
Sunday |
4 |
"2023-02-28" |
E1, E2, E3, E4, E6 |
Tuesday |
5 |
"1899-12-31" |
I2 |
Invalid date format. Please use YYYY-MM-DD. |
6 |
"2101-01-01" |
I2 |
Invalid date format. Please use YYYY-MM-DD. |
7 |
"2023-13-01" |
I3 |
Invalid date format. Please use YYYY-MM-DD. |
8 |
"2023-00-01" |
I3 |
Invalid date format. Please use YYYY-MM-DD. |
9 |
"2023-01-32" |
I4 |
Invalid date format. Please use YYYY-MM-DD. |
10 |
"2023-02-29" |
I6 |
Invalid date format. Please use YYYY-MM-DD. |
11 |
"2023-04-31" |
I7 |
Invalid date format. Please use YYYY-MM-DD. |
12 |
"2023-01-00" |
I4 |
Invalid date format. Please use YYYY-MM-DD. |
④ 运用边界值法设计测试用例。
1 "1900-01-01" 最小年份 Monday
2 "2100-12-31" 最大年份 Friday
3 "2023-01-01" 最小月份 Sunday
4 "2023-12-31" 最大月份 Sunday
5 "2023-02-28" 非闰年 2 月 Tuesday
6 "2020-02-29" 闰年 2 月 Saturday
7 "2023-04-30" 30 天月份 Sunday
8 "2023-01-31" 31 天月份 Tuesday
9 "2023-01-32" 超出日期范围 Invalid date format. Please use YYYY-MM-DD.
10 "2023-02-29" 非闰年 2 月 29 天 Invalid date format. Please use YYYY-MM-DD.
2、找零钱最佳组合
假设商店货品价格(R) 都不大于100元(且为整数),若顾客付款(P)在100元内,现有一个程序能在每位顾客付款后给出找零钱的最佳组合(找给顾客货币张数最少)。假定此商店的货币面值只包括:50元(N50)、10元(N10)、 5元(N5)、1元(N1) 四种。
请结合等价类划分法和边界值分析法为上述程序设计出相应的测试用例。
实验步骤:
同上题
- 设计程序
2) from flask import Flask, render_template, request
app = Flask(__name__)
def calculate_change(price, payment):
if not (1 <= price <= 99):
return None, "商品价格必须在1-99元之间"
if not (1 <= payment <= 100):
return None, "付款金额必须在1-100元之间"
if payment < price:
return None, "付款金额不足"
change = payment - price
result = {}
result['50元'] = change // 50
change %= 50
result['10元'] = change // 10
change %= 10
result['5元'] = change // 5
change %= 5
result['1元'] = change
return result, None
@app.route('/', methods=['GET', 'POST'])
def index():
change_result = None
error = None
if request.method == 'POST':
try:
price = int(request.form['price'])
payment = int(request.form['payment'])
change_result, error = calculate_change(price, payment)
except ValueError:
error = "请输入有效的整数"
return render_template('change_calculator.html',
change_result=change_result,
error=error)
if __name__ == '__main__':
app.run(debug=True)
3) <!DOCTYPE html>
<html>
<head>
<title>找零钱最佳组合计算器</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #f5f5f5;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
input {
padding: 8px;
width: 100%;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.result {
margin-top: 20px;
padding: 15px;
background-color: #e9f7ef;
border-radius: 4px;
}
.error {
color: red;
margin-top: 10px;
padding: 10px;
background-color: #ffebee;
border-radius: 4px;
}
.denomination {
display: flex;
justify-content: space-between;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>找零钱最佳组合计算器</h1>
<form method="POST">
<label for="price">商品价格(1-99元):</label>
<input type="number" id="price" name="price" min="1" max="99" required>
<label for="payment">付款金额(1-100元):</label>
<input type="number" id="payment" name="payment" min="1" max="100" required>
<button type="submit">计算找零</button>
</form>
{% if error %}
<div class="error">{{ error }}</div>
{% endif %}
{% if change_result %}
<div class="result">
<h3>最佳找零组合:</h3>
{% for denom, count in change_result.items() %}
{% if count > 0 %}
<div class="denomination">
<span>{{ denom }}:</span>
<span>{{ count }}张</span>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
</body>
</html>
等价类,得到等价类表。等价类表格式如下:
输入条件 |
有效等价类 |
唯一标识 |
无效等价类 |
唯一标识 |
价格 RR |
0<R≤1000<R≤100 且为整数 |
E1 |
R≤0R≤0 或 R>100R>100 |
I1 |
|
|
|
RR 不是整数 |
I2 |
付款 PP |
0<P≤1000<P≤100 且为整数 |
E2 |
P≤0P≤0 或 P>100P>100 |
I3 |
|
|
|
PP 不是整数 |
I4 |
付款与价格关系 |
P≥RP≥R |
E3 |
P<RP<R |
I5 |
运用等价类划分法设计测试用例,得到测试用例表。测试用例表格式如下:
序号 |
输入数据 |
覆盖等价类 |
输出 |
1 |
R=50,P=100R=50,P=100 |
E1, E2, E3 |
找零组合:1 张 50 元,0 张 10 元,0 张 5 元,0 张 1 元 |
2 |
R=23,P=50R=23,P=50 |
E1, E2, E3 |
找零组合:0 张 50 元,2 张 10 元,1 张 5 元,2 张 1 元 |
3 |
R=99,P=100R=99,P=100 |
E1, E2, E3 |
找零组合:0 张 50 元,0 张 10 元,0 张 5 元,1 张 1 元 |
4 |
R=1,P=100R=1,P=100 |
E1, E2, E3 |
找零组合:1 张 50 元,4 张 10 元,1 张 5 元,4 张 1 元 |
5 |
R=0,P=50R=0,P=50 |
I1 |
无效输入:价格和付款必须大于 0 |
6 |
R=101,P=100R=101,P=100 |
I1 |
无效输入:价格和付款不能超过 100 元 |
7 |
R=50,P=49R=50,P=49 |
I5 |
无效输入:付款金额不能小于价格 |
8 |
R=50.5,P=100R=50.5,P=100 |
I2 |
无效输入:价格和付款必须为整数 |
9 |
R=50,P=100.5R=50,P=100.5 |
I4 |
无效输入:价格和付款必须为整数 |
4. 运用边界值法设计测试用例。
序号 |
输入数据 |
覆盖边界值 |
输出 |
1 |
R=1,P=1R=1,P=1 |
RR 和 PP 的最小值 |
无需找零 |
2 |
R=100,P=100R=100,P=100 |
RR 和 PP 的最大值 |
无需找零 |
3 |
R=1,P=100R=1,P=100 |
RR 最小值,PP 最大值 |
找零组合:1 张 50 元,4 张 10 元,1 张 5 元,4 张 1 元 |
4 |
R=99,P=100R=99,P=100 |
RR 接近最大值,PP 最大值 |
找零组合:0 张 50 元,0 张 10 元,0 张 5 元,1 张 1 元 |
5 |
R=50,P=50R=50,P=50 |
R=PR=P 的情况 |
无需找零 |
6 |
R=0,P=0R=0,P=0 |
无效边界值 |
无效输入:价格和付款必须大于 0 |
7 |
R=101,P=101R=101,P=101 |
无效边界值 |
无效输入:价格和付款不能超过 100 元 |
3、有一个饮料自动售货机(处理单价为5角钱)的控制处理软件,它的软件规格说明如下:
若投入5角钱的硬币,按下“橙汁”或“啤酒”的按钮,则相应的饮料就送出来。若投入1元钱的硬币,同样也是按“橙汁”或“啤酒”的按钮,则自动售货机在送出相应饮料的同时退回5角钱的硬币。
模拟程序如下:
用因果图法测试该程序,并撰写实验报告。
实验步骤:
①编写程序
②分析原因与结果
③画出因果图
④转化为决策表
⑥ 根据决策表设计测试用例,得到测试用例表
1.编写程序:
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
class VendingMachine:
def __init__(self):
self.money_inserted = 0.0
def insert_money(self, amount):
self.money_inserted += amount
return self.money_inserted
def select_drink(self, drink):
if self.money_inserted >= 0.5:
change = self.money_inserted -
0.5
self.money_inserted = 0
return {"success": True, "message": f"出货 {drink}{f', 找零 {change}元' if change > 0 else ''}"}
else:
return {"success": False, "message": "金额不足,请投币"}
def return_money(self):
if self.money_inserted > 0:
money = self.money_inserted
self.money_inserted = 0
return {"success": True, "message": f"退还 {money}元"}
else:
return {"success": False, "message": "没有可退还的金额"}
machine = VendingMachine()
@app.route('/')
def index():
return render_template('index.html')
@app.route('/insert', methods=['POST'])
def insert():
amount = float(request.form['amount'])
current =
machine.insert_money(amount)
return jsonify({"current": current})
@app.route('/select', methods=['POST'])
def select():
drink = request.form['drink']
result = machine.select_drink(drink)
return jsonify(result)
@app.route('/return', methods=['POST'])
def return_money():
result = machine.return_money()
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
分析原因结果:
C1:投入一元
C2:投入五角
C3:选择橙汁
C4:选择啤酒
结果:
B1:送出橙汁
B2:送出啤酒
B3:退回5角硬币(如果投入一元)
因果图:
决策表
序号 |
C1C1 |
C2C2 |
B1B1 |
B2B2 |
R1R1 |
R2R2 |
R3R3 |
1 |
是 |
否 |
是 |
否 |
是 |
否 |
否 |
2 |
是 |
否 |
否 |
是 |
否 |
是 |
否 |
3 |
否 |
是 |
是 |
否 |
是 |
否 |
是 |
4 |
否 |
是 |
否 |
是 |
否 |
是 |
是 |
5 |
否 |
否 |
是 |
否 |
无效输入 |
无效输入 |
无效输入 |
6 |
否 |
否 |
否 |
是 |
无效输入 |
无效输入 |
无效输入 |
7 |
是 |
是 |
是 |
否 |
无效输入 |
无效输入 |
无效输入 |
8 |
是 |
是 |
否 |
是 |
无效输入 |
无效输入 |
无效输入 |
⑤根据决策表设计测试用例,得到测试用例表
序号 |
输入数据 |
覆盖条件 |
预期输出 |
1 |
coin=0.5,button="橙汁"coin=0.5,button="橙汁" |
C1,B1C1,B1 |
送出 橙汁 |
2 |
coin=0.5,button="啤酒"coin=0.5,button="啤酒" |
C1,B2C1,B2 |
送出 啤酒 |
3 |
coin=1.0,button="橙汁"coin=1.0,button="橙汁" |
C2,B1C2,B1 |
送出 橙汁,退回 5 角硬币 |
4 |
coin=1.0,button="啤酒"coin=1.0,button="啤酒" |
C2,B2C2,B2 |
送出 啤酒,退回 5 角硬币 |
5 |
coin=0.0,button="橙汁"coin=0.0,button="橙汁" |
无效输入 |
无效输入:请投入 5 角或 1 元硬币 |
6 |
coin=0.0,button="啤酒"coin=0.0,button="啤酒" |
无效输入 |
无效输入:请投入 5 角或 1 元硬币 |
7 |
coin=0.5,button="可乐"coin=0.5,button="可乐" |
无效输入 |
无效输入:请选择“橙汁”或“啤酒” |
8 |
coin=1.0,button="可乐"coin=1.0,button="可乐" |
无效输入 |
无效输入:请选择“橙汁”或“啤酒” |
4、航空服务查询问题:根据航线,仓位,飞行时间查询航空服务。
假设一个中国的航空公司规定:
① 中国去欧美的航线所有座位都有食物供应,每个座位都可以播放电影。
② 中国去非欧美的国外航线都有食物供应,只有商务仓可以播放电影。
③ 中国国内的航班的商务仓有食物供应,但是不可以播放电影
④ 中国国内航班的经济仓只有当飞行时间大于2小时时才有食物供应,但是不可以播放电影。
请用程序实现上述功能,并用决策表法设计测试用例,再执行测试,撰写实验报告。
实验步骤:
① 编写程序
② 构造决策表
③ 根据决策表设计测试用例,得到测试用例表
- 编写程序:
2. from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
class AirlineService:
@staticmethod
def check_service(region, seat_class, duration):
# 规则判断
if region == "欧美":
return {"food": True, "movie": True}
elif region == "非欧美":
return {"food": True, "movie": seat_class == "商务舱"}
elif region == "国内":
if seat_class == "商务舱":
return {"food": True, "movie": False}
else: # 经济舱
return {"food": duration > 2, "movie": False}
else:
return {"error": "无效的地区输入"}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/check_service', methods=['POST'])
def check_service():
data = request.json
region = data.get('region')
seat_class = data.get('seat_class')
duration = float(data.get('duration', 0))
result = AirlineService.check_service(region, seat_class, duration)
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
3. <!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>航空公司服务查询系统</title>
<style>
:root {
--primary: #4361ee;
--secondary: #3f37c9;
--success: #4cc9f0;
--danger: #f72585;
--light: #f8f9fa;
--dark: #212529;
--display-bg: #2b2d42;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
margin: 0;
}
.service-checker {
background: white;
border-radius: 20px;
box-shadow: var(--shadow);
width: 100%;
max-width: 500px;
overflow: hidden;
transition: var(--transition);
}
.header {
background: linear-gradient(to right, var(--primary), var(--secondary));
color: white;
padding: 25px;
text-align: center;
position: relative;
}
.header h1 {
margin: 0;
font-size: 28px;
font-weight: 700;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
.form-container {
padding: 25px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--dark);
}
select, input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 16px;
transition: var(--transition);
}
select:focus, input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.2);
}
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
padding: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
width: 100%;
transition: var(--transition);
box-shadow: var(--shadow);
}
.btn:hover {
background: var(--secondary);
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.result-panel {
background: var(--light);
border-radius: 8px;
padding: 20px;
margin-top: 20px;
display: none;
}
.result-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.result-item:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.result-label {
font-weight: 600;
}
.result-value {
font-weight: 500;
}
.available {
color: var(--success);
}
.unavailable {
color: var(--danger);
}
.icon {
margin-right: 8px;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head>
<body>
<div class="service-checker">
<div class="header">
<h1><i class="fas fa-plane icon"></i>航空公司服务查询系统</h1>
</div>
<div class="form-container">
<div class="form-group">
<label for="region"><i class="fas fa-globe-americas icon"></i>航线地区</label>
<select id="region" class="form-control">
<option value="">请选择航线地区</option>
<option value="欧美">中国 - 欧美航线</option>
<option value="非欧美">中国 - 非欧美航线</option>
<option value="国内">中国国内航线</option>
</select>
</div>
<div class="form-group">
<label for="seat_class"><i class="fas fa-chair icon"></i>座位等级</label>
<select id="seat_class" class="form-control">
<option value="">请选择座位等级</option>
<option value="商务舱">商务舱</option>
<option value="经济舱">经济舱</option>
</select>
</div>
<div class="form-group">
<label for="duration"><i class="fas fa-clock icon"></i>飞行时长(小时)</label>
<input type="number" id="duration" class="form-control" min="0" step="0.1" placeholder="请输入飞行时长">
</div>
<button class="btn" onclick="checkService()"><i class="fas fa-search icon"></i>查询服务</button>
<div id="resultPanel" class="result-panel">
<h3 style="margin-top: 0; margin-bottom: 15px;">服务查询结果</h3>
<div class="result-item">
<span class="result-label">餐饮服务:</span>
<span id="foodResult" class="result-value"></span>
</div>
<div class="result-item">
<span class="result-label">电影服务:</span>
<span id="movieResult" class="result-value"></span>
</div>
</div>
</div>
</div>
<script>
function checkService() {
const region = document.getElementById('region').value;
const seatClass = document.getElementById('seat_class').value;
const duration = document.getElementById('duration').value;
if (!region || !seatClass || !duration) {
alert('请填写所有查询条件');
return;
}
fetch('/check_service', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
region: region,
seat_class: seatClass,
duration: duration
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
return;
}
const resultPanel = document.getElementById('resultPanel');
const foodResult = document.getElementById('foodResult');
const movieResult = document.getElementById('movieResult');
// 更新结果
foodResult.textContent = data.food ? "提供" : "不提供";
foodResult.className = data.food ? "result-value available" : "result-value unavailable";
movieResult.textContent = data.movie ? "提供" : "不提供";
movieResult.className = data.movie ? "result-value available" : "result-value unavailable";
// 显示结果面板
resultPanel.style.display = 'block';
})
.catch(error => {
console.error('Error:', error);
alert('查询失败,请稍后再试');
});
}
</script>
</body>
</html>
2构造决策表
序号 |
航线 |
舱位 |
飞行时间 |
食物供应 |
电影播放 |
1 |
欧美 |
商务舱 |
两小时以内 |
有 |
可以 |
2 |
欧美 |
商务舱 |
超过两小时 |
有 |
可以 |
3 |
欧美 |
经济舱 |
两小时以内 |
有 |
可以 |
4 |
欧美 |
经济舱 |
超过两小时 |
有 |
可以 |
5 |
国外非欧美 |
商务舱 |
两小时以内 |
有 |
可以 |
6 |
国外非欧美 |
商务舱 |
超过两小时 |
有 |
可以 |
7 |
国外非欧美 |
经济舱 |
两小时以内 |
有 |
无 |
8 |
国外非欧美 |
经济舱 |
超过两小时 |
有 |
无 |
9 |
国内 |
商务舱 |
两小时以内 |
有 |
无 |
10 |
国内 |
商务舱 |
超过两小时 |
有 |
无 |
11 |
国内 |
经济舱 |
两小时以内 |
无 |
无 |
12 |
国内 |
经济舱 |
超过两小时 |
有 |
无 |
3. 根据决策表设计测试用例,得到测试用例表
序号 |
输入数据 |
预期输出 |
1 |
航线:欧美,舱位:商务舱,飞行时间:两小时以内 |
食物供应:有,电影播放:可以 |
2 |
航线:欧美,舱位:商务舱,飞行时间:超过两小时 |
食物供应:有,电影播放:可以 |
3 |
航线:欧美,舱位:经济舱,飞行时间:两小时以内 |
食物供应:有,电影播放:可以 |
4 |
航线:欧美,舱位:经济舱,飞行时间:超过两小时 |
食物供应:有,电影播放:可以 |
5 |
航线:国外非欧美,舱位:商务舱,飞行时间:两小时以内 |
食物供应:有,电影播放:可以 |
6 |
航线:国外非欧美,舱位:商务舱,飞行时间:超过两小时 |
食物供应:有,电影播放:可以 |
7 |
航线:国外非欧美,舱位:经济舱,飞行时间:两小时以内 |
食物供应:有,电影播放:无 |
8 |
航线:国外非欧美,舱位:经济舱,飞行时间:超过两小时 |
食物供应:有,电影播放:无 |
9 |
航线:国内,舱位:商务舱,飞行时间:两小时以内 |
食物供应:有,电影播放:无 |
10 |
航线:国内,舱位:商务舱,飞行时间:超过两小时 |
食物供应:有,电影播放:无 |
11 |
航线:国内,舱位:经济舱,飞行时间:两小时以内 |
食物供应:无,电影播放:无 |
12 |
航线:国内,舱位:经济舱,飞行时间:超过两小时 |
食物供应:有,电影播放:无 |
5、旅馆住宿系统中,旅馆业主可进行添加房间操作。
– 旅馆业主登录旅馆住宿系统后,可以请求添加房间;
– 待进入“房间管理”对话框,单击“添加”按钮可进行添加房间操作;
– 添加房间时,可以设定房间的房间编号、房间类型、房间描述信息;
– 添加房间信息不能缺失,若某一项未填写,要给出提示信息;
– 房间编号长度不超过5个字符;
– 房间描述长度不超过1000个字符;
– 房间信息不能重复,成功填写后,可进行保存或取消操作,之后返回“房间管理”对话框,结束添加房间流程。
实验步骤:
利用黑盒测试策略编写添加房间功能的测试用例。
程序设计:
from flask import Flask, render_template, request, jsonify, redirect, url_for
app = Flask(__name__)
# 模拟数据库
rooms = []
class Room:
def __init__(self, room_id, room_type, description):
self.room_id = room_id
self.room_type = room_type
self.description = description
@app.route('/')
def index():
# 直接返回房间管理页面
return render_template('room_management.html', rooms=rooms)
@app.route('/add_room', methods=['POST'])
def add_room():
room_id = request.form.get('room_id')
room_type = request.form.get('room_type')
description = request.form.get('description')
# 验证输入
if not all([room_id, room_type, description]):
return jsonify({'success': False, 'message': '所有字段都必须填写'})
if len(room_id) > 5:
return jsonify({'success': False, 'message': '房间编号不能超过5个字符'})
if len(description) > 1000:
return jsonify({'success': False, 'message': '房间描述不能超过1000个字符'})
# 检查房间是否已存在
if any(room.room_id == room_id for room in rooms):
return jsonify({'success': False, 'message': '房间编号已存在'})
# 添加新房间
new_room = Room(room_id, room_type, description)
rooms.append(new_room)
return jsonify({'success': True, 'message': '房间添加成功'})
if __name__ == '__main__':
app.run(debug=True)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>旅馆房间管理系统</title>
<style>
:root {
--primary: #4361ee;
--secondary: #3f37c9;
--success: #4cc9f0;
--danger: #f72585;
--light: #f8f9fa;
--dark: #212529;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
margin: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
background: linear-gradient(to right, var(--primary), var(--secondary));
color: white;
padding: 20px;
border-radius: 10px 10px 0 0;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.btn {
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
padding: 10px 15px;
cursor: pointer;
transition: var(--transition);
}
.btn:hover {
background: var(--secondary);
transform: translateY(-2px);
box-shadow: var(--shadow);
}
.btn-danger {
background: var(--danger);
}
.card {
background: white;
border-radius: 10px;
box-shadow: var(--shadow);
padding: 20px;
margin-bottom: 20px;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 10px;
width: 500px;
max-width: 90%;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
input, select, textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
}
textarea {
min-height: 100px;
resize: vertical;
}
.error-message {
color: var(--danger);
margin-top: 5px;
font-size: 14px;
}
.room-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.room-card {
border: 1px solid #eee;
border-radius: 8px;
padding: 15px;
transition: var(--transition);
}
.room-card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow);
}
.room-id {
font-weight: bold;
color: var(--primary);
font-size: 18px;
}
.room-type {
color: var(--secondary);
margin: 5px 0;
}
.room-desc {
color: #666;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-hotel"></i> 旅馆房间管理系统</h1>
<button class="btn btn-danger" onclick="logout()">退出登录</button>
</div>
<div class="card">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2>房间管理</h2>
<button class="btn" onclick="openAddRoomModal()">添加房间</button>
</div>
<div class="room-list" id="roomList">
<!-- 房间列表将通过JavaScript动态生成 -->
<div class="room-card">
<div class="room-id">101</div>
<div class="room-type">标准间</div>
<div class="room-desc">舒适标准间,配备双人床和独立卫浴</div>
</div>
</div>
</div>
</div>
<!-- 添加房间模态框 -->
<div class="modal" id="addRoomModal">
<div class="modal-content">
<h2>添加新房间</h2>
<form id="addRoomForm">
<div class="form-group">
<label for="room_id">房间编号 *</label>
<input type="text" id="room_id" name="room_id" maxlength="5" required>
<div class="error-message" id="room_id_error"></div>
</div>
<div class="form-group">
<label for="room_type">房间类型 *</label>
<select id="room_type" name="room_type" required>
<option value="">请选择房间类型</option>
<option value="标准间">标准间</option>
<option value="大床房">大床房</option>
<option value="豪华套房">豪华套房</option>
<option value="家庭房">家庭房</option>
</select>
<div class="error-message" id="room_type_error"></div>
</div>
<div class="form-group">
<label for="description">房间描述 *</label>
<textarea id="description" name="description" maxlength="1000" required></textarea>
<div class="error-message" id="description_error"></div>
<div style="text-align: right; font-size: 12px; color: #666;">
<span id="charCount">0</span>/1000
</div>
</div>
<div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px;">
<button type="button" class="btn btn-danger" onclick="closeAddRoomModal()">取消</button>
<button type="submit" class="btn">保存</button>
</div>
</form>
</div>
</div>
<!-- 登录页面 -->
<div id="loginPage" style="display: none;">
<div class="container" style="max-width: 400px;">
<div class="card" style="text-align: center;">
<h2>旅馆业主登录</h2>
<form id="loginForm" style="margin-top: 20px;">
<div class="form-group">
<input type="text" placeholder="用户名" required>
</div>
<div class="form-group">
<input type="password" placeholder="密码" required>
</div>
<button type="submit" class="btn" style="width: 100%;">登录</button>
</form>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js"></script>
<script>
// 页面加载时检查登录状态
document.addEventListener('DOMContentLoaded', function() {
fetch('/check_login').then(response => response.json()).then(data => {
if (!data.logged_in) {
document.getElementById('loginPage').style.display = 'block';
document.querySelector('.container').style.display = 'none';
} else {
loadRooms();
}
});
// 监听描述文本长度
document.getElementById('description').addEventListener('input', function() {
document.getElementById('charCount').textContent = this.value.length;
});
});
// 登录表单提交
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.elements[0].value,
password: this.elements[1].value
})
}).then(response => {
if (response.ok) {
window.location.reload();
}
});
});
// 退出登录
function logout() {
fetch('/logout').then(() => {
window.location.reload();
});
}
// 打开添加房间模态框
function openAddRoomModal() {
document.getElementById('addRoomModal').style.display = 'flex';
}
// 关闭添加房间模态框
function closeAddRoomModal() {
document.getElementById('addRoomModal').style.display = 'none';
document.getElementById('addRoomForm').reset();
clearErrors();
}
// 清除错误信息
function clearErrors() {
document.querySelectorAll('.error-message').forEach(el => {
el.textContent = '';
});
}
// 加载房间列表
function loadRooms() {
fetch('/get_rooms').then(response => response.json()).then(data => {
const roomList = document.getElementById('roomList');
roomList.innerHTML = '';
data.rooms.forEach(room => {
const roomCard = document.createElement('div');
roomCard.className = 'room-card';
roomCard.innerHTML = `
<div class="room-id">${room.room_id}</div>
<div class="room-type">${room.room_type}</div>
<div class="room-desc">${room.description}</div>
`;
roomList.appendChild(roomCard);
});
});
}
// 添加房间表单提交
document.getElementById('addRoomForm').addEventListener('submit', function(e) {
e.preventDefault();
clearErrors();
const formData = new FormData(this);
fetch('/add_room', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
if (data.success) {
closeAddRoomModal();
loadRooms();
} else {
// 显示错误信息
if (data.message.includes('编号')) {
document.getElementById('room_id_error').textContent = data.message;
} else if (data.message.includes('类型')) {
document.getElementById('room_type_error').textContent = data.message;
} else if (data.message.includes('描述')) {
document.getElementById('description_error').textContent = data.message;
} else {
document.getElementById('room_id_error').textContent = data.message;
}
}
}).catch(error => {
console.error('Error:', error);
});
});
</script>
</body>
</html>
运行截图:
测试用例:
四、实验思考
① 在实际的测试中,如何设计测试用例才能达到用最少的测试用例检测出最多的缺陷;
- 基于需求和规格说明设计测试用例:
- 确保测试用例覆盖所有功能需求和业务规则。
- 从用户的角度出发,设计测试用例以验证软件是否满足用户的实际需求。
- 使用多种测试方法结合:
- 等价类划分:将输入数据划分为有效和无效等价类,减少测试用例数量,同时覆盖所有可能的输入情况。
- 边界值分析:针对输入数据的边界值进行测试,因为边界值往往是缺陷的高发区域。
- 因果图法和决策表法:用于处理复杂的逻辑关系,确保所有可能的逻辑路径都被覆盖。
- 场景法:设计测试用例以覆盖用户可能的操作场景,包括正常流程和异常流程。
- 优先级排序:
- 根据风险和重要性对测试用例进行优先级排序,优先执行高优先级的测试用例。
- 重点关注核心功能和高风险模块的测试。
- 利用自动化测试工具:
- 对于重复性高、容易出错的测试任务,使用自动化测试工具可以提高测试效率和覆盖率。
- 自动化测试可以快速执行大量测试用例,发现潜在缺陷。
- 持续集成和持续测试:
- 在软件开发过程中,尽早且频繁地进行测试,及时发现和修复缺陷。
- 利用持续集成工具(如 Jenkins、GitLab CI 等)自动运行测试用例,确保每次代码提交后都能及时发现问题。
- 基于风险的测试:
- 识别软件中可能存在的风险点,设计针对性的测试用例。
- 对于高风险的功能或模块,增加测试用例的覆盖率。
② 在进行用例设计时,如何考虑软件测试用例的充分性和减少软件测试用例的冗余性;
- 明确测试目标:
- 在设计测试用例之前,明确测试目标和范围,避免测试用例偏离目标。
- 确保每个测试用例都有明确的预期结果,避免模糊不清的测试用例。
- 避免重复覆盖:
- 在设计测试用例时,仔细检查是否已经存在类似的测试用例,避免重复覆盖相同的功能或逻辑。
- 使用测试用例管理工具(如 TestRail、Zephyr 等)来管理和跟踪测试用例,避免重复设计。
- 基于等价类划分:
- 等价类划分可以将输入数据划分为等价类,每个等价类只需要一个测试用例即可覆盖。
- 避免为每个输入值都设计一个测试用例,而是选择具有代表性的输入值。
- 优化边界值测试:
- 边界值测试应覆盖输入数据的边界值,但避免过多的边界值测试用例。
- 选择关键的边界值进行测试,而不是穷举所有可能的边界值。
- 使用正交测试方法:
- 正交测试方法可以确保测试用例的组合覆盖所有可能的输入参数组合,同时减少冗余。
- 通过正交表设计测试用例,确保测试用例的充分性和高效性。
- 定期审查和优化测试用例:
- 定期对测试用例进行审查,删除冗余或无效的测试用例。
- 根据软件的变化和测试结果,动态调整测试用例,确保测试用例始终具有针对性和有效性。
- 利用测试覆盖率工具:
- 使用测试覆盖率工具(如 JaCoCo、Istanbul 等)来评估测试用例的覆盖率,确保测试用例覆盖了代码的所有分支和路径。
- 根据覆盖率报告,补充缺失的测试用例,优化冗余的测试用例。