软件测试是确保软件质量的关键环节,它通过执行程序来发现错误,验证软件是否满足需求。本章将依据目录,结合 Java 代码示例、可视化图表,深入讲解软件测试的概念、过程、方法及实践。

12.1 软件测试的概念
12.1.1 软件测试的任务
软件测试的主要任务是:
- 发现错误 :通过执行程序,找出代码中的缺陷和逻辑错误。
- 验证功能:确保软件满足用户需求和规格说明。
- 评估质量:对软件的可靠性、性能等质量属性进行评估。
12.1.2 测试阶段的信息流程
测试阶段的信息流程:

展示测试过程中的信息流转。
12.1.3 测试用例及其设计
测试用例是为测试而设计的一组输入和预期输出,用于验证软件的特定功能。例如,测试一个简单的加法函数的测试用例:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
// 对应的测试用例(使用JUnit框架)
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class CalculatorTest {
@Test
public void testAddPositiveNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(3, 5);
assertEquals(8, result); // 预期结果为8
}
@Test
public void testAddNegativeNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(-3, -5);
assertEquals(-8, result); // 预期结果为-8
}
}
12.1.4 软件测试的原则
软件测试应遵循以下原则:
- 尽早测试:测试应从软件开发的早期阶段开始,如需求分析和设计阶段。
- 全面测试 :覆盖所有可能的输入和场景,包括边界条件和异常情况。
- 避免自测:开发人员应避免测试自己编写的代码,减少主观因素影响。
- 记录测试结果:详细记录测试过程和结果,便于追踪和分析。
12.2 软件测试的过程模型
软件测试的过程模型通常与软件开发过程模型对应,常见的有 V 模型、W 模型等。以 V 模型为例:

