黑盒测试

系列 - 软件测试过程
  1. 理解等价类划分的概念;
  2. 能够根据规格说明设计出符合等价类划分的测试用例;
  3. 能够应用等价类划分测试法设计出有效的测试方案。
  1. 理解等价类划分的概念;
  2. 能够根据规格说明设计出符合等价类划分的测试用例;
  3. 能够应用等价类划分测试法设计出有效的测试方案。
  1. 理解决策表测试法的概念;
  2. 能够根据规格说明设计出符合决策表测试法的测试用例;
  3. 能够应用决策表测试法设计出有效的测试方案。
  1. 理解动态规划算法,等价类划分法和边界值分析法;
  2. 能够根据问题需求设计出动态规划的解法,利用等价类划分法和边界值分析法设计合适的测试用例;
  3. 能够应用动态规划算法求解实际问题并通过等价类划分法和边界值分析法设计出有效的测试方案。
  • 黑盒测试被称为功能测试或数据驱动测试,它将被测程序视为一个不能打开的黑盒子,在完全不考虑内部特性的情况下进行测试。黑盒测试可以分为多个不同的子类型,其中包括有效等价类测试、边界值测试和基于决策表的测试。
  • 采用黑盒测试的目的主要是在己知软件产品所应具有的功能的基础上,进行:
    1. 检查程序功能能否按需求规格说明书的规定正常使思,测试各个功能是否有道漏,检测性能等特性要求是否满足。
    2. 检测人机交互是否错识,检测数据绪构或外部数据库访问是否销误,程序是否能适当地接收输入数据而产生正确的输出结果,并保持外部信息(如数据库或文件)的完整性。
    3. 检测程序初始化和终止方面的错识

有效等价类测试是一种常见的黑盒测试方法,它将不能穷举的测试过程进行合理分类,从而保证设计出来的测试用例具有完整性和代表性。在该方法中,将输入域划分为若干部分,从每一部分中选取少数有代表性的数据作为测试用例。等价类是指某个输入域的子集合,在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,它们具有等价特性,即每一种代表性数据在测试中的作用都等效于这一类中的其它数据。因此,可以合理的假定:测试某等价类的代表值就是等效于对于这一类其它值的测试。

边界值测试是一种测试方法,它专注于测试输入数据的边界值,例如输入的最小值、最大值和临界值。在该方法中,测试人员会针对各种边界条件设计测试用例,从而揭示程序在边界条件下的错误。

基于决策表的测试是一种黑盒测试方法,它基于决策表对程序进行测试。在该方法中,将程序的输入和输出转化为决策表,然后针对决策表中的各种情况设计测试用例,从而检测程序中可能存在的错误。这种方法特别适用于测试输入和输出之间具有多个关系的程序,例如状态机或控制系统。

某城市电话号码由三部分组成。它们的名称和内容分别是: 地区码:空白或三位数字;
前 缀:非 '0'或'1'的三位数字;后 缀:4 位数字。假定被测程序能接受一切符合上述规定
的电话号码,拒绝所有不符合规定的电话号码。根据该程序的规格说明,作等价类的划分,并
设计测试方案。
  • 被测程序
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package blackbox

import (
"regexp"
)

func ValidatePhoneNumber(phoneNumber string) bool {
	re := regexp.MustCompile(`^(\d{3}[- ]?)?[2-9]{3}\d{4}$`)
	return re.MatchString(phoneNumber)
}
  • 等价类划分
输入条件 有效等价类 编号 无效等价类 编号
是否可以构成电话号码 地区码为空白或三位数的 1 地区码不为空且非三位数字 5
前缀不包含非'0’或'1' 2 前缀包含为'0’或'1' 6
前缀为三位数字 3 前缀不为三位数字 7
后缀为4位数字的电话号码 4 后缀不为4位数字的电话号码 8
  • 设计测试用例
输入 预期结果 覆盖范围
123-4567890 true 1,2,3,4
123 4567890 true 1,2,3,4
4567890 true 1,2,3,4
12-4567890 false 5
123-0127890 false 5
0127890 false 6
123-457890 false 7
457890 false 7
123-456789 false 8
456789 false 8
  • 测试程序
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package blackbox

