How to wait for a thread that spawns it's own

2020-02-01 18:30发布

问题:

I'm trying to test a method that does it's work in a separate thread, simplified it's like this:

public void methodToTest()
{
    Thread thread = new Thread()
    {
        @Override
        public void run() {
            Clazz.i = 2;
        }
    };
    thread.start();
}

In my unit test I want to test that Clazz.i == 2, but I can't do this because I think that the assert is run before the thread changes the value. I thought of using another thread to test it and then use join to wait but it still doesn't work.

SSCCE:

@Test
public void sscce() throws InterruptedException
{
    Thread thread = new Thread()
    {
        @Override
        public void run() {
            methodToTest()
        }
    };
    thread.start();     
    thread.join();  
    AssertEquals(2, Clazz.i);
}

public static class Clazz
{
    public static int i = 0;
}

I think this is because the test main code creates a thread that is waiting (joined) to the 2nd thread, but the 2nd thread doesn't do the work, it creates another thread to do the work and then finishes, which continues the first thread, while the third thread does the Clazz.i = 2 after the assertion.

How can I make it so that the first thread waits for the thread that it starts as well as any threads that that thread starts?

回答1:

Without a reference to the thread created in methodToTest, you cannot, quite simply. Java provides no mechanism for finding "threads that were spawned during this particular time period" (and even if it did, it would arguably be an ugly mechanism to use).

As I see it, you have two choices:

  • Make methodToTest wait for the thread it spawns. Of course, if you explicitly want this to be an asynchronous action, then you can't very well do that.
  • Return the newly created thread from methodToTest, so that any callers can choose to wait for it if they so wish.

It may be noted that the second choice can be formulated in a few different ways. You could, for instance, return some abstract Future-like object rather than a thread, if you want to extend the liberty of methodToTest to use various ways of doing asynchronous work. You could perhaps also define some global task-pool that you enforce all your asynchronous tasks to run inside, and then wait for all tasks in the pool to finish before checking the assertion. Such a task pool could take the form of an ExecutorService, or a ThreadGroup, or any number of other forms. They all do the same thing in the end, but may be more or less suited to your environment -- the main point being that you have to explicitly give the caller access to the newly created thread, is some manner or another.



回答2:

Since your threads seems to be performing different operations, you can use CountDownLatch to solve your problem.

Declare a CountDownLatch in main thread and pass this latch object to other threads. use await() in main thread and decrement latch in other threads.

In Main thread: ( first thread)

CountDownLatch latch = new CountDownLatch(2);
/* Create Second thread and pass the latch. Pass the same latch from second 
   thread to third thread when you are creating third thread */
try {
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

Pass this latch to second and third threads and use countdown in these threads

In second and third threads,

try {
    // add your business logic i.e. run() method implementation
    latch.countDown();
} catch (InterruptedException e) {
    e.printStackTrace();
}

Have a look this article for better understanding.

ExecutorService invokeAll() API is other preferable solution.



回答3:

You can't unit-test functionality that the unit does not provide.

You're saying that you want to verify that methodToTest() eventually sets Clazz.i=2, but what does "eventually" mean? Your methodToTest() function does not provide its caller with any way to know when Clazz.i has been set. The reason you're having a hard time figuring out how to test the feature is because your module does not provide that feature.

This might be a good time for you to read up on Test Driven Development (TDD). That's where you write the tests first, and then you write code that makes the tests pass. Writing the tests first helps you to paint a clearer picture of whatever it is that you want the module to do.

It also has a side benefit: If you practice strict TDD (i.e., if you never write any module code except to make a test pass), then your test coverage will be 100%.

And, that leads to another side benefit: If you have 100% test coverage, then you can refactor without fear because if you break anything at all, your unit tests will tell you so.