走进Java接口测试之解决超大文本数据驱动报OOM问题

举报
zuozewei 发表于 2022/01/03 15:56:25 2022/01/03
【摘要】 走进Java接口测试之解决超大文本数据驱动报OOM问题

前言

上篇文章 走进Java接口测试之测试框架TestNG数据驱动(入门篇) 阐述测试框架 TestNG 中的一些基本的概念和玩法,本文带着大家来解决一个实际的工程问题。

问题分析

现象

使用文本做数据驱动的时候出现 JVM Heap 区 OOM。

在这里插入图片描述

原因

核实下 IDEA 的 JVM 参数设置,JVM 最大可用内存为 2G:

在这里插入图片描述

考虑到参数化文件大概有 20 万条记录,判断这是由于程序一次性读取大量的文本数据导致的。

解法

这时候我们想到测试框架 TestNG 有为这种大量数据驱动场景提供解法,即:延迟数据提供者

有的场景我们需要大量参数进行读取,比如参数数据源是 DB,而数据达到百万级,这样测试程序遍历所有数据时,可能就会导致内存溢出。
那么我们怎样解决这个问题?当我们获取了一条数据,对它执行测试方法,然后就废弃这个数据对象,再测试下一个。这个原则是延迟初始化,这个思想就是当你真正需要一个对象时才创建它,而不是提前创建它

具体实现

为了实现这种延迟加载的方法,TestNG 允许我们从数据提供者返回一个 Iterator 对象,而不是一个二维对象数组。

Iterator 是 java.util 包中的一个接口,它的方法签名如下:

public interface Iterator<E> {

    boolean hasNext();
    E next();
    default void remove();

 }

它可以通过 next 调用下一组数据,这样就有机会在最后一刻实例化相应的对象,即刚好在需要在这些参数的测试方法被调用之前。这样的好处是不用把所有的测试数据都加载到内存中,而是需要的时候就读一条。

首先配置 maven 依赖包:

   <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--引入 testng 测试框架-->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.14.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

然后实现 Iterator 接口,用于从文件中读取数据,返回给被测试类:


@Slf4j
public class TxtIterator implements Iterator<Object[]> {

    /**
     *  数据文件
     */
    File txtFile;
    BufferedReader bs;
    String currentLine;

    public TxtIterator(File txtFile) throws IOException {
        super();
        this.txtFile = txtFile;
        try {
            bs = new BufferedReader(new FileReader(txtFile));
        } catch (FileNotFoundException e) {
            log.error("文件找不到");
            e.printStackTrace();
        }
        currentLine = bs.readLine();
    }

    @Override
    public boolean hasNext() {
        if (currentLine != null) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public String[] next() {
        String returnLine = currentLine;
        try {
            currentLine = bs.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return returnLine.split(",");
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

@DataProvider 函数调用:

	String filePath; // 文件名

    @Parameters({"filePath"})
    @BeforeClass()
    public void beforeClass(String filePath) {
        log.info("文件路径:[{}]",filePath);
        this.filePath =  System.getProperty("user.dir") + "\\" + filePath;;
    }


    @DataProvider(name = "iterator")
    public Iterator<Object[]> iteratorDataProvider() throws IOException {
        log.info("文件路径:[{}]",filePath);
        return new TxtIterator(new File(filePath));
    }

@Test 测试运行函数:

    @Test(dataProvider = "iterator" ,description = "测试延迟提供数据")
    public void testcase2(String username,String password) throws InterruptedException {
       log.info(" username = [{}] ,password = [{}]" ,username,password );
       
       // 休眠2秒
       Thread.sleep(2000);
    }

整体测试类代码:


@SpringBootTest
@Slf4j
public class DataProviderTest extends AbstractTestNGSpringContextTests {

    String filePath; // 文件名

    @Parameters({"filePath"})
    @BeforeClass()
    public void beforeClass(String filePath) {
        log.info("文件路径:[{}]",filePath);
        this.filePath =  System.getProperty("user.dir") + "\\" + filePath;;
    }


    @DataProvider(name = "iterator")
    public Iterator<Object[]> iteratorDataProvider() throws IOException {
        log.info("文件路径:[{}]",filePath);
        return new TxtIterator(new File(filePath));
    }

    @Test(dataProvider = "iterator" ,description = "测试延迟提供数据")
    public void testcase2(String username,String password) throws InterruptedException {
       log.info(" username = [{}] ,password = [{}]" ,username,password );

       // 休眠2秒
       Thread.sleep(2000);
    }

}

testng.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Suite">
    <parameter name="filePath" value="data.txt"/> <!--文件名-->
    <test name="DataProviderTest">
        <classes>
            <class name="com.zuozewei.springboottextdatadrivendemo.TestCase.DataProviderTest" />
        </classes>
    </test>
</suite>

数据文件 data.txt

Liming,12
HanMeimei,13
Lily,11
Lucy,12

运行结果:

在这里插入图片描述

小结

  • 运行 testng.xml,找到对应的测试类,执行前需要初始化 filePath 参数,于是从 testng.xml 文件中把参数的值取出来,传给了测试类中的 filePath 变量。
  • 开始执行测试,发现该测试方法需要一个 DataProvider,于是在本类中找到了 iteratorDataProvider() 方法,执行该方法,构造出 Iterator 对象,传递给测试方法。
  • Iterator 对象使用了 filePath 值构造出一个 BufferedReader 对象,每当测试方法需要一条数据时就由 BufferedReader 读一条数据出来,再拆分成数组,返回给测试方法调用。

这样就实现了延迟提供的数据驱动

示例代码:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。