import (
"testing"
)

func TestValidPhoneNumber(t *testing.T) {
	testCases := []struct {
		phoneNumber string
		expectedResult bool
	}{
		{"123-4567890", true},
		{"123 4567890", true},
		{"4567890",true},
		{"12-4567890", false},
		{"123-0127890", false},
		{"0127890", false},
		{"123-457890", false},
		{"457890", false},
		{"123-456789", false},
		{"456789", false},
	}
	
	for _, tc := range testCases {
		result := ValidatePhoneNumber(tc.phoneNumber)
		if result != tc.expectedResult {
			t.Errorf("ValidPhoneNumber(%s), 期望输出: %t, 实际输出:%t",
			tc.phoneNumber, tc.expectedResult, result)
		}
	}
}
根据下面给出的规格说明,利用等价类划分的方法,给出足够的测试用例。
“一个程序读入三个整数。把此三个数值看成是一个三角形的三个边。这个程序要打印出
信息,说明这个三角形是三边不等的、是等腰的、还是等边的。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package blackbox

func isOverflow(a, b float64) bool {
	c := a * b
	return c/b != a
}

func JudgeTriangle(a, b, c float64) string {
	if a <= 0 || b <= 0 || c <= 0 || isOverflow(a, a) || isOverflow(b, b) || isOverflow(c, c) {
		return "非法输入"
	}
	if a+b <= c || a+c <= b || b+c <= a {
		return "无法构成三角形"
	} else if a == b || a == c || b == c {
		if a*a+b*b == c*c || a*a+c*c == b*b || b*b+c*c == a*a {
			return "等腰直角三角形"
		} else if a == b && b == c {
			return "等边三角形"
		} else {
			return "等腰三角形"
		}
	} else if a*a+b*b == c*c || a*a+c*c == b*b || b*b+c*c == a*a {
		return "直角三角形"
	} else {
		return "普通三角形"
	}
}
输入条件 有效等价类 编号 无效等价类 编号
输入3个计算结果不溢出的正整数 3个正数 1 a <= 0 12
b <= 0 13
c <= 0 14
构成三角形 a+b>c 2 a+b<=c 15
a+c>b 3 a+c<=b 16
b+c>a 4 b+c<=a 17
构成等腰三角形 a等于b不等于c 5 三边各不相等 18
a等于c不等于b 6
b等于c不等于a 7
构成直角三角形 a为直角斜边  8 三边都不为直角边 19
b为直角斜边  9
c为直角斜边 10
构成等边三角形 a等于b&&b等于c&&c等于a 11 a不等于b 20
b不等于c 21
c不等于a 22
  • 有效等价类测试用例
输入(a,b,c) 预期结果 覆盖范围
4,5,6 普通三角形 1-4
5,5,6 等腰三角形 1-4,5
5,6,5 等腰三角形 1-4,6
6,5,5 等腰三角形 1-4,7
5,3,4 直角三角形 1-4,8
3,5,4 直角三角形 1-4,9
3,4,5 直角三角形 1-4,10
5,5,5 等边三角形 1-4,11
  • 无效等价类测试用例
输入(a,b,c) 预期结果 覆盖范围
0,1,2 非法输入 12
1,0,2 非法输入 13
1,2,0 非法输入 14
1,2,3 无法构成三角形 15
1,3,2 无法构成三角形 16
3,1,2 无法构成三角形 17
4,5,6 非“等腰三角形” 18
4,6,7 非“直角三角形” 19
4,5,6 非“等边三角形” 20
4,5,6 非“等边三角形” 21
6,5,4 非“等边三角形” 22
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package blackbox

import (
	"testing"
)

