结合自己在企业项目的感受,谈谈为什么一定要用Junit来写测试类。之前看过好多人写的代码都是通java main方法来完成测试。主要是谈谈两者区别?
从代码从此分析入手一下,定义一个类
public class Calculate { public int compute(int x, int y) { return x + y; } } main方法测试 public class TestApp { public static void main( String[] args ) { Calculate calculate = new Calculate(); int result = calculate.compute(100, 100); System.out.println(result); } } junit4测试 public class CalculateTest { @Test public void calculateTest() { Calculate calculate = new Calculate(); int result = calculate.compute(100, 100); Assert.assertEquals(200, result); } }从上面的代码执行情况来看,是区别不大的,我们通过运行我们写的测试用例代码来看,都能够起到验证结果。但是还是有以下几点区别,值得讨论一下?
我们的项目代码每天都会产生变化,也就是说代码变化后,我们都应该保证之前的功能是正确的。如下图所示,我们代码有三个版本迭代,我们讨论一下是否Calculate类的测试案例都应该测试一下。
自己修改了Calculate类其他人修改了Calculate类所有人都没有修改Calculate类,且修改其他类对Calculate类无影响所有人都没有修改Calculate类,且修改其他类对Calculate类有影响如果是编写main方法测试用例,自己肯定会运行main测试用例,所以情况1 3代码都是正确的,因为我们都有对应的测试用例,无法保证情况2 4是否一定正确
那么junit是如何保证的1 3 2 4情况 ,这里面我通过maven管理项目,maven生命周期中,有一个测试test周期,可以检验我们所有的测试用例,比如我写了一个工程,这里面有12个junit测试用例,每次我编译、打包的项目时候,都需要运行所有的测试用例,执行以下命令
mvn test我们发现第一个就是我们CalculateTest单元测试类,一共有12个测试类。这里面都验证通过,这里假设有人改了Calculate类里面的逻辑,且没有通知我,修改逻辑如下:
public class Calculate { public int compute(int x, int y) { return x + y + 1; } }当我再次运行我的mvn test时,CalculateTest单元测试类出现异常 12个用例中失败了一个 回到junit测试代码中,因为我们写了预期值,不满足就会报错,这样正在做到每一个版本都做到所有测试用例检查,如果通过main方法那样去运行是做不到的情况2 4,因为我们无法得知别人什么时候更改了什么
public class CalculateTest { @Test public void calculateTest() { Calculate calculate = new Calculate(); int result = calculate.compute(100, 100); Assert.assertEquals(200, result); } }由于测试用例代码都是检查代码,和实际运行代码是无关的,所以是费代码,如果把测试用例代码和生产代码区分不明确,会导致工程出现大量费代码,所有还是将测试用例单独管理比较好。 junit因为会有一些关系自,如**Test关键字区分,可以在构建工程时候,将测试用例代码剥离出来。
下面谈谈junit4语法
当前主要使用junit4语法进行编写测试类,下面将展示以下主要用途
我这里面项目都是maven管理的,配置junit依赖如下,也可以自己导入对应的jar包
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency>逻辑类
public class Calculate { public int compute(int x, int y) { return x + y; } }junit测试类,方法名上面加上@Test即可
public class CalculateTest { @Test public void calculateTest() { Calculate calculate = new Calculate(); int result = calculate.compute(100, 100); Assert.assertEquals(200, result); } }运行方式,通过Junit Test
测试通过情况
改一下预期值为201,测试不通过情况
public class CalculateTest { @Test public void calculateTest() { Calculate calculate = new Calculate(); int result = calculate.compute(100, 100); Assert.assertEquals(201, result); } }输出期望是201,但是实际值为200,,所以测试不通过 java.lang.AssertionError: expected:<201> but was:<200>
其中Assert有大量的判等与不等的情况API使用,还需要使用者自行查看
定义几个周期的关键字@BeforeClass、@AfterClass、@Before、@After、@Test
public class JunitProcessTest { @BeforeClass public static void beforeClass() { System.out.println("in before class"); } @AfterClass public static void afterClass() { System.out.println("in after class"); } @Before public void before() { System.out.println("in before"); } @After public void after() { System.out.println("in after"); } @Test public void testCase1() { System.out.println("in test case 1"); } @Test public void testCase2() { System.out.println("in test case 2"); } }测试结果:
in before class in before in test case 1 in after in before in test case 2 in after in after class其中@BeforeClass、@AfterClass只加载一次。而@Before、@After每次都会伴随@Test运行,Junit生命周期由上面五个关键字定义
我们不需要测试类运行,可以加入关键字@Ignore,程序每次运行默认验证成功。
public class IgnoreTest { @Ignore // 忽略、不再有效 @Test public void verifyTrue() throws Exception { Assert.assertTrue(false); } }运行结果通过,注释掉@Ignore程序会执行测试用例,该测试用例会报错的
在@Test关键字加上timeout属性如@Test(timeout = 2000L),时间单位为毫秒
测试通过,因为测试时间为大概1秒,未超过2秒
public class TimeoutTest { @Test(timeout = 2000L) public void verifyFastZipCodeMatch() throws Exception { TimeUnit.SECONDS.sleep(1); } }调整测试时间为3秒,超过2秒,再次测试
public class TimeoutTest { @Test(timeout = 2000L) public void verifyFastZipCodeMatch() throws Exception { TimeUnit.SECONDS.sleep(3); } }在@Test关键字加上expected属性如@Test(expected = ArithmeticException.class), 我们预期测试用例会抛出ArithmeticException异常
public class ExceptionTest { @Test(expected = ArithmeticException.class) public void verifyException() throws Exception { int i = 1 / 0; } }测试结果:
修改程序,我们期待抛出异常ArithmeticException,实际抛出NullPointerException
public class ExceptionTest { @Test(expected = ArithmeticException.class) public void verifyException() throws Exception { List list = null; list.clear(); } }简单理解为批量测试,如我们定义了一个加法算法f(x,y)=x+y+1,我们期待测试5组数据如下
xyf(x,y)1132253374385612先给出例子说明,再说明具体关键字 逻辑类
public class Calculate { public int compute(int x, int y) { return x + y + 1; } }测试用例
@RunWith(Parameterized.class) public class ParameterCalculateTest { private int x; private int y; private int result; public ParameterCalculateTest(int x, int y, int result) { this.x = x; this.y = y; this.result = result; } @Test public void verifyGoodZipCode() throws Exception { Calculate calculate = new Calculate(); int compute = calculate.compute(x, y); Assert.assertEquals("f(x,y)!=x+y+1", result, compute); } @Parameters public static List<Integer[]> regExValues() { return Arrays.asList(new Integer[][] { { 1,1,3}, { 2,2,5}, { 3,3,7 }, { 4,3,8 } , { 5,6,12 } }); } }所有的测试数据都测试通过:
其中@RunWith(Parameterized.class)表示类为参数化测试,@Parameters表示参数化数据,这里我先改修一组测试数据1,1,4,再测验证一下
@Parameters public static List<Integer[]> regExValues() { return Arrays.asList(new Integer[][] { { 1,1,4}, { 2,2,5}, { 3,3,7 }, { 4,3,8 } , { 5,6,12 } }); }“套件测试”是指把几个单元测试用例组合起来运行。先给出例子说明,再说明具体关键字
单元测试案例一
public class JunitTest1 { @Test public void printMessage() { System.out.println("in JunitTest1"); } }单元测试案例二
public class JunitTest2 { @Test public void printMessage() { System.out.println("in JunitTest2"); } }套件测试案例
@RunWith(Suite.class) @Suite.SuiteClasses({ JunitTest1.class, JunitTest2.class }) public class JunitSuite { }其中JunitSuite组合了两个测试类,运行JunitSuite,测试结果:
关键字解释:其中@RunWith(Suite.class)表示类为参数化测试类型为Suite,其中@Suite.SuiteClasses表示需要组合的测试类
@Suite.SuiteClasses({ JunitTest1.class, JunitTest2.class })也就是批量测试
点到即止吧,maven相关的我就不讲解了,主要是说明mvn test只会自动运行*Test.java或*TestCase.java后缀的测试类,如果需要修改,可以在pom配置如下,可以添加想测试的
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.19.1</version> <configuration> <includes> <include>**/*TestNon.java</include> </includes> </configuration> </plugin>托管github对应代码:https://github.com/dengjili/junit-demo
