简体   繁体   English

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

[英]how to unit test a synchronized method?

Say I have such a method: 说我有这样一种方法:

synchronized void incrementIndex() {
      index++;
}

I want to unit test this method to see whether index's final value is set correctly if multiple threads simultaneously attempt to increment index. 我想对该方法进行单元测试,以查看如果多个线程同时尝试递增索引时索引的最终值是否设置正确。 Assuming I don't know about the "synchronized" keyword in the method's declaration (and I only know the contract of the method), how can I do the test? 假设我不知道方法声明中的“ synchronized”关键字(而且我只知道方法的约定),该如何进行测试?

ps I am using Mockito for writing test cases if it helps. PS:如果有帮助,我正在使用Mockito编写测试用例。

You could test this by having multiple threads execute the method and then asserting that the result is what you would expect. 您可以通过让多个线程执行该方法,然后断言结果是否符合您的预期来对此进行测试。 I have my doubts about how effective and reliable this would be. 我对这将是多么有效和可靠感到怀疑。 Multithreaded code is notoriously difficult to test and it mostly comes down to careful design. 众所周知,多线程代码很难测试,并且大多归结为精心设计。 I would definitely recommend adding tests that assert that the methods you expect to by synchronized actually have the synchronized modifier. 我绝对会建议添加一些测试,这些测试断言您期望通过同步进行的方法实际上具有synced修饰符。 See an example of both approaches below: 请参阅以下两种方法的示例:

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

CandiedOrange is correct in his comment to your question. CandiedOrange对您的问题的评论正确。 In other words, and given the method you mentioned, you should not worry about threadA calling the method at the same moment threadB is since both calls are writing to index. 换句话说,给定您提到的方法,您不必担心threadA在同一时刻调用方法的原因在于threadB是因为两个调用都写入索引。 Had it been something like: 如果是这样的话:

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

threaA calls the method, increments index in the first statement, then attempts to read the value of index in the second statement, by which time threadB might have already made the call to the method and incremented index before threadA reads it and prints it. threaA调用该方法,在第一条语句中递增索引,然后尝试读取第二条语句中的index值,此时线程B可能已经调用了该方法,并在线程A读取并打印它之前递增了索引。 This is where synchronized is necessary to avoid such a situation. 为了避免这种情况,必须在此处进行synchronized

Now, if you still want to test synchronization, and you have access to the method code (or maybe you can do a similar prototype), you may consider something like the following that illustrates how multithreading behaves with synchronized methods: 现在,如果您仍然想测试同步,并且可以访问方法代码(或者您可以做一个类似的原型),则可以考虑以下内容,这些内容说明了同步方法中多线程的行为:

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!");
}

This should output: 这应该输出:

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

Without the synchronized keyword, the output be: 如果没有synchronized关键字,则输出为:

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

Is. 是。 i++. 我++。 Atomic? 原子?

No 没有

So sync is justified if you care about the correctness of your program. 因此,如果您关心程序的正确性,那么同步是合理的。

But testing is hard. 但是测试很难。

Visual inspection tells us that the nonatomic increment ooperation is protected and made atomic and all is well as far as we know, but we're don't know anything about the state of the rest of the system. 目视检查告诉我们,非原子增量操作是受保护的并成为原子操作,据我们所知,一切都很好,但是我们对系统其余部分的状态一无所知。

It is possible to test that a function is sychronised only by its side effects. 可以测试某个功能是否仅因其副作用而同步。 There is a testable pattern that orgs the code such that you dependency inject the sync rather than using the Jave intrinsic, but if all there is is your original question then I would rely on visual inspection and obvious correctness. 有一个可测试的模式来组织代码,以便您依赖注入同步而不是使用Jave内在函数,但是如果所有都是您的原始问题,那么我将依靠视觉检查和明显的正确性。

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

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