func TestJudgeTriangle(t *testing.T) {
	testCases := []struct {
		a, b, c int
		expectedResult string
	}{
		{4, 5, 6, "普通三角形"},
		{5, 5, 6, "等腰三角形"},
		{5, 6, 5, "等腰三角形"},
		{6, 5, 5, "等腰三角形"},
		{5, 3, 4, "直角三角形"},
		{3, 5, 4, "直角三角形"},
		{3, 4, 5, "直角三角形"},
		{5, 5, 5, "等边三角形"},
		{0, 1, 2, "非法输入"},
		{1, 0, 2, "非法输入"},
		{1, 2, 0, "非法输入"},
		{1, 2, 3, "无法构成三角形"},
		{1, 3, 2, "无法构成三角形"},
		{3, 1, 2, "无法构成三角形"},
	}

	testNocase := []struct {
		a, b, c int
		noExpected string
	}{
		{4, 5, 6, "等腰三角形"},
		{4, 6, 7, "直角三角形"},
		{4, 5, 6, "等边三角形"},
		{4, 5, 6, "等边三角形"},
		{6, 5, 4, "等边三角形"},
	}

	for _, tc := range testCases {
		result := JudgeTriangle(tc.a, tc.b, tc.c)
		if result != tc.expectedResult {
			t.Errorf("judgeTriangle(%d, %d, %d), 期望输出: %s, 实际输出:%s",
				tc.a, tc.b, tc.c, tc.expectedResult, result)
		}
	}
	for _, tc := range testNocase {
		result := JudgeTriangle(tc.a, tc.b, tc.c)
		if result == tc.noExpected {
			t.Errorf("judgeTriangle(%d, %d, %d), 不期望输出: %s, 实际输出:%s",
				tc.a, tc.b, tc.c, tc.noExpected, result)
		}
	}
}
用决策表测试法测试以下程序:该程序有三个输入变量 month、day、year(month 、
day 和 year 均为整数值,并且满足: 1≤month≤12 和 1≤day≤31 分), 别作为输入日期
的月份、日、年份,通过程序可以输出该输入日期在日历上隔一天的日期。例如,输入为 2004
年 11 月 29 日,则该程序的输出为 2004 年 12 月 1 日。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package blackbox

import (
	"time"
)

func NextDay(year, month, day int) string {
	if year < 1000 || year > 9999 {
		return "输入年份不合法"
	}
	if month < 1 || month > 12 {
		return "输入月份不合法"
	}
	// 将输入的年份、月份、日期转换为 time.Time 类型对象
    date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
	// 判断日期是否合法
    if date.Year() != year || date.Month() != time.Month(month) || date.Day() != day {
        return "输入日期不合法"
    }
	nextDate := date.AddDate(0, 0, 1)
    return nextDate.Format("2006年01月02日")
}
输入域 有效等价类 编号 无效等价类 编号
日期 1 <= 日期 <= 27 D1 日期 < 1 D12
日期 = 28 D2 日期 > 31 D13
日期 = 29 D3 日期 = 29 D14
日期 = 30 D4 日期 = 30 D15
日期 = 31 D5 日期 = 31 D16
月份 月份 = 4, 6, 9, 11 M6 月份<1 M17
月份 = 1, 3, 5, 7, 8, 10 M7 月份>12 M18
月份 = 2 M8
月份 = 12 M9
年份 1000 <= 年份 <= 9999 的闰年 Y10 年份 < 1000   Y19
1000 <= 年份 <= 9999 的非闰年 Y11 年份 > 9999 Y20
  • 动作列表
动作名称 动作编号 动作
这个月下一天 A1 day+1
下个月第一天 A2 month+1,day = 1
明年第一天 A3 year+1,month=1,day=1
输入错误 A4 err
  • 动作桩
规则 规则编号 年份 月份 日期 动作
非闰年2月28 R1 Y11 M8 D2 A2
闰年2月29 R2 Y10 M8 D3 A2
1-27号 R3 Y10,Y11 M6,M7,M8,M9 D1 A1
短月末尾 R4 Y10,Y11 M6 D4 A2
长月末尾 R5 Y10,Y11 M7 D5 A2
一年的末尾 R6 Y10,Y11 M9 D5 A3
长月30号 R7 Y10,Y11,Y9 M7 D4 A1
非二月的28-29号 R8 Y10,Y11 M6,M7,M9 D2,D3 A1
闰年2月28号 R9 Y10 M8 D2 A1
其余 R10 A4
规则 动作
R3或R7或R8或R9 A1
R1或R2或R4或R5 A2
R6 A3
R10 A4
  • 有效等价类测试用例
