前言:单元测试是保障代码质量、确保软件稳定可靠的关键环节。
目录
一、单元测试
代码的 “体检”
先来聊聊单元测试是个啥。简单来说,就是把代码里的一个个 “单元”(比如方法、函数)单独揪出来测一测,看看它们能不能正常工作。就好比给身体做体检,先查每个器官,看有没有毛病。
为啥要做单元测试呢?好处可多了:
-
早发现早治疗 :在代码刚写完的时候就测,问题还小,修复起来省时省力。
-
质量有保障 :把代码拆开测,能揪出隐藏的缺陷,让代码更可靠。
-
文档式示例 :测试代码本身就是文档,新来的小伙伴能快速看懂代码怎么用。
举个简单例子,假设有个 Calculator
类,能做加减乘除:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public double multiply(double a, double b) {
return a * b;
}
public double divide(double a, double b) {
if (b == 0) {
throw new ArithmeticException("除数不能为零");
}
return a / b;
}
}
我们想测测它的加法功能,最原始的办法就是写个主函数来试试:
public class CalculatorTest {
public static void main(String[] args) {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
if (result == 5) {
System.out.println("加法测试通过!");
} else {
System.out.println("加法测试失败!");
}
}
}
运行这段代码,如果输出 “加法测试通过!”,说明加法功能正常。不过,这种方法测试起来又慢又麻烦,尤其是功能多了以后。这就轮到 JUnit 和 TestNG 出场了。
二、JUnit:单元测试的 “老大哥”
JUnit 是 Java 单元测试的元老级框架,上手简单,功能又强。
(一)JUnit 的基本用法
先来个简单的 JUnit 测试示例:
import static org.junit.Assert.*;
import org.junit.Test;
public class CalculatorJUnitTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals("加法测试失败,结果不等于 5", 5, result);
System.out.println("加法测试结果:正确");
}
}
注意看代码里的 @Test
,这就是 JUnit 的注解,告诉程序这个方法是个测试用例。assertEquals
是断言方法,如果结果不对,就会报错。运行这个测试,如果没报错,控制台就会输出 “加法测试结果:正确”。
(二)JUnit 的进阶用法
-
多个测试用例 :把
Calculator
的各种功能都测一测:
import static org.junit.Assert.*;
import org.junit.Test;
public class CalculatorJUnitTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals("加法测试失败,结果不等于 5", 5, result);
System.out.println("加法测试通过,结果正确");
}
@Test
public void testSubtract() {
Calculator calculator = new Calculator();
int result = calculator.subtract(5, 3);
assertEquals("减法测试失败,结果不等于 2", 2, result);
System.out.println("减法测试通过,结果正确");
}
@Test
public void testMultiply() {
Calculator calculator = new Calculator();
double result = calculator.multiply(4.5, 2.3);
assertEquals("乘法测试失败,结果不正确", 10.35, result, 0.001);
System.out.println("乘法测试通过,结果正确");
}
@Test(expected = ArithmeticException.class)
public void testDivideByZero() {
Calculator calculator = new Calculator();
calculator.divide(5, 0);
}
}
这里的 testDivideByZero
方法用了 expected
属性,专门测试除以零会不会抛出异常。
-
测试套件 :如果测试用例多了,可以用测试套件把它们整合在一起:
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses({ CalculatorJUnitTest.class })
public class TestSuite {
}
运行这个测试套件,就能把相关的测试用例都跑一遍。
三、TestNG
JUnit 的 “升级版”
TestNG 在 JUnit 的基础上做了改进,功能更强大,特别适合复杂的测试场景。
(一)TestNG 的基本用法
简单测试一下 Calculator
的加法功能:
import org.testng.annotations.Test;
public class CalculatorTestNGTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
org.testng.Assert.assertEquals(result, 5, "加法测试失败");
System.out.println("加法测试通过,结果正确");
}
}
和 JUnit 类似,用 @Test
标记测试方法,org.testng.Assert.assertEquals
是断言。
(二)TestNG 的进阶用法
-
灵活的执行顺序 :可以通过
priority
属性指定测试方法的执行顺序:
import org.testng.annotations.Test;
public class CalculatorTestNGTest {
@Test(priority = 1)
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
org.testng.Assert.assertEquals(result, 5, "加法测试失败");
System.out.println("加法测试优先执行");
}
@Test(priority = 2)
public void testSubtract() {
Calculator calculator = new Calculator();
int result = calculator.subtract(5, 3);
org.testng.Assert.assertEquals(result, 2, "减法测试失败");
System.out.println("减法测试随后执行");
}
}
运行测试,会先跑 testAdd
,再跑 testSubtract
。
-
参数化测试 :如果想测试多种输入情况,可以用参数化测试:
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
public class ParameterizedTest {
@Test
@Parameters({ "a", "b" })
public void testAdd(int a, int b) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
System.out.println(a + " + " + b + " = " + result);
}
}
在运行测试时,可以通过配置文件传入不同的参数值,测试多种情况。
-
数据驱动测试 :把测试数据和测试逻辑分开,用
@DataProvider
提供数据:
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class DataDrivenTest {
@DataProvider(name = "testData")
public Object[][] provideData() {
return new Object[][] { { 2, 3 }, { 5, 7 }, { 10, 15 } };
}
@Test(dataProvider = "testData")
public void testAdd(int a, int b) {
Calculator calculator = new Calculator();
int result = calculator.add(a, b);
System.out.println(a + " + " + b + " = " + result);
}
}
provideData
方法提供多组数据,testAdd
方法用这些数据循环测试。
-
并发测试 :如果想模拟多个线程同时运行测试,TestNG 也支持:
import org.testng.annotations.Test;
public class ConcurrentTest {
@Test(invocationCount = 3, threadPoolSize = 2)
public void testAdd() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
System.out.println("测试执行,线程 ID:" + Thread.currentThread().getId() + ",结果:" + result);
}
}
设置 invocationCount
和 threadPoolSize
,就能让测试方法用多个线程跑多次。
四、JUnit 与 TestNG
选谁更好?
JUnit 和 TestNG 各有千秋,选谁看需求:
-
项目复杂度 :简单项目,JUnit 足够用;复杂项目,TestNG 更合适,功能强大。
-
测试场景 :要是有参数化、数据驱动、并发测试这些复杂需求,TestNG 是首选;要是就测测简单的功能,JUnit 也行。
-
团队熟悉度 :团队用哪个顺手,就优先选哪个,毕竟熟练了效率高。
对比一下关键特性:
对比维度 | JUnit | TestNG |
---|---|---|
测试用例组织 | 以类的形式组织,方法以 test 开头 | 用注解灵活控制 |
参数化测试 | 需继承 Parameterized 类 | 用 @Parameters 注解轻松搞定 |
数据驱动测试 | 无直接支持 | 用 @DataProvider 注解方便实现 |
并发测试 | 支持有限 | 支持良好,可设置线程池大小 |
与 Selenium 集成 | 较少 | 紧密,Web 测试首选 |
总结
单元测试是提升代码质量的必备技能,JUnit 和 TestNG 是两个强有力的工具。JUnit 上手快,适合简单项目;TestNG 功能强,适合复杂场景。无论选谁,养成写单元测试的习惯,就是给代码系上了 “安全带”,让代码更稳、更可靠。