展示 V 模型中测试阶段与开发阶段的对应关系。
12.3 软件测试方法
12.3.1 白盒测试
白盒测试基于代码的内部结构和逻辑,主要技术包括:
- 语句覆盖:确保每个语句至少执行一次。
- 判定覆盖:确保每个判定的真假分支至少执行一次。
- 条件覆盖:确保每个判定中的每个条件的可能取值至少执行一次。
示例代码及测试用例:
public class ControlFlow {
public boolean checkNumber(int num) {
if (num > 10 && num % 2 == 0) {
return true;
} else {
return false;
}
}
}
// 白盒测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ControlFlowTest {
@Test
public void testCheckNumberTrue() {
ControlFlow cf = new ControlFlow();
assertTrue(cf.checkNumber(12)); // 覆盖true分支
}
@Test
public void testCheckNumberFalse1() {
ControlFlow cf = new ControlFlow();
assertFalse(cf.checkNumber(5)); // 覆盖false分支(num <= 10)
}
@Test
public void testCheckNumberFalse2() {
ControlFlow cf = new ControlFlow();
assertFalse(cf.checkNumber(11)); // 覆盖false分支(num为奇数)
}
}
12.3.2 黑盒测试
黑盒测试基于软件的外部功能和需求,不考虑内部实现。主要技术包括:
- 等价类划分:将输入域划分为若干等价类,从每个等价类中选取代表性值作为测试用例。
- 边界值分析:选择输入域的边界值作为测试用例,如最小值、最大值、刚好超过边界的值。
- 错误推测法:基于经验和直觉推测可能的错误,设计针对性的测试用例。
以三角形分类函数为例:
public class TriangleClassifier {
public String classify(int a, int b, int c) {
// 检查是否构成三角形
if (a <= 0 || b <= 0 || c <= 0 || a + b <= c || a + c <= b || b + c <= a) {
return "非三角形";
}
// 检查等边三角形
if (a == b && b == c) {
return "等边三角形";
}
// 检查等腰三角形
if (a == b || a == c || b == c) {
return "等腰三角形";
}
// 普通三角形
return "普通三角形";
}
}
// 黑盒测试用例(等价类划分和边界值分析)
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TriangleClassifierTest {
@Test
public void testEquilateralTriangle() {
TriangleClassifier tc = new TriangleClassifier();
assertEquals("等边三角形", tc.classify(3, 3, 3));
}
@Test
public void testIsoscelesTriangle() {
TriangleClassifier tc = new TriangleClassifier();
assertEquals("等腰三角形", tc.classify(3, 3, 4));
}
@Test
public void testScaleneTriangle() {
TriangleClassifier tc = new TriangleClassifier();
assertEquals("普通三角形", tc.classify(3, 4, 5));
}
@Test
public void testNotTriangle() {
TriangleClassifier tc = new TriangleClassifier();
assertEquals("非三角形", tc.classify(1, 2, 3));
}
@Test
public void testBoundaryValues() {
TriangleClassifier tc = new TriangleClassifier();
assertEquals("非三角形", tc.classify(0, 3, 4)); // 边界值0
assertEquals("等腰三角形", tc.classify(2, 2, 4)); // 边界值a+b=c
}
}
12.4 软件测试活动及实施策略
12.4.1 单元测试
单元测试是对软件中的最小可测试单元(如方法、类)进行测试。例如,测试一个栈类的基本操作:
import java.util.EmptyStackException;
public class Stack {
private int[] array;
private int top;
private int capacity;
public Stack(int capacity) {
this.capacity = capacity;
this.array = new int[capacity];
this.top = -1;
}
public void push(int item) {
if (top == capacity - 1) {
throw new StackOverflowError("栈已满");
}
array[++top] = item;
}
public int pop() {
if (isEmpty()) {
throw new EmptyStackException();
}
return array[top--];
}
public int peek() {
if (isEmpty()) {
throw new EmptyStackException();
}
return array[top];
}
public boolean isEmpty() {
return top == -1;
}
}
// 单元测试用例
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StackTest {
private Stack stack;
@BeforeEach
public void setUp() {
stack = new Stack(5);
}
@Test
public void testPushAndPop() {
stack.push(10);
stack.push(20);
assertEquals(20, stack.pop());
assertEquals(10, stack.pop());
}
@Test
public void testPeek() {
stack.push(10);
assertEquals(10, stack.peek());
assertEquals(10, stack.peek()); // 多次peek不应改变栈
}
@Test
public void testEmptyStack() {
assertTrue(stack.isEmpty());
assertThrows(EmptyStackException.class, () -> stack.pop());
assertThrows(EmptyStackException.class, () -> stack.peek());
}
@Test
public void testStackOverflow() {
for (int i = 0; i < 5; i++) {
stack.push(i);
}
assertThrows(StackOverflowError.class, () -> stack.push(5));
}
}
12.4.2 集成测试
集成测试是将多个单元组合成更大的模块进行测试,验证模块间的交互。例如,测试一个简单的订单处理系统:
// 订单类
public class Order {
private String orderId;
private double totalAmount;
private boolean paid;
public Order(String orderId, double totalAmount) {
this.orderId = orderId;
this.totalAmount = totalAmount;
this.paid = false;
}
public String getOrderId() {
return orderId;
}
public double getTotalAmount() {
return totalAmount;
}
public boolean isPaid() {
return paid;
}
public void setPaid(boolean paid) {
this.paid = paid;
}
}
// 支付服务接口
public interface PaymentService {
boolean processPayment(double amount);
}
// 订单处理服务
public class OrderService {
private PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
public boolean checkout(Order order) {
if (order.isPaid()) {
return false;
}
boolean paymentResult = paymentService.processPayment(order.getTotalAmount());
if (paymentResult) {
order.setPaid(true);
return true;
}
return false;
}
}
// 集成测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class OrderServiceIntegrationTest {
@Test
public void testCheckoutSuccess() {
// 创建模拟的支付服务
PaymentService paymentService = mock(PaymentService.class);
when(paymentService.processPayment(100.0)).thenReturn(true);
// 创建订单服务并注入模拟的支付服务
OrderService orderService = new OrderService(paymentService);
Order order = new Order("ORD123", 100.0);
// 执行测试
boolean result = orderService.checkout(order);
// 验证结果
assertTrue(result);
assertTrue(order.isPaid());
verify(paymentService, times(1)).processPayment(100.0);
}
@Test
public void testCheckoutFailure() {
PaymentService paymentService = mock(PaymentService.class);
when(paymentService.processPayment(100.0)).thenReturn(false);
OrderService orderService = new OrderService(paymentService);
Order order = new Order("ORD123", 100.0);
boolean result = orderService.checkout(order);
assertFalse(result);
assertFalse(order.isPaid());
verify(paymentService, times(1)).processPayment(100.0);
}
}
12.4.3 确认测试
确认测试验证软件是否满足用户需求和规格说明,通常包括功能测试、性能测试等。例如,测试一个用户注册功能:
// 用户类
public class User {
private String username;
private String email;
private String password;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
// Getters and setters
public String getUsername() { return username; }
public String getEmail() { return email; }
public String getPassword() { return password; }
}
// 用户服务接口
public interface UserService {
boolean registerUser(User user);
boolean isValidEmail(String email);
}
// 用户服务实现
public class UserServiceImpl implements UserService {
@Override
public boolean registerUser(User user) {
// 验证邮箱格式
if (!isValidEmail(user.getEmail())) {
return false;
}
// 验证用户名和密码长度
if (user.getUsername().length() < 3 || user.getPassword().length() < 6) {
return false;
}
// 模拟保存用户到数据库
System.out.println("用户注册成功: " + user.getUsername());
return true;
}
@Override
public boolean isValidEmail(String email) {
// 简单的邮箱格式验证
return email.matches("^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$");
}
}
// 确认测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class UserServiceVerificationTest {
@Test
public void testRegisterUserSuccess() {
UserService userService = new UserServiceImpl();
User user = new User("john_doe", "[email protected]", "password123");
assertTrue(userService.registerUser(user));
}
@Test
public void testRegisterUserInvalidEmail() {
UserService userService = new UserServiceImpl();
User user = new User("john_doe", "invalid_email", "password123");
assertFalse(userService.registerUser(user));
}
@Test
public void testRegisterUserShortUsername() {
UserService userService = new UserServiceImpl();
User user = new User("jo", "[email protected]", "password123");
assertFalse(userService.registerUser(user));
}
@Test
public void testRegisterUserShortPassword() {
UserService userService = new UserServiceImpl();
User user = new User("john_doe", "[email protected]", "pass");
assertFalse(userService.registerUser(user));
}
}
12.4.4 系统测试
系统测试将软件作为一个整体进行测试,验证系统是否满足需求。例如,测试一个在线购物系统的完整流程:
// 商品类
public class Product {
private String productId;
private String name;
private double price;
private int stock;
public Product(String productId, String name, double price, int stock) {
this.productId = productId;
this.name = name;
this.price = price;
this.stock = stock;
}
// Getters and setters
public String getProductId() { return productId; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
public void setStock(int stock) { this.stock = stock; }
}
// 购物车类
public class ShoppingCart {
private Map<Product, Integer> items = new HashMap<>();
public void addItem(Product product, int quantity) {
items.put(product, items.getOrDefault(product, 0) + quantity);
}
public void removeItem(Product product) {
items.remove(product);
}
public double getTotalPrice() {
double total = 0;
for (Map.Entry<Product, Integer> entry : items.entrySet()) {
total += entry.getKey().getPrice() * entry.getValue();
}
return total;
}
public Map<Product, Integer> getItems() {
return items;
}
}
// 订单类
public class Order {
private String orderId;
private List<Product> products;
private double totalAmount;
private OrderStatus status;
public Order(String orderId, List<Product> products, double totalAmount) {
this.orderId = orderId;
this.products = products;
this.totalAmount = totalAmount;
this.status = OrderStatus.PENDING;
}
// Getters and setters
public String getOrderId() { return orderId; }
public List<Product> getProducts() { return products; }
public double getTotalAmount() { return totalAmount; }
public OrderStatus getStatus() { return status; }
public void setStatus(OrderStatus status) { this.status = status; }
}
// 订单状态枚举
public enum OrderStatus {
PENDING, PAID, SHIPPED, DELIVERED, CANCELLED
}
// 系统测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ShoppingSystemSystemTest {
@Test
public void testEndToEndShoppingFlow() {
// 创建商品
Product laptop = new Product("P001", "笔记本电脑", 5000.0, 10);
Product mouse = new Product("P002", "鼠标", 50.0, 20);
// 创建购物车
ShoppingCart cart = new ShoppingCart();
cart.addItem(laptop, 1);
cart.addItem(mouse, 2);
// 验证购物车总价
assertEquals(5100.0, cart.getTotalPrice(), 0.001);
// 创建订单
String orderId = "ORD" + System.currentTimeMillis();
Order order = new Order(orderId,
cart.getItems().keySet().stream().collect(Collectors.toList()),
cart.getTotalPrice());
// 处理订单支付
order.setStatus(OrderStatus.PAID);
assertEquals(OrderStatus.PAID, order.getStatus());
// 更新库存
for (Map.Entry<Product, Integer> entry : cart.getItems().entrySet()) {
entry.getKey().setStock(entry.getKey().getStock() - entry.getValue());
}
// 验证库存更新
assertEquals(9, laptop.getStock());
assertEquals(18, mouse.getStock());
// 发货
order.setStatus(OrderStatus.SHIPPED);
assertEquals(OrderStatus.SHIPPED, order.getStatus());
// 确认收货
order.setStatus(OrderStatus.DELIVERED);
assertEquals(OrderStatus.DELIVERED, order.getStatus());
}
}
12.5 面向对象软件的测试
12.5.1 类的测试
类的测试关注类的属性、方法和状态。例如,测试一个银行账户类:
public class BankAccount {
private String accountNumber;
private double balance;
private AccountStatus status;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.status = AccountStatus.ACTIVE;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("存款金额必须大于0");
}
if (status != AccountStatus.ACTIVE) {
throw new IllegalStateException("账户状态异常,无法存款");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("取款金额必须大于0");
}
if (status != AccountStatus.ACTIVE) {
throw new IllegalStateException("账户状态异常,无法取款");
}
if (amount > balance) {
throw new InsufficientFundsException("余额不足");
}
balance -= amount;
}
public void close() {
if (balance != 0) {
throw new IllegalStateException("账户余额不为0,无法关闭");
}
status = AccountStatus.CLOSED;
}
// Getters
public String getAccountNumber() { return accountNumber; }
public double getBalance() { return balance; }
public AccountStatus getStatus() { return status; }
}
// 账户状态枚举
public enum AccountStatus {
ACTIVE, CLOSED, FROZEN
}
// 自定义异常
public class InsufficientFundsException extends RuntimeException {
public InsufficientFundsException(String message) {
super(message);
}
}
// 类的测试用例
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class BankAccountTest {
private BankAccount account;
@BeforeEach
public void setUp() {
account = new BankAccount("123456", 1000.0);
}
@Test
public void testInitialState() {
assertEquals("123456", account.getAccountNumber());
assertEquals(1000.0, account.getBalance(), 0.001);
assertEquals(AccountStatus.ACTIVE, account.getStatus());
}
@Test
public void testDeposit() {
account.deposit(500.0);
assertEquals(1500.0, account.getBalance(), 0.001);
}
@Test
public void testDepositNegativeAmount() {
assertThrows(IllegalArgumentException.class, () -> account.deposit(-100.0));
}
@Test
public void testWithdraw() {
account.withdraw(300.0);
assertEquals(700.0, account.getBalance(), 0.001);
}
@Test
public void testWithdrawInsufficientFunds() {
assertThrows(InsufficientFundsException.class, () -> account.withdraw(1500.0));
}
@Test
public void testCloseAccount() {
account.withdraw(1000.0); // 清空余额
account.close();
assertEquals(AccountStatus.CLOSED, account.getStatus());
}
@Test
public void testCloseAccountWithBalance() {
assertThrows(IllegalStateException.class, () -> account.close());
}
}
12.5.2 交互测试
交互测试验证对象之间的协作和交互。例如,测试一个订单处理系统中对象间的交互:
// 订单类
public class Order {
private String orderId;
private List<OrderItem> items;
private OrderStatus status;
public Order(String orderId, List<OrderItem> items) {
this.orderId = orderId;
this.items = items;
this.status = OrderStatus.PENDING;
}
public void processPayment(PaymentService paymentService) {
double totalAmount = calculateTotalAmount();
boolean paymentResult = paymentService.processPayment(totalAmount);
if (paymentResult) {
this.status = OrderStatus.PAID;
}
}
private double calculateTotalAmount() {
double total = 0;
for (OrderItem item : items) {
total += item.getProduct().getPrice() * item.getQuantity();
}
return total;
}
// Getters and setters
public String getOrderId() { return orderId; }
public List<OrderItem> getItems() { return items; }
public OrderStatus getStatus() { return status; }
}
// 订单条目类
public class OrderItem {
private Product product;
private int quantity;
public OrderItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
// Getters
public Product getProduct() { return product; }
public int getQuantity() { return quantity; }
}
// 商品类
public class Product {
private String productId;
private String name;
private double price;
public Product(String productId, String name, double price) {
this.productId = productId;
this.name = name;
this.price = price;
}
// Getters
public String getProductId() { return productId; }
public String getName() { return name; }
public double getPrice() { return price; }
}
// 支付服务接口
public interface PaymentService {
boolean processPayment(double amount);
}
// 交互测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class OrderInteractionTest {
@Test
public void testOrderPaymentProcess() {
// 创建模拟的支付服务
PaymentService paymentService = mock(PaymentService.class);
when(paymentService.processPayment(100.0)).thenReturn(true);
// 创建商品和订单条目
Product product = new Product("P001", "手机", 100.0);
OrderItem item = new OrderItem(product, 1);
// 创建订单
Order order = new Order("ORD123", List.of(item));
// 处理支付
order.processPayment(paymentService);
// 验证结果
assertEquals(OrderStatus.PAID, order.getStatus());
verify(paymentService, times(1)).processPayment(100.0);
}
@Test
public void testOrderPaymentFailure() {
PaymentService paymentService = mock(PaymentService.class);
when(paymentService.processPayment(100.0)).thenReturn(false);
Product product = new Product("P001", "手机", 100.0);
OrderItem item = new OrderItem(product, 1);
Order order = new Order("ORD123", List.of(item));
order.processPayment(paymentService);
assertEquals(OrderStatus.PENDING, order.getStatus());
verify(paymentService, times(1)).processPayment(100.0);
}
}
12.5.3 继承的测试
继承的测试关注子类与父类的关系,以及多态的正确性。例如,测试一个形状继承体系:
// 抽象形状类
public abstract class Shape {
public abstract double calculateArea();
public abstract double calculatePerimeter();
}
// 矩形类
public class Rectangle extends Shape {
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
public double calculateArea() {
return length * width;
}
@Override
public double calculatePerimeter() {
return 2 * (length + width);
}
}
// 正方形类
public class Square extends Rectangle {
public Square(double side) {
super(side, side);
}
}
// 圆形类
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
}
// 继承测试用例
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class ShapeInheritanceTest {
@Test
public void testRectangleArea() {
Rectangle rectangle = new Rectangle(5.0, 3.0);
assertEquals(15.0, rectangle.calculateArea(), 0.001);
assertEquals(16.0, rectangle.calculatePerimeter(), 0.001);
}
@Test
public void testSquareArea() {
Square square = new Square(4.0);
assertEquals(16.0, square.calculateArea(), 0.001);
assertEquals(16.0, square.calculatePerimeter(), 0.001);
}
@Test
public void testCircleArea() {
Circle circle = new Circle(2.0);
assertEquals(Math.PI * 4.0, circle.calculateArea(), 0.001);
assertEquals(2 * Math.PI * 2.0, circle.calculatePerimeter(), 0.001);
}
@Test
public void testPolymorphism() {
Shape rectangle = new Rectangle(5.0, 3.0);
Shape square = new Square(4.0);
Shape circle = new Circle(2.0);
assertEquals(15.0, rectangle.calculateArea(), 0.001);
assertEquals(16.0, square.calculateArea(), 0.001);
assertEquals(Math.PI * 4.0, circle.calculateArea(), 0.001);
}
}
12.6小结
软件测试是软件开发过程中不可或缺的环节,它贯穿于整个软件生命周期。通过本章的学习,我们了解了软件测试的概念、过程模型、测试方法以及面向对象软件的测试技术。合理运用各种测试方法和技术,能够有效发现软件中的缺陷,提高软件质量,确保软件满足用户需求。在实际项目中,应根据项目特点和需求,选择合适的测试策略和方法,制定全面的测试计划,以保证软件的可靠性和稳定性。