预期输出 覆盖范围
2000 4 1 2000年04月02日 A1
2000 1 30 2000年01月31号 A1
2000 3 28 2000年03月29 A1
2000 3 29 2000年03月30 A1
2000 2 28 2000年02月29日 A1
2001 2 28 2001年03月01日 A2
2000 2 29 2000年03月01日 A2
2000 4 30 2000年05月01日 A2
2000 3 31 2000年04月01日 A2
2000 12 31 2001年01月01日 A3
  • 无效等价类测试用例
期望输出 覆盖范围(R10)
2000 1 0 输入日期不合法 D12
2000 1 -1 输入日期不合法 D12
2000 1 32 输入日期不合法 D13
2001 2 29 输入日期不合法 D14
2000 2 30 输入日期不合法 D15
2000 4 31 输入日期不合法 D16
2000 0 1 输入月份不合法 M17
2000 -1 1 输入月份不合法 M17
2000 13 1 输入月份不合法 M18
100 1 1 输入年份不合法 Y19
10000 1 1 输入年份不合法 Y20
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package blackbox

import (
	"testing"
)

func TestNextDay(t *testing.T) {
	testCases := []struct {
		year, month, day int
		expectedResult string
	}{
		{2000, 4, 1, "2000年04月02日"},
		{2000, 1, 30, "2000年01月31日"},
		{2000, 3, 28, "2000年03月29日"},
		{2000, 3, 29, "2000年03月30日"},
		{2000, 2, 28, "2000年02月29日"},
		{2001, 2, 28, "2001年03月01日"},
		{2000, 2, 29, "2000年03月01日"},
		{2000, 4, 30, "2000年05月01日"},
		{2000, 3, 31, "2000年04月01日"},
		{2000, 12, 31, "2001年01月01日"},
		{2000, 1, 0, "输入日期不合法"},
		{2000, 1, -1, "输入日期不合法"},
		{2000, 1, 32, "输入日期不合法"},
		{2001, 2, 29, "输入日期不合法"},
		{2000, 2, 30, "输入日期不合法"},
		{2000, 4, 31, "输入日期不合法"},
		{2000, 0, 1, "输入月份不合法"},
		{2000, -1, 1, "输入月份不合法"},
		{2000, 13, 1, "输入月份不合法"},
		{100, 1, 1, "输入年份不合法"},
		{10000, 1, 1, "输入年份不合法"},
	}

	for _, tc := range testCases {
		result := NextDay(tc.year, tc.month, tc.day)
		if result != tc.expectedResult {
			t.Errorf("NextDay(%d, %d, %d), 期望输出: %s, 实际输出:%s",
				tc.year, tc.month, tc.day, tc.expectedResult, result)
		}
	}
}
假设商店货品价格(R) 皆不大于 100 元(且为整数) ,若顾客付款在 100 元内(P) , 求
找给顾客最少货币个(张)数?(货币面值 50 元 10 元, 5 元, 1 元四种 )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

var coins = [4]int{1, 5, 10, 50}

func CoinChange(price, amount int) int {
	if amount - price < 0 || price > 100 || price <0 {
		return -1
	}
	amount = amount - price
	dp := make([]int, amount+1)
	for i := 1; i < len(dp); i++{
		dp[i] = amount+1
		for j := 0; j < len(coins); j++ {
			if i - coins[j] < 0 {
				break
			}
			dp[i] = min(dp[i-coins[j]]+1, dp[i])
		}
	}
	if dp[amount] == amount+1 {
		return -1
	}
	return dp[amount]
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}
输入条件 有效等价类 编号 无效等价类 编号
商品价格 R>0 1 R<0 7
R=0 2 R>100 8
R<100 3
R=100 4
付款 P>R 5 P<R 9
P=R 6
  • 有效等价类测试用例
