集成测试(Integration Testing):
系统测试(System Testing):
接受测试/验收测试(Acceptance Testing):
性能测试(Performance Testing):
回归测试(Regression Testing):
关键特点:
独立性:
JUnit (Java)
TestNG
pytest (Python)
NUnit (.NET)
Mocha (JavaScript/Node.js)
RSpec (Ruby)
Google Test (C++)
Karma (JavaScript)
Selenium
选择测试框架时的考虑因素:
正向测试(Positive Testing)
边界条件测试(Boundary Value Analysis)
等价类划分(Equivalence Partitioning)
错误猜测(Error Guessing)
测试用例的结构
断言的作用:
验证预期结果: 断言允许测试者明确地指出代码执行后应该产生的结果。
提供明确的错误信息: 当测试失败时,断言可以提供关于期望值和实际值差异的具体信息,这有助于快速定位问题。
简化测试逻辑: 通过使用断言,测试代码可以更加简洁和直观,因为断言通常集成在测试框架中。
自动化测试验证: 断言使得测试结果的验证自动化,无需手动检查输出。
提高测试覆盖率: 断言有助于确保测试覆盖了所有重要的执行路径和边界条件。
相等性断言:
assertEquals(expectedValue, actualValue);
不等性断言:
assertNotEquals(notExpectedValue, actualValue);
真值断言:
assertTrue(condition);
假值断言:
assertFalse(condition);
异常断言:
assertThrows(ExpectedException.class, () -> {
// 调用可能抛出异常的方法
});
范围断言:
assertGreater(expectedMin, actualValue);
assertLess(expectedMax, actualValue);
正则表达式断言:
assertMatchesRegex("expectedPattern", actualString);
同一度断言:
assertSame(expectedObject, actualObject);
测试代码的组织结构
按功能组织:
目录结构:
test
或tests
,在这个目录下进一步按模块划分子目录。测试类和方法:
Test
作为后缀。testAdditionPositiveNumbers
。使用命名空间(针对某些语言):
模块化测试代码:
命名约定:
测试类命名:
ClassNameTest
或TestClassName
的命名模式。测试方法命名:
test_
前缀,后跟测试的场景或行为描述,如test_addition_with_negative_numbers
。常量和变量:
避免使用缩写:
测试的管理和执行策略:
自动化测试执行:
测试依赖管理:
测试数据管理:
测试环境隔离:
测试覆盖率目标:
测试分层:
测试报告:
测试维护:
测试代码审查:
测试优先级:
测试版本控制:
Mock对象:
Stubs:
应用场景:
使用Mocking工具隔离外部依赖:
假设有一个UserService
类,它依赖于一个UserRepository
来获取用户数据。在单元测试中,我们不想与数据库交互,而是想模拟UserRepository
的行为:
@Test
public void testGetUser() {
// 创建Mock对象
UserRepository mockRepository = Mockito.mock(UserRepository.class);
// 设置Mock行为
Mockito.when(mockRepository.findById(1)).thenReturn(new User(1, "TestUser"));
// 创建被测试对象,并注入Mock的依赖
UserService userService = new UserService(mockRepository);
// 调用方法并验证结果
User user = userService.getUser(1);
assertEquals("TestUser", user.getName());
// 验证交互
Mockito.verify(mockRepository).findById(1);
}
重要性:
衡量测试的完整性:
发现未测试的代码:
提高代码质量:
使用工具衡量和提高测试覆盖率:
集成覆盖率工具:
分析覆盖率报告:
改进测试用例:
设置覆盖率目标:
持续改进:
一 实验目的:
1、了解什么是单元测试,单元测试的级别、单元测试的内容。
2、掌握单元测试框架JUnit的使用。
3、掌握参数化测试方法的运用及测试脚本的编写。
二 实验环境
1、JDK8.0或以上;
2、Intellij IDEA集成开发环境;
3、Maven构建工具。
三 实验准备
1、掌握JUnit测试框架的基本使用;
2、具备Java编程基础;
3、安装及配置好测试环境。
四 实验内容
(一)网上蛋糕购物系统中,针对蛋糕商品查询业务的持久类GoodsDao中的getGoodsById、getCountOfGoodsByTypeID方法编写单元测试类(一般情况下,单元测试要对每个方法进行测试)。
(1)创建GoodsDaoTest测试类,编写对应的测试方法。请提供GoodsDaoTest测试类代码,要求代码中要对每个方法进行注释说明。
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class GoodsDaoTest {
@Mock
private GoodsDataAccess goodsDataAccess; // 假设这是访问数据库的接口
@InjectMocks
private GoodsDao goodsDao; // 被测试的持久类
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this); // 初始化Mock对象
}
// 测试getGoodsById方法
@Test
public void testGetGoodsById_WithValidId_ShouldReturnCorrectGoods() {
// 准备
int validId = 1;
Goods expectedGoods = new Goods(validId, "Chocolate Cake", 20.0);
Mockito.when(goodsDataAccess.getGoodsById(validId)).thenReturn(expectedGoods);
// 执行
Goods result = goodsDao.getGoodsById(validId);
// 验证
assertNotNull(result, "返回的对象不应为空");
assertEquals(expectedGoods, result, "返回的蛋糕应与预期相符");
}
@Test
public void testGetGoodsById_WithInvalidId_ShouldReturnNull() {
// 准备
int invalidId = -1;
Mockito.when(goodsDataAccess.getGoodsById(invalidId)).thenReturn(null);
// 执行
Goods result = goodsDao.getGoodsById(invalidId);
// 验证
assertNull(result, "使用无效ID查询时,应返回null");
}
// 测试getCountOfGoodsByTypeID方法
@Test
public void testGetCountOfGoodsByTypeID_WithValidTypeId_ShouldReturnCorrectCount() {
// 准备
int validTypeId = 1;
int expectedCount = 10;
Mockito.when(goodsDataAccess.getCountOfGoodsByTypeID(validTypeId)).thenReturn(expectedCount);
// 执行
int result = goodsDao.getCountOfGoodsByTypeID(validTypeId);
// 验证
assertEquals(expectedCount, result, "返回的数量应与预期相符");
}
@Test
public void testGetCountOfGoodsByTypeID_WithInvalidTypeId_ShouldReturnZero() {
// 准备
int invalidTypeId = -1;
Mockito.when(goodsDataAccess.getCountOfGoodsByTypeID(invalidTypeId)).thenReturn(0);
// 执行
int result = goodsDao.getCountOfGoodsByTypeID(invalidTypeId);
// 验证
assertEquals(0, result, "使用无效类型ID查询时,应返回0");
}
}
代码解释:
@Mock
注解用于创建模拟对象。@InjectMocks
注解用于创建被测试类的实例,并将模拟对象注入到它的依赖中。@BeforeEach
注解的方法在每个测试方法执行之前都会运行,用于设置测试环境。@Test
注解的方法是实际的测试用例。Mockito.when(...).thenReturn(...)
用于定义模拟对象的行为。assertNotNull
、assertEquals
和assertNull
是JUnit提供的断言方法,用于验证测试结果是否符合预期。
(2)请设计3条测试用例测试getCountOfGoodsByTypeID方法,执行测试。
测试用例:
序号 |
测试用例编号 |
测试用例名称 |
输入数据 |
预期结果 |
测试结果 |
序号 | 测试用例编号 | 测试用例名称 | 输入数据 | 预期结果 | 测试结果 |
---|---|---|---|---|---|
1 | TC001 | 正常商品类型查询 | 1 | >0 | [待测试] |
说明:此测试用例验证当提供有效的商品类型ID时,方法应返回该类型下商品的正数数量。
序号 | 测试用例编号 | 测试用例名称 | 输入数据 | 预期结果 | 测试结果 |
---|---|---|---|---|---|
2 | TC002 | 负数商品类型查询 | -1 | 0 | [待测试] |
说明:此测试用例验证当商品类型ID为负数时,方法应返回0,表示没有商品匹配。
序号 | 测试用例编号 | 测试用例名称 | 输入数据 | 预期结果 | 测试结果 |
---|---|---|---|---|---|
3 | TC003 | 最大正整数商品类型查询 | Integer.MAX_VALUE | 0或实际数量 | [待测试] |
说明:此测试用例验证当商品类型ID为整数最大值时,方法的表现,可能是返回0或者实际的商品数量,取决于数据库中的数据。
(3)针对以上测试方法的不足(多条测试用例需要多次执行),根据参数化测试的规则,使用以上测试用例数据,修改测试方法并执行测试。请提供修改后的测试方法代码
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
public class GoodsDaoTest {
@Mock
private GoodsDataAccess goodsDataAccess;
@InjectMocks
private GoodsDao goodsDao;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);
}
// 参数化测试用例
@ParameterizedTest
@CsvSource({
"1, 10, 应返回正数的商品数量",
"-1, 0, 应返回0表示没有商品",
"2147483647, 5, 应返回实际的商品数量或0"
})
public void testGetCountOfGoodsByTypeID(int typeId, int expectedResult, String caseDescription) {
// 准备
Mockito.when(goodsDataAccess.getCountOfGoodsByTypeID(typeId)).thenReturn(expectedResult);
// 执行
int result = goodsDao.getCountOfGoodsByTypeID(typeId);
// 验证
assertEquals(expectedResult, result, caseDescription);
}
}
public class Foo {
private Bar bar;
public void setBar(Bar bar) {
this.bar = bar;
}
public String doSomething() {
return "Foo::doSomething " + bar.doSomethingElse();
}
}
public class Bar {
public String doSomethingElse() {
return "Bar::doSomethingElse";
}
}
测试脚本截图为:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
public class FooTest {
@Mock
private Bar mockBar; // 创建Bar的Mock对象
@InjectMocks
private Foo foo; // 被测试的Foo对象
@BeforeEach
public void setUp() {
// 初始化Mock对象
MockitoAnnotations.openMocks(this);
}
@Test
public void testDoSomething() {
// 准备
when(mockBar.doSomethingElse()).thenReturn("Bar::doSomethingElse");
// 执行
String result = foo.doSomething();
// 验证
verify(mockBar).doSomethingElse(); // 验证Bar的doSomethingElse方法被调用
assertEquals("Foo::doSomething Bar::doSomethingElse", result);
}
}
五 实验总结
(1)什么叫桩程序?桩程序有什么作用?
桩程序(Stub): 桩程序是一种模拟对象,用于在单元测试中代替实际的依赖项或外部系统。它通常用于模拟那些在测试环境中不可用或不适合使用的组件,如数据库、网络服务或复杂计算。
作用:
(2)使用参数化测试有什么好处?
参数化测试是一种测试方法,它允许使用不同的输入参数多次执行同一个测试方法。以下是使用参数化测试的一些好处: