繁体   English   中英

如何测试启动可能由于操作系统而失败的新线程的方法

[英]how to test method that starts new thread which may fail due to operating system

我有一个基于Spring Boot构建的应用程序。 有一个简单的控制器,带有创建新线程并启动它的方法。 但是,可运行程序会执行Unix命令(nc)(用于此过程的ProcessBuilder)。 因此,当我在Windows上运行它时,会从启动线程中获取异常。 确实不能运行unix程序。 现在,我想为此控制器编写一个测试,但是我想知道这是否可行和合理。 尽管我不知道如何完成操作,但我只是在考虑更改可运行任务的行为以进行测试。 感谢您提供的帮助和其他针对此案例的想法/解决方案。

控制器:

@Controller
public class TaskController {

    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat(task-%d").build();

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {
        Runnable runnable= new Task();       
        threadFactory.newThread(runnable).start();  
        return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
}

任务:

public class Task implements Runnable {

    @Override
    public void run() {
        // start unix process
    }
}

应用类别:

@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

整合测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest("server.port=0")
@DirtiesContext
public class ApplicationTest {

@Value("${local.server.port}")
private int port;

@Test
public void shouldStartTask() throws Exception {
    // when
    ResponseEntity<String> entity = new TestRestTemplate().getForEntity("http://localhost:" + this.port + "/startTask", String.class);

    // then
    assertThat(entity.getStatusCode()).isSameAs(HttpStatus.ACCEPTED);
}

}

如果从控制器逻辑中提取应用程序的处理逻辑(使用线程执行操作),并将处理逻辑放置在控制器委托的单独服务层中 ,则可能会更容易测试程序。 通过提供访问其当前状态而不只是执行操作的方法,将服务层设计为具有易于进行单元测试的API。 使用依赖注入将控制器连接到服务层。

因此,如下所示:

 public interface Service
 {

      // Sets this.wasTaskStarted() == true
      void startTask();

      boolean wasTaskStarted();

      void awaitCompletionOfTask();
 }

 @Controller
 public class TaskController {
    private final Service service;

    @Autowired
    public TaskController(Service service) {
       this.service = service;
    }

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {
       service.startTask();
       return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
 }

 public ServiceImpl implements Service {
      private final ThreadFactor threadFactory = new ....;
      private Thread taskTread;

      @Override
      public synchronized void startTask() {
         if (taskTread == null) {
            taskTread = threadFactory.newThread(new Task());
            taskTread.start();
            notifyAll();
         }
         // else already started
      }

      @Override
      public synchronized boolean wasTaskStarted() {
         return taskTread != null;
      }

      @Override
      public synchronized void awaitCompletionOfTask() {
         while (taskTread == null) {
            wait();
         }
         taskTread.join();
      }
 }

要测试您的控制器启动任务,只需在调用TaskController.startTask()之后测试Service.wasTaskStarted()是否为true

您还必须测试服务层:

 public class ServiceImplTest
 {

    @Test
    public void testStartTask() {
       final ServiceImpl service = new ServiceImpl(....);
       service.startTask();
       assert(service.wasTastStarted());
    }
    @Test
    public void testRunTask() {
       final ServiceImpl service = new ServiceImpl(....);
       service.startTask();
       service.awaitCompletionOfTask();
       // Add assertions here to test that the task did what it ought to do
    }
 }

谢谢你的建议。 您刚刚打开我的胸,我对设计进行了一些更改。 我从集成测试中辞职了。 从业务角度来看,我不需要检查任务是否已经开始甚至完成。 现在看起来如下:

控制器:

@Controller
public class TaskController {

    private ThreadService threadService;

    @Autowired
     public TaskController (ThreadService threadService) {
        this.threadService= threadService;        
    }

    @RequestMapping(value = "/startTask")
    public @ResponseBody ResponseEntity<String> startTask() {    
        // some conditions here which I would like to test 
        threadService.startNewThread(new Task());
        return new ResponseEntity<String>("Task started", HttpStatus.ACCEPTED);  
    }
}

任务:

public class Task implements Runnable {

    @Override
    public void run() {
        // start unix process
    }
}

线程服务:

@Component
public class ThreadService {

    ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("task-%d").build();

    public void startNewThread(Runnnable task) {
        threadFactory.newThread(task).start();
    }

}

然后我决定对我的控制器进行单元测试,并使用Mockito对ThreadService进行测试:

@RunWith(MockitoJUnitRunner.class)
public class TaskControllerTest {

    @Mock
    ThreadService threadService;

    @InjectMocks
    private TaskController objectUnderTest;

    @Test
    public void shouldStartTask() throws FileNotFoundException {
        // when
        ResponseEntity<String> response = objectUnderTest.startTask();

        // then
        assertThat(response.getStatusCode()).isSameAs(HttpStatus.ACCEPTED);
        // more assertions
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM