【单元测试】JUnit参数化测试的讨论

文章目录

  • 什么是测试参数化
  • JUnit4参数化测试的局限
  • JUnit4 DataProvider

什么是测试参数化

直接举例,还是用前面测试格式化文件路径的例子:

 
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"));
    }
 
}

【单元测试】JUnit参数化测试的讨论_第1张图片

可以看到,如果需要添加测试用例,可能需要添加代码,如果参数组装简单,可能只需要增加一两行代码,但如果参数组装很复杂,可能要添加更多的代码。同时对于同一个关注点也会重复断言,修改断言和添加用例两个动作会导致重复修改,也会让测试逻辑也来越难以维护,因此我们可以这样写,让测试用例数据和测试断言本身分离,便于后期维护和理解:

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));
    }
 
}

JUnit4参数化测试的局限

正如上面这个例子这种看到的:一个类中只能有一个测试方法,因为整个类只有一个提供测试数据的方法,这就很难受,如果对一个有很多方法的类测试,有多少方法就要写多少个测试类,这也会让维护的难度加大。

在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);
    }
 
}

【单元测试】JUnit参数化测试的讨论_第2张图片

JUnit5可以有更丰富的注解,支持测试用例和断言分离,而且可以自定义参数转换,可以将注解中的数据转换成一个自定义的对象传入测试。JUnit4目前还不能支持这样的测试,这是JUnit4的做参数化测试的局限。

JUnit4 DataProvider

我们增强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;
        }
    }
 
}

【单元测试】JUnit参数化测试的讨论_第3张图片
虽然不如JUnit5炫酷,支持的Provider类型也不多,没有JUnit5的@EmptySource、@EnumSource、@NummSource等,但可以高度自定义,可以在一个类中定义多个方法的测试及数据,而且没有IDE版本不同需要引入不同版本jar包的问题,因此可以尝试使用junit-dataprovider。

你可能感兴趣的:(Java,单元测试)