【问题标题】:A TestNg Multithreading Issue. TestNg does not respect the child threadsTestNg 多线程问题。 TestNg 不尊重子线程
【发布时间】:2016-06-24 21:20:27
【问题描述】:

我有一个非常简单的类,可以将列表异步写入文件:

import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;

public enum FileOps {
    INSTANCE;

    private ExecutorService threadPool = Executors.newFixedThreadPool(30);
    private AtomicInteger fileCount = new AtomicInteger(0);

    private <T> void writeListToFile(String fileName, List<T> obj) {
        FileWriter writer = null;
        Type tType = new TypeToken<ArrayList<T>>() {
            private static final long serialVersionUID = 4376511240656742709L;
        }.getType();
        Gson gson = new Gson();
        try {
            writer = new FileWriter(fileName);
            writer.append(gson.toJson(obj, tType));
            writer.flush();
        } catch (Exception e) {

        } finally {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    public <T> void asynWriteListToFile(List<T> obj){
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                String fileName = "C:\\data\\" + fileCount.incrementAndGet() + "_data.txt";
                System.out.println(fileName);
                FileOps.INSTANCE.writeListToFile(fileName, obj);
            }
        });
    }

}

我已经使用 TestNg 为这个类编写了一个单元测试

import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.Test;

public class FileOpsTest {

    @Test
    public void asynWriteListToFile() {
        List<Integer> list = new ArrayList<>();
        list.add(3);
        for (int i = 0; i < 10000; i++) {
            FileOps.INSTANCE.asynWriteListToFile(list);
        }

    }
}

我的情况很奇怪。在我的 TestNg 执行中,测试引擎如何不等待子线程完成。所以我期望在磁盘上写入 10000 个文件,但每次我看到磁盘上写入的文件更少。但是,如果我使用 main 方法编写客户端,则一切正常。

import java.util.ArrayList;
import java.util.List;

public class FileOpsClient {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(3);
        for (int i = 0; i < 10000; i++) {
            FileOps.INSTANCE.asynWriteListToFile(list);
        }
    }
}

testNg 引擎不知何故关闭了我的线程池。

【问题讨论】:

    标签: java multithreading testng


    【解决方案1】:

    由于文件是异步写入的,FileOpsTest.asynWriteListToFile() 在文件全部写入之前结束,org.testng.TestNG(或您的 IDE 的测试运行程序)调用 System.exit(int)(例如 TestNG.java:1375)。

    相比之下,FileOpsClient.main(String[]) 没有显式调用System.exit(int),因此 JVM 等待您的线程结束,因为它们不是守护线程。详情请见How to make TestNG wait for my test to complete before shutting it down

    在您的情况下,您可以进行一些更改,以便您的测试可以有效地调用 threadPool.awaitTermination(long, TimeUnit)(例如,将 FileOps.threadPool 设为“本地包”而不是“私有”并从您的测试中访问它,在FileOps 为您这样做并保持FileOps.threadPool“私人”等)。

    但是,如果您的目标是进行单元测试,那么我建议 ExecutorService 是一个“尴尬的协作者”,并且您应该重构您的代码,以便 1) 您可以使用模拟的 ExecutorService (请参阅How to unit test that ExecutorService spawns new thread for task?),2) 您可以测试将列表实际写入文件的逻辑,与如何将其创建为异步任务无关,以及 3) 不要直接使用 FileWriter,而只需使用 Writer 所以您还可以在测试时进行模拟,避免在单元测试中实际写入/读取文件,并在需要时将此类练习留给集成测试。

    【讨论】:

    • 批评者不直接使用 FileWriter 而是将抽象 Writer 类型注入到模拟方法中,并且为了更可测试的代码改变了我对单元测试的看法。这是一个顿悟。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-29
    • 2014-12-20
    • 1970-01-01
    • 2021-04-30
    相关资源
    最近更新 更多