一、实验原理
1、JUnit
JUnit是一个Java语言的单元测试框架,用于编写和运行测试。它提供了一些注解和断言方法,可以使测试代码更加简洁和易于阅读。使用JUnit进行单元测试,可以提高代码的质量和可维护性,减少代码的错误和缺陷,从而提高整个系统的稳定性和可靠性。
JUnit框架的核心是@Test注解和断言方法,同时提供了一些其他的注解和方法:
- @Test注解:用于标记测试方法;
- @Before注解:用于在测试方法执行前执行一些操作,例如初始化测试数据;
- @After注解:用于在测试方法执行后执行一些操作,例如清理测试数据;
- assertEquals(expected, actual):断言方法,验证两个值是否相等;
- assertTrue(condition):断言方法,验证一个条件是否为true;
- assertFalse(condition):断言方法,验证一个条件是否为false;
- assertNotNull(object):断言方法,验证一个对象是否不为null;
- assertSame(expected, actual):断言方法,验证两个对象是否是同一个对象;
2、Fixture
Fixture指的是测试方法执行前后需要进行的操作,通常包括setUp()和tearDown()方法,前者用于创建和准备测试环境,后者用于清理和销毁测试环境。Fixture的目的是确保测试方法的可重复性和独立性,减少测试代码的重复性,从而提高测试的准确性、可靠性和可维护性。
标记为@BeforeEach和@AfterEach的方法会在运行每个@Test方法前后自动运行,而标记为@BeforeAll和@AfterAll的方法在运行所有@Test前后运行:
invokeBeforeAll(Test.class);
for (Method testMethod : findTestMethods(Test.class)) {
var test = new Test();
invokeBeforeEach(test);
invokeTestMethod(test, testMethod);
invokeAfterEach(test);
}
invokeAfterAll(Test.class);
3、条件测试
条件测试是软件测试中的一种测试方法,用于验证程序在不同条件下的行为是否符合预期。条件测试通常涉及到程序中的分支结构,例如if语句、switch语句等。在条件测试中,我们通常需要考虑以下几个方面:
- 边界条件:在测试条件分支时,需要测试各种边界情况,例如测试if语句中的等于、大于、小于等不同情况。
- 正常条件:需测试条件分支的正常情况,如测试if语句中的true分支和false分支。
- 异常条件:需测试条件分支的异常情况,如测试if语句中的null值、空字符串等情况。
- 多重嵌套条件:需测试多个条件分支嵌套时的情况,例如测试if - else if - else结构中各种不同情况。
条件测试的目的是发现和修复程序中可能存在的逻辑错误和漏洞,从而提高程序的质量和可靠性。
4、异常处理
在编写程序时,我们无法预知所有可能出现的异常情况,因此需要使用异常处理方法来处理程序中的异常情况,以避免程序崩溃或产生错误结果。常见的异常处理方法包括try-catch语句、finally语句、throws关键字和自定义异常类等。在处理异常时,需要根据具体情况选择合适的异常处理方法,以确保程序的可靠性和稳定性。
5、参数注入
参数注入是指将对象所需的参数通过构造函数或属性注入到对象中的过程。在程序开发中,对象常常需要依赖其他对象或数据,而这些依赖关系通常通过构造函数或属性来传递。参数注入的目的是将对象所依赖的参数从对象的实现中解耦出来,使得对象具有更好的可测试性和可维护性,测试人员和开发人员可以更容易地控制和修改对象所依赖的参数。参数注入还可以提高代码的可重用性和可扩展性。
6、直觉经验法和错误推测方法
直觉经验法和错误推测方法是在软件测试和开发中常用的一些技术和方法:
- 直觉经验法是一种基于测试人员的经验和直觉进行测试的方法。测试人员通过自己的经验和知识对测试对象进行分析和测试,从而发现和修复程序中的错误和漏洞。通常适用于小规模或简单的测试任务,可以帮助测试人员快速地发现问题和解决问题。
- 错误推测方法是一种基于推测和猜测进行测试的方法。测试人员通过推测程序中可能出现的错误和漏洞,来设计和执行测试用例,并验证程序的行为是否符合预期。通常适用于复杂或高风险的测试任务,可以帮助测试人员发现一些难以通过传统测试方法发现的问题。
直觉经验法和错误推测方法在软件测试和开发中有一定的应用价值,但它们也存在缺点和风险,因此在实际测试和开发中,我们应该综合使用多种测试方法和技术,例如自动化测试、白盒测试、黑盒测试等,以提高测试的准确性和可靠性。
二、实验步骤
1、搭建JUnit单元测试环境,完成JDK、Java开发环境的搭建(推荐使用Eclipse或IDEA集成开发平台),完成JUnit的安装与集成。
2、学习JUnit常见用法,详见《JUnit教程》,完成以下内容的练习:
- 固定片断方法(Fixture)
- 异常处理
- 条件测试
- 参数注入方法
3、完成BMI程序的测试工作,体现对上述方法的应用。
三、详细设计
1、BMI测试程序
public String getBMIType() {
double BMI;
if (weight > 0 && height > 0) {
BMI = weight / (height * height);
if (BMI < 18.5) {
return "偏瘦";
} else if (BMI < 24) {
return "正常";
} else if (BMI < 28) {
return "偏胖";
} else {
return "肥胖";
}
} else {
return "错误";
}
}
2、测试数据集设计
为了便于按照不同方式测试各种情况的数据,根据程序接收的参数设计数据集格式如下:
数据集的数据结构是一个二维数组,每一行表示一个测试用例,每个用例包括三个特征:体重、身高和类型。其中,体重和身高是数值型特征,类型是分类特征。具体来说,这个数据集中每个测试用例包含三个元素,可以表示为:
{weight, height, type}
其中体重和身高是double类型,类型是String类型,表示人的体型类型,可能为以下四种之一:"偏瘦"、"正常"、"偏胖"和"肥胖"。
3、测试用例设计
(1)等价类划分法
等价类划分法的基本思想是将输入和输出的值划分为几个等价类,每个等价类包含一组具有相同功能和行为的值,然后对于每个等价类,至少要设计一个测试用例进行测试。
等价类划分法的优点是能够有效地减少测试用例的数量,并且可以检测到各种可能的错误情况。但是,需要注意的是,等价类划分法也有一些局限性,例如可能存在一些特殊情况无法被等价类划分法所覆盖。因此,等价类划分法应该与其他测试设计技术相结合使用,以提高测试用例的覆盖率和有效性。
BMI(Body Mass Index,身体质量指数)是一种常用的身体健康指标,用于评估一个人的体型是否偏瘦、正常、偏胖或肥胖。有效等价类测试数据是指在测试中选择具有相同特征和相同测试结果的测试用例组成的一组等价类;而无效等价类指的是测试中的一组输入数据,它们被认为是等价的,但是不会导致程序的任何不良行为或错误行为。
以下为根据实验要求设计的有关BMI测试程序的等价类划分测试用例:
1、有效等价类
// 偏瘦
{45.0, 1.70, "偏瘦"}
{50.0, 1.80, "偏瘦"}
{55.0, 1.75, "偏瘦"}
{60.0, 1.85, "偏瘦"}
{65.0, 1.90, "偏瘦"}
///正常
{60.0, 1.70, "正常"}
{65.0, 1.80, "正常"}
{70.0, 1.75, "正常"}
{75.0, 1.85, "正常"}
{80.0, 1.90, "正常"}
// 偏胖
{70.0, 1.60, "偏胖"}
{75.0, 1.70, "偏胖"}
{80.0, 1.75, "偏胖"}
{85.0, 1.85, "偏胖"}
{90.0, 1.90, "偏胖"}
// 肥胖
{90.0, 1.60, "肥胖"}
{95.0, 1.70, "肥胖"}
{100.0, 1.75, "肥胖"}
{105.0, 1.85, "肥胖"}
{110.0, 1.90, "肥胖"}
2、无效等价类
{45.0, 0.0, "错误"}
{0.0, 1.63, "错误"}
{0.0, 0.0, "错误"}
{-50.0, 1.65, "错误"}
{52.0, -1.68, "错误"}
{-55.0, -1.70, "错误"}
(2)边界值法
边界值法是一种测试用例设计方法,可以在最少的测试用例数量下覆盖系统的边界情况和特殊情况。它的基本思想是,选择输入值的最小值、最大值和临界值作为测试用例,以验证系统在边界情况下的行为。边界值法可以应用于各种类型的系统,包括软件系统、硬件系统和物理系统等。
边界值法可以帮助测试人员在最短的时间内发现系统的边界情况和特殊情况,避免遗漏关键测试点,提高测试效率和质量。同时,边界值法也可以帮助系统开发人员识别和修复潜在的问题,提高系统的可靠性和健壮性。
在计算BMI之前,先对输入的身高和体重进行了合法性检查。如果输入的身高或体重不合法,则直接返回相应的提示信息;否则,继续计算BMI,并根据不同的BMI分类规则,返回相应的分类结果。按照边界值法可以设计以下测试样例:
① 最小值测试样例:将体重和身高都设置为最小值,例如0和0。这个测试样例可以验证系统在最小边界情况下的行为。
② 最大值测试样例:将体重和身高都设置为最大值,例如200和2.5。这个测试样例可以验证系统在最大边界情况下的行为。
③ 临界值测试样例:将体重和身高设置为临界值,例如18.5和25。这个测试样例可以验证系统在临界情况下的行为。
④ 异常值测试样例:将体重和身高设置为非法值,例如-1和-1.5。这个测试样例可以验证系统对于非法输入的处理情况。
⑤ 正常值测试样例:将体重和身高设置为正常值,例如65和1.75。这个测试样例可以验证系统在正常情况下的行为。
以下为根据实验要求设计的有关BMI测试程序的边界值测试用例:
4、样例测试
首先按照Fixture方法编写测试方法执行前后需要进行的操作,代码如下:
// 创建被测对象
@BeforeEach
public void setUp(double w, double h) {
bmi = new BMI(w, h);
}
// 释放被测对象
@AfterEach
public void tearDown() {
bmi = null;
}
public boolean verify(String expected, String actual) {
return Objects.equals(expected, actual);
}
public String record(double w, double h, String expected, String actual, boolean testReslt) {
if (testResult) {
return String.format("Pass. 体重:%.1fkg, 身高:%.2fm, result:%s", w, h, actual);
} else {
return String.format("Fail. 体重:%.1fkg, 身高:%.2fm, Expected:%s, Actual:%s", w, h, expected, actual);
}
}
public void testGetBMIType(double weight, double height, String res) {
setUp(weight, height);
String actual = bmi.getBMIType();
boolean testResult = verify(res, actual);
System.out.println(record(weight, height, res, actual, testResult));
tearDown();
}
(1)等价类测试
// 有效等价类测试
@Test
public void test_valid() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_valid) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
// 无效等价类测试
@Test
public void test_invalid() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_invalid) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
(2)边界值测试
// 最小值测试
@Test
public void test_min() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_min) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
// 最大值测试
@Test
public void test_max() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_max) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
// 临界值测试
@Test
public void test_critical() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_critical) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
// 异常值测试
@Test
public void test_abnormal() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_abnormal) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
// 正常值测试
@Test
public void test_normal() {
BMITest test = new BMITest();
for (Object[] data : test.dataset_normal) {
double weight = (double) data[0];
double height = (double) data[1];
String expected = (String) data[2];
test.testGetBMIType(weight, height, expected);
}
}
四、实验结果与分析
1、实验结果
(1)等价类测试
[1] 有效等价类
[2] 无效等价类
(2)边界值测试
[1] 最小值
[2] 最大值
[3] 临界值
[4] 异常值
[5] 正常值
2、实验分析
项目 | 统计数据 |
测试用例总数 | 51 |
测试用例执行总数 | 51 |
测试用例执行率 | 100% |
测试用例通过总数 | 51 |
测试用例通过率 | 100% |
测试用例未通过总数 | 0 |
测试用例未通过率 | 0% |
从表中得出所有测试样例全部通过,说明BMI测试程序设计完善,通过测试。