【问题标题】:how to unit test a synchronized method?如何对同步方法进行单元测试?
【发布时间】:2014-11-17 06:47:32
【问题描述】:

说我有这样的方法:

synchronized void incrementIndex() {
      index++;
}

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

附言如果有帮助,我正在使用 Mockito 编写测试用例。

【问题讨论】:

  • 我不确定这是否可测试,因为您的 incrementIndex 方法是原子的。仅当至少有两个步骤必须一起执行时,同步才重要,例如两次写入、两次读取,或者最典型的一次读取和一次写入(经典的测试和设置问题)。任何测试如何捕获您的 incrementIndex() 方法未能同步?

标签: java unit-testing testing synchronized


【解决方案1】:

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

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

【讨论】:

  • 在 executor.awaitTermination() 之前,你应该先调用 executor.shutdown() 。否则,你可能会等待很长时间,因为 awaitTermination 实际上并没有关闭你的 executor。 (stackoverflow.com/questions/18425026/…)
  • 如果方法只包含一个同步块,所以它没有同步作为修饰符怎么办?除了断言“预期行为”之外,还有其他方法可以测试吗?
  • @ToniNagy 我认为断言行为将是您唯一的选择。因为无论如何你总是首先测试代码的正确性,所以我会默认测试预期的行为而不是同步修饰符的存在。
【解决方案2】:

CandiedOrange 在他对您的问题的评论中是正确的。换句话说,鉴于您提到的方法,您不应该担心 threadA 在同一时刻调用该方法 threadB 因为两个调用都写入索引。如果是这样的:

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

threaA 调用该方法,在第一条语句中增加 index,然后尝试在第二条语句中读取 index 的值,此时 threadB 可能已经在 threadA 读取并打印之前调用了该方法并增加了 index它。这就是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!

【讨论】:

    【解决方案3】:

    是。我++。原子?

    没有

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

    但是测试很难。

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

    可以测试一个函数是否仅通过其副作用进行同步。有一个可测试的模式来组织代码,这样您就可以依赖注入同步而不是使用 Jave 内在函数,但是如果所有这些都是您最初的问题,那么我将依赖视觉检查和明显的正确性。

    猜你喜欢
    • 1970-01-01
    • 2016-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-15
    • 1970-01-01
    • 2020-05-21
    • 2021-07-29
    相关资源
    最近更新 更多