2016-06-23 38 views
0

我有一个非常简单的类,异步写入列表保存到文件:TestNg多线程问题。 TestNG的不尊重子线程

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

} 

我已经写了单元测试

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

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

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引擎关闭我的线程池。

回答

2

由于文件异步写入,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,这样你在测试时也可以进行模拟并避免实际在单元测试中写入/读取文件,并在需要时将这种练习留给集成测试。

+1

批评人士不直接使用FileWriter,而是将抽象Writer类型注入到模拟方法中,而对于更多可测试的代码更改方式则改变了单元测试的方式。这是一个顿悟。谢谢。 – cgon