如何对同步方法进行单元测试?

时间:2014-11-17 06:47:32

标签: java unit-testing testing synchronized

说我有这样的方法:

synchronized void incrementIndex() {
      index++;
}

我想对此方法进行单元测试,以查看如果多个线程同时尝试增加索引,是否正确设置了索引的最终值。假设我不知道"同步"方法声明中的关键字(我只知道方法的合同),我该如何进行测试?

P.S。如果有帮助,我正在使用Mockito编写测试用例。

3 个答案:

答案 0 :(得分:4)

您可以通过让多个线程执行该方法然后断言结果是您所期望的来测试它。我怀疑这将是多么有效和可靠。众所周知,多线程代码难以测试,主要归结为精心设计。我肯定会建议添加测试,声明您通过synchronized实现的方法实际上具有synchronized修饰符。请参阅以下两种方法的示例:

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class SyncTest {
  private final static int NUM_THREADS = 10;
  private final static int NUM_ITERATIONS = 1000;

  @Test
  public void testSynchronized() throws InterruptedException {
    // This test will likely perform differently on different platforms.
    ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
    final Counter sync = new Counter();
    final Counter notSync = new Counter();

    for (int i = 0; i < NUM_THREADS; i++) {
      executor.submit(new Runnable() {
        @Override
        public void run() {
          for (int i = 0; i < NUM_ITERATIONS; i++) {
            sync.incSync();
            notSync.inc();
          }
        }
      });
    }

    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
    assertThat(sync.getValue(), is(NUM_THREADS * NUM_ITERATIONS));
    assertThat(notSync.getValue(), is(not(NUM_THREADS * NUM_ITERATIONS)));
  }

  @Test
  public void methodIncSyncHasSynchronizedModifier() throws Exception {
    Method m = Counter.class.getMethod("incSync");
    assertThat(Modifier.isSynchronized(m.getModifiers()), is(true)); 
  }

  private static class Counter {
    private int value = 0;

    public synchronized void incSync() {
      value++;
    }

    public void inc() {
      value++;
    }

    public int getValue() {
      return value;
    }
  }
}

答案 1 :(得分:3)

CandiedOrange对你的问题的评论是正确的。换句话说,根据您提到的方法,您不必担心threadA在同一时刻调用该方法,因为两个调用都写入索引。它有类似的东西:

void incrementIndex() {
     index++;
     System.out.println(index); // threadB might have written to index
                                // before this statement is executed in threadA
}

threaA调用该方法,在第一个语句中递增索引,然后尝试在第二个语句中读取index的值,此时threadB可能已经调用了该方法并在threadA读取并打印之前增加了索引它。这是避免这种情况所必需synchronized的地方。

现在,如果您仍想测试同步,并且您可以访问方法代码(或者您可以执行类似的原型),您可以考虑以下内容,说明多线程如何使用同步方法:

public void theMethod(long value, String caller) {
    System.out.println("thread" + caller + " is calling...");
    System.out.println("thread" + caller + " is going to sleep...");

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("thread" + caller + " woke up!");
}

这应输出:

threadA is calling...
threadA is going to sleep...
threadA woke up!
threadB is calling...
threadB is going to sleep...
threadB woke up!

如果没有synchronized关键字,则输出为:

threadA is calling...
threadA is going to sleep...
threadB is calling...
threadB is going to sleep...
threadA woke up!
threadB woke up!

答案 2 :(得分:-3)

时。我++。原子?

没有

如果您关心程序的正确性,那么同步是合理的。

但测试很难。

视觉检查告诉我们,非原子增量操作受到保护并且是原子的,并且据我们所知,一切都很好,但我们对系统其余部分的状态一无所知。

可以测试函数仅通过其副作用进行同步。有一个可测试的模式可以对代码进行整理,使得依赖注入同步而不是使用Jave内在,但如果只有你原来的问题,那么我将依赖于视觉检查和明显的正确性。