直接举例,还是用前面测试格式化文件路径的例子:
package github.clyoudu.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import java.util.regex.Matcher;
/**
* Create by IntelliJ IDEA
*
* @author chenlei
* @dateTime 2019/7/29 10:01
* @description FileUtil
*/
@Slf4j
public class FileUtil {
private static final String DOUBLE_SLASH = "//";
private static final String DOUBLE_BACK_SLASH = "\\\\";
private static final String SLASH = "/";
private static final String BACK_SLASH = "\\";
private FileUtil() {
}
public static String formatFilePath(String filePath) {
if (StringUtils.isBlank(filePath)) {
return filePath;
}
while (filePath.contains(DOUBLE_SLASH) || filePath.contains(DOUBLE_BACK_SLASH)) {
filePath = filePath.replaceAll(DOUBLE_SLASH, SLASH).replaceAll(Matcher.quoteReplacement(DOUBLE_BACK_SLASH), Matcher.quoteReplacement(BACK_SLASH));
}
return filePath;
}
}
如果使用传统的测试,写法如下:
package github.clyoudu.util;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import java.util.UUID;
/**
* Create by IntelliJ IDEA
*
* @author chenlei
* @dateTime 2019/7/29 11:26
* @description FileUtilTest
*/
@Slf4j
public class FileUtilTest {
@Test
public void testFormatFilePath() {
String path = " \t ";
Assert.assertEquals(path, FileUtil.formatFilePath(path));
Assert.assertTrue(FileUtil.formatFilePath(null) == null);
Assert.assertEquals("/a/b/c/d/e.txt", FileUtil.formatFilePath("///a//b//////c/d/e.txt"));
Assert.assertEquals("C:\\a\\b\\c\\d\\e.txt", FileUtil.formatFilePath("C:\\a\\\\b\\\\\\\\\\c\\d\\\\\\e.txt"));
}
}
可以看到,如果需要添加测试用例,可能需要添加代码,如果参数组装简单,可能只需要增加一两行代码,但如果参数组装很复杂,可能要添加更多的代码。同时对于同一个关注点也会重复断言,修改断言和添加用例两个动作会导致重复修改,也会让测试逻辑也来越难以维护,因此我们可以这样写,让测试用例数据和测试断言本身分离,便于后期维护和理解:
package github.clyoudu.util;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Collection;
/**
* Create by IntelliJ IDEA
*
* @author chenlei
* @dateTime 2019/8/6 20:23
* @description FileUtil1Test
*/
@RunWith(Parameterized.class)
public class FileUtil1Test {
private String path;
private String expected;
public FileUtil1Test(String path, String expected) {
this.path = path;
this.expected = expected;
}
@Parameterized.Parameters
public static Collection<Object[]> getTestData() {
return ArraysUtil.asList(new Object[][]{
{"///a//b//////c/d/e.txt", "/a/b/c/d/e.txt"},
{"C:\\a\\\\b\\\\\\\\\\c\\d\\\\\\e.txt", "C:\\a\\b\\c\\d\\e.txt"}
});
}
@Test
public void test() {
Assert.assertEquals(expected, FileUtil.formatFilePath(path));
}
}
正如上面这个例子这种看到的:一个类中只能有一个测试方法,因为整个类只有一个提供测试数据的方法,这就很难受,如果对一个有很多方法的类测试,有多少方法就要写多少个测试类,这也会让维护的难度加大。
在JUnit5中这一点得到了改善,还是同样的例子,如果使用JUnit5可以写成这样:
package github.clyoudu.util;
import org.junit.Assert;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
/**
* Created by IntelliJ IDEA
*
* @author chenlei
* @date 2019/8/11
* @time 18:19
* @desc FileUtil1Test
*/
public class FileUtil1Test {
@ParameterizedTest
@CsvSource({",", "' ',' '", "' \t ',' \t '","//a/b///c/d///e.txt,/a/b/c/d/e.txt","C:\\\\\\a\\b\\c\\d\\\\\\\\\\e.txt,C:\\a\\b\\c\\d\\e.txt"})
void testFormatFilePath(String path, String expected){
Assert.assertEquals(FileUtil.formatFilePath(path), expected);
}
}
JUnit5可以有更丰富的注解,支持测试用例和断言分离,而且可以自定义参数转换,可以将注解中的数据转换成一个自定义的对象传入测试。JUnit4目前还不能支持这样的测试,这是JUnit4的做参数化测试的局限。
我们增强Junit的时候一般是自定义一个Runner,比如常用的PowerMockRunner、SpringRunner、MockitoJUnitRunner、SpringJUnit4ClassRunner等,我们一定也可以写一个自己的Runner,用于支持测试数据外部化,定义几个注解等等,但这在JUnit5中已经支持了,不过使用JUnit5又有IDE版本限制,比如IDEA不同版本支持的JUnit5版本也是不同的,Eclipse应该也有类似的问题,所以最好在成熟的JUnit4上支持参数外部化,那有没有现成的Runner可以用呢?
答案是肯定的,JUnit类似的测试工具——TESTNG是有专门的参数化测试解决方案,有@DataProvider注解,结合JUnit Runner的架构,可以把这种思想在Junit上实现,于是有了junit-dataprovider,它的使用方式跟JUnit5是非常相似的,用同样的例子写一下:
首先加入junit-dataprovider引用
<dependency>
<groupId>com.tngtech.javagroupId>
<artifactId>junit-dataproviderartifactId>
<version>1.13.1version>
<scope>testscope>
dependency>
package github.clyoudu.util;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Created by IntelliJ IDEA
*
* @author chenlei
* @date 2019/8/11
* @time 18:59
* @desc FileUtil2Test
*/
@RunWith(DataProviderRunner.class)
public class FileUtil2Test {
@Test
@UseDataProvider("testData")
public void test(String path, String expected) {
Assert.assertEquals(FileUtil.formatFilePath(path), expected);
}
@DataProvider
public static Object[][] testData() {
return new Object[][] {
{null, null},
{" ", " "},
{" \t ", " \t "},
{"//a/b///c/d///e.txt", "/a/b/c/d/e.txt"},
{"C:\\\\\\a\\b\\c\\d\\\\\\\\\\e.txt", "C:\\a\\b\\c\\d\\e.txt"}
};
}
@Test
@DataProvider(value = {",", "' ',' '", "' \t ',' \t '","//a/b///c/d///e.txt,/a/b/c/d/e.txt","C:\\\\\\a\\b\\c\\d\\\\\\\\\\e.txt,C:\\a\\b\\c\\d\\e.txt"}, trimValues = false, format = "%m: %p[0] -> %p[1]")
public void test1(String path, String expected) {
Assert.assertEquals(FileUtil.formatFilePath(path), expected);
}
@Test
@UseDataProvider("test2Data")
public void test2(FileFormatDto fileFormatDto) {
Assert.assertEquals(FileUtil.formatFilePath(fileFormatDto.getPath()), fileFormatDto.getExpected());
}
@DataProvider(format = "%m: %p[0]")
public static Object[][] test2Data() {
return new Object[][] {
{new FileFormatDto(null,null)},
{new FileFormatDto(" ", " ")},
{new FileFormatDto(" \t ", " \t ")},
{new FileFormatDto("//a/b///c/d///e.txt", "/a/b/c/d/e.txt")},
{new FileFormatDto("C:\\\\\\a\\b\\c\\d\\\\\\\\\\e.txt", "C:\\a\\b\\c\\d\\e.txt")}
};
}
static class FileFormatDto {
private String path;
private String expected;
public FileFormatDto(String path, String expected) {
this.path = path;
this.expected = expected;
}
public String getPath() {
return path;
}
public String getExpected() {
return expected;
}
@Override
public String toString() {
return path + " -> " + expected;
}
}
}
虽然不如JUnit5炫酷,支持的Provider类型也不多,没有JUnit5的@EmptySource、@EnumSource、@NummSource等,但可以高度自定义,可以在一个类中定义多个方法的测试及数据,而且没有IDE版本不同需要引入不同版本jar包的问题,因此可以尝试使用junit-dataprovider。