简体   繁体   English

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

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

I have an application built upon Spring Boot. 我有一个基于Spring Boot构建的应用程序。 There is simple controller with a method which creates new Thread and starts it. 有一个简单的控制器,带有创建新线程并启动它的方法。 However a runnable executes unix command (nc) (used ProcessBuilder for that). 但是,可运行程序会执行Unix命令(nc)(用于此过程的ProcessBuilder)。 Thus when I'm runnning it on the windows I get exceptions from started thread. 因此,当我在Windows上运行它时,会从启动线程中获取异常。 Indeed it can not run unix program. 确实不能运行unix程序。 Now I would like to write a test for this controller, but I'm wondering is it possible and reasonable. 现在,我想为此控制器编写一个测试,但是我想知道这是否可行和合理。 I was thinking about changing behaviour of runnable task just for testing, although I don't know how can it be done. 尽管我不知道如何完成操作,但我只是在考虑更改可运行任务的行为以进行测试。 Thanks for any help and other ideas/solutions for this case. 感谢您提供的帮助和其他针对此案例的想法/解决方案。

Controller: 控制器:

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

Task: 任务:

public class Task implements Runnable {

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

Application class: 应用类别:

@ComponentScan
@EnableAutoConfiguration
public class Application {

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

Integration Test: 整合测试:

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

} }

You might find it easier to test your program if you the extract the processing logic of your application (which does things using threads) from your controller logic, placing the processing logic in a separate service layer , which your controller delegates to. 如果从控制器逻辑中提取应用程序的处理逻辑(使用线程执行操作),并将处理逻辑放置在控制器委托的单独服务层中 ,则可能会更容易测试程序。 Design the service layer to have an API that is easy to unit test, by providing methods for accessing its current state, not just for performing actions. 通过提供访问其当前状态而不只是执行操作的方法,将服务层设计为具有易于进行单元测试的API。 Use dependency injection to connect your controller to your service layer. 使用依赖注入将控制器连接到服务层。

So, something like this: 因此,如下所示:

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

To test that your controller starts a task, you just need to test that Service.wasTaskStarted() is true after calling TaskController.startTask() . 要测试您的控制器启动任务,只需在调用TaskController.startTask()之后测试Service.wasTaskStarted()是否为true

You also have to test your service layer: 您还必须测试服务层:

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

Thanks for the suggestion. 谢谢你的建议。 You just opened my mind and I changed the design a bit. 您刚刚打开我的胸,我对设计进行了一些更改。 I resigned from an integration test. 我从集成测试中辞职了。 From business point of view, I don't need to check whether task has been started or even completed. 从业务角度来看,我不需要检查任务是否已经开始甚至完成。 Now it looks as follows: 现在看起来如下:

Controller: 控制器:

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

Task: 任务:

public class Task implements Runnable {

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

Thread service: 线程服务:

@Component
public class ThreadService {

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

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

}

And I decided to unit test my controller, stubbing ThreadService with mockito: 然后我决定对我的控制器进行单元测试,并使用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