Java JUnit测试只有在我逐个运行时才通过。线程可能不正确

Java JUnit测试只有在我逐个运行时才通过。线程可能不正确,java,spring,multithreading,spring-boot,junit,Java,Spring,Multithreading,Spring Boot,Junit,UPD:有标记的答案解决了这个问题,请参阅它的注释以获得小的修复 首先,请原谅我对代码的残杀。这个问题让我发疯,我尝试了很多方法来确定问题的根源,但是失败了 我有一个SpringBootWeb应用程序,它有FMonitor服务。当某个文件夹中出现扩展名为“.done”的文件时,它会在应用程序运行时工作并通知我。当我运行这个应用程序的时候,它工作得非常完美(至少看起来是这样) 我决定练习测试,并抛出了三个简单的测试。当我一次一个地运行它们时,它们工作得非常成功。然而,当我运行整个类的测试时,只有前

UPD:有标记的答案解决了这个问题,请参阅它的注释以获得小的修复

首先,请原谅我对代码的残杀。这个问题让我发疯,我尝试了很多方法来确定问题的根源,但是失败了

我有一个SpringBootWeb应用程序,它有FMonitor服务。当某个文件夹中出现扩展名为“.done”的文件时,它会在应用程序运行时工作并通知我。当我运行这个应用程序的时候,它工作得非常完美(至少看起来是这样)

我决定练习测试,并抛出了三个简单的测试。当我一次一个地运行它们时,它们工作得非常成功。然而,当我运行整个类的测试时,只有前一个通过,其他两个失败

我的测试检查系统,并将其与所需的输出进行比较。这是我最奇怪的事情。事情就是这样

第一次测试通过后,FMMonitor的输出与assertEquals中的完全相同。 找到test1 找到测试2 找到测试3

第二次测试失败。由于某些原因,产量奇怪地翻了一番: 找到test1 找到test1 找到测试3 找到测试3

然后第三个失败了。现在产量是原来的三倍: 发现试验 发现试验 发现试验

我的猜测是,我对线程做了一些完全错误的操作,因此fm.monitor()以某种方式捕获了所有事件以及类似的内容。我很困惑。我在这里尝试了很多关于如何实现线程的方法,我不太擅长,但它仍然可以正常工作。我还认为@Async annotation for monitor()可能会搞乱一些事情,但删除它并没有改变任何事情。请帮忙

BunchOfTests

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.*;
import java.util.concurrent.Executor;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = FMonitor.class)
public class BunchOfTests {
    private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
    private Executor executor;

    @Autowired
    FMonitor fm;

    @Test
    public void test1() throws InterruptedException, IOException {
        Runnable task = () -> {
            System.setOut(new PrintStream(outContent));
            fm.monitor();
            System.setOut(System.out);
        };
        executor = (runnable) -> new Thread(runnable).start();
        executor.executeTask(task);
        Thread.sleep(3000);
        File file1 = new File("C:\\dir\\test1.done");
        File file2 = new File("C:\\dir\\test2.done");
        File file3 = new File("C:\\dir\\test3.done");
        file1.createNewFile();
        file2.createNewFile();
        file3.createNewFile();
        Thread.sleep(3000);
        file1.delete();
        file2.delete();
        file3.delete();
        Assert.assertEquals("Found test1\r\n" + "Found test2\r\n" + "Found test3\r\n", outContent);
    }

    @Test
    public void test2() throws InterruptedException, IOException {
        Runnable task = () -> {
            System.setOut(new PrintStream(outContent));
            fm.monitor();
            System.setOut(System.out);
        };
        executor = (runnable) -> new Thread(runnable).start();
        executor.executeTask(task);
        Thread.sleep(3000);
        File file1 = new File("C:\\dir\\test1.done");
        File file2 = new File("C:\\dir\\test2.txt");
        File file3 = new File("C:\\dir\\test3.done");
        file1.createNewFile();
        file2.createNewFile();
        file3.createNewFile();
        Thread.sleep(3000);
        file1.delete();
        file2.delete();
        file3.delete();
        Assert.assertEquals("Found test1\r\n" + "Found test3\r\n", outContent);
    }

    @Test
    public void test3() throws InterruptedException, IOException {
        Runnable task = () -> {
            System.setOut(new PrintStream(outContent));
            fm.monitor();
            System.setOut(System.out);
        };
        executor = (runnable) -> new Thread(runnable).start();
        executor.executeTask(task);
        Thread.sleep(3000);
        File file = new File("C:\\dir\\test.done");
        file.createNewFile();
        Thread.sleep(3000);
        file.delete();
        Assert.assertEquals("Found test\r\n", outContent);
    }
}
FMonitor

import org.springframework.stereotype.Service;
import org.srpingframework.scheduling.annotation.Async;

import java.io.IOException;
import java.nio.file.*;

@Service
public class FMonitor {

    @Async("fMonitor")
    public void monitor() {
        Path path = Paths.get("C:\\dir");
        try {
            WatchService watchService = FileSystems.getDefault.newWatchService();
            path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
            WatchKey key;
            while ((key = watchService.take()) != null) {
                for (WatchEvent<?> event: key.pollEvents()) {
                    String filename = event.context().toString();
                    if (filename.endsWith(".done")) {
                        processFile(filename.substring(0, filename.lastIndexOf('.')));
                    }
                }
                key.reset();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void processFile(String filename) {
        System.out.println("Found " + filename);
    }
}

首先,您应该在完成后完全停止
监视服务
。实现一个这样做的方法,并使用
@PreDestroy
进行注释

@Service
public class FMonitor {

    private final WatchService watchService = FileSystems.getDefault.newWatchService();


    @Async("fMonitor")
    public void monitor() {
        Path path = Paths.get("C:\\dir");
        try {
            path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
            WatchKey key;
            while ((key = watchService.take()) != null) {
                for (WatchEvent<?> event: key.pollEvents()) {
                    String filename = event.context().toString();
                    if (filename.endsWith(".done")) {
                        processFile(filename.substring(0, filename.lastIndexOf('.')));
                    }
                }
                key.reset();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void processFile(String filename) {
        System.out.println("Found " + filename);
    }

    @PreDestroy
    public void shutdown() {
      try {
        watchService.close();
      } catch (IOException ex) {}
    }
}

类似这样的操作或多或少应该满足您的需要。

使用适当的执行器,在测试结束后关闭它,以便停止线程。@M.Deinum我尝试过,没有帮助您尝试过什么?我也不太明白你的测试。为什么您在执行器中执行任务,而Spring Boot已经在线程中执行该任务。。。。每次调用
monitor
时,它都会启动一个新的监视器。将一个添加到任务列表中,因为只有一个应用程序。因此,您将完成多个任务,每个测试一个任务。因此,第一个执行1个任务,第二个执行2个任务,第三个执行3个任务。您的测试中也不需要执行器。@M.Deinum我尝试过使用“带关机的正确执行器”。执行人服务。如果您知道如何正确地执行,请随意分享。如前所述,问题是您正在对spring管理的bean调用一个方法。每次调用
monitor
时,它都会安排一个新任务。每个测试添加一个。你不需要遗嘱执行人。我认为您的
FMonitor
稍有错误。您应该将
WatchService
存储为实例变量,并添加一个用
@PreDestroy
注释的方法。测试完成后,调用此方法清理观察者。非常感谢。这有帮助。有一个拼写错误,您在测试类的@After中构造do时忘记了do的“do{/code/}”。同样对于assertEquals,我将简单的“output”改为“output.toString()”。如果有人试图运行你的示例,那么没有遗漏的操作。它只是一个空的while循环,将在执行器终止时等待。
@Service
public class FMonitor {

    private final WatchService watchService = FileSystems.getDefault.newWatchService();


    @Async("fMonitor")
    public void monitor() {
        Path path = Paths.get("C:\\dir");
        try {
            path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
            WatchKey key;
            while ((key = watchService.take()) != null) {
                for (WatchEvent<?> event: key.pollEvents()) {
                    String filename = event.context().toString();
                    if (filename.endsWith(".done")) {
                        processFile(filename.substring(0, filename.lastIndexOf('.')));
                    }
                }
                key.reset();
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void processFile(String filename) {
        System.out.println("Found " + filename);
    }

    @PreDestroy
    public void shutdown() {
      try {
        watchService.close();
      } catch (IOException ex) {}
    }
}
public class BunchOfTests {

    @Rule
    public OutputCaptureRule output = new OutputCaptureRule();
    
    private ExecutorService executor = Executors.newSingleThreadExecutor();

    private final FMonitor fm = new FMonitor();

    @After
    public void cleanUp() throws Exception {
      fm.shutdown();
      executor.shutdown();
      while (!executor.awaitTermination(100, TimeUnit.MICROSECONDS));
    }

    @Test
    public void test1() throws InterruptedException, IOException {
        executor.submit(() -> fm.monitor());

        Thread.sleep(3000);
        File file1 = new File("C:\\dir\\test1.done");
        File file2 = new File("C:\\dir\\test2.done");
        File file3 = new File("C:\\dir\\test3.done");
        file1.createNewFile();
        file2.createNewFile();
        file3.createNewFile();

        Thread.sleep(3000);
        file1.delete();
        file2.delete();
        file3.delete();
        Assert.assertEquals("Found test1\r\n" + "Found test2\r\n" + "Found test3\r\n", output.toString());
    }

    @Test
    public void test2() throws InterruptedException, IOException {
        executor.submit(() -> fm.monitor());

        Thread.sleep(3000);
        File file1 = new File("C:\\dir\\test1.done");
        File file2 = new File("C:\\dir\\test2.txt");
        File file3 = new File("C:\\dir\\test3.done");
        file1.createNewFile();
        file2.createNewFile();
        file3.createNewFile();

        Thread.sleep(3000);
        file1.delete();
        file2.delete();
        file3.delete();
        Assert.assertEquals("Found test1\r\n" + "Found test3\r\n", output.toString());
    }

    @Test
    public void test3() throws InterruptedException, IOException {
        executor.submit(() -> fm.monitor());

        Thread.sleep(3000);

        File file = new File("C:\\dir\\test.done");
        file.createNewFile();

        Thread.sleep(3000);
        file.delete();
        Assert.assertEquals("Found test\r\n", output.toString());
    }
}