商品价格 顾客付款 预期输出 覆盖范围
50 66 3 1,3,5
50 50 0 1,3,6
0 50 1 2,3,5
0 0 0 2,3,6
100 101 1 1,4,5
100 100 0 1,4,6
  • 无效等价类测试用例
商品价格 顾客付款 预期输出 覆盖范围
-1 0 -1 7
101 110 -1 8
50 30 -1 9
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package blackbox

import (
	"testing"
)

func TestCoinChange(t *testing.T) {
	testCases := []struct {
		price          int
		amount         int
		expectedResult int
	}{
		// 有效等价类测试用例
		{50, 66, 3},   // 1.商品价格小于顾客付款,预期输出为 3
		{50, 50, 0},   // 2.商品价格等于顾客付款,预期输出为 0
		{0, 50, 1},    // 3.商品价格为 0 元,预期输出为 1
		{0, 0, 0},     // 4.商品价格为 0 元,顾客付款为 0 元,预期输出为 0
		{100, 101, 1}, // 5.商品价格为 100 元,顾客付款比商品价格多 1 元,预期输出为 1
		{100, 100, 0}, // 6.商品价格为 100 元,顾客付款等于商品价格,预期输出为 0

		// 无效等价类测试用例
		{-1, 0, -1},  // 7.商品价格为负数,预期输出为 -1
		{101, 110, -1}, // 8.顾客付款大于 100 元,预期输出为 -1
		{50, 30, -1},  // 9.商品价格大于顾客付款,预期输出为 -1
	}

	for _, tc := range testCases {
		if res := CoinChange(tc.price , tc.amount); res != tc.expectedResult {
			t.Errorf("CoinChange(%d, %d); 期望输出 %d; 实际输出 %d", tc.price, tc.amount, tc.expectedResult, res)
		}
	}
}

image.png

image.png

image.png

image.png

在等价类划分法的基础上,可以通过设计有效的测试用例对电话号码问题进行测试。测试用例的设计需要考虑到有效等价类、无效等价类、边界值等因素。在实验过程中,如果测试结果正确,则说明该等价类划分法是有效的。如果测试结果不正确,则需要检查测试用例的设计是否合理,是否考虑到了所有的等价类和边界值,是否遗漏了一些情况。此外,需要检查代码实现是否符合规格说明,是否存在漏洞。

在等价类划分法的基础上,可以通过设计有效的测试用例对三角形问题进行测试。测试用例的设计需要考虑到有效等价类、无效等价类、边界值等因素。在实验过程中,如果测试结果正确,则说明该等价类划分法是有效的。如果测试结果不正确,则需要检查测试用例的设计是否合理,是否考虑到了所有的等价类和边界值,是否遗漏了一些情况。此外,需要检查代码实现是否符合规格说明,是否存在漏洞。

在决策表测试法的基础上,可以通过设计有效的测试用例对日期问题进行测试。测试用例的设计需要考虑到所有可能的决策路径和各个条件的取值范围。在实验过程中,如果测试结果正确,则说明该决策表测试法是有效的。如果测试结果不正确,则需要检查测试用例的设计是否合理,是否考虑到了所有的决策路径和条件的取值范围,是否遗漏了一些情况。此外,需要检查代码实现是否符合规格说明,是否存在漏洞。

在动态规划算法、等价类划分法和边界值分析法的基础上,可以通过设计有效的测试用例对找零钱最佳组合问题进行测试。测试用例的设计需要考虑到有效等价类、无效等价类、边界值等因素。在实验过程中,如果测试结果正确,则说明该动态规划算法和测试方法是有效的。如果测试结果不正确,则需要检查测试用例的设计是否合理,是否考虑到了所有的等价类和边界值,是否遗漏了一些情况。此外,需要检查代码实现是否符合规格说明,是否存在漏洞。同时,需要注意动态规划算法的时间和空间复杂度,以及在实际问题中的可行性和适用性。