У меня есть несколько классов, которые реализуют некоторый интерфейс. Интерфейс имеет контракт, что должны синхронизироваться некоторые методы, и некоторые не должны, и я хочу проверить что контракт через модульные тесты на все реализации. Методы должны использовать синхронизируемое ключевое слово или быть соединены this
- очень похожий на synchronizedCollection () обертка. Это означает, что я должен быть в состоянии наблюдать его внешне.
Для продолжения примера Collections.synchronizedCollection (), если у меня есть один итератор вызова потока () я должен все еще быть в состоянии войти в методы, любят, добавляют () с другим потоком, потому что итератор () не должен делать никакой блокировки. С другой стороны, я должен быть в состоянии синхронизироваться на наборе внешне и видеть, что другой поток блоки на добавляет ().
Существует ли хороший способ протестировать это, метод синхронизируется в тесте JUnit? Я хочу избежать длинных операторов сна.
Большое спасибо Цвею Штайнену за описание подхода, который я использовал. В примере кода, над которым я работал, есть несколько проблем, поэтому я подумал, что стоит опубликовать свои выводы здесь.
Вот код теста синхронизации как признак Scala:
trait SynchronizedTestTrait
{
val classUnderTest: AnyRef
class Gate
{
val latch = new java.util.concurrent.CountDownLatch(1)
def open()
{
this.latch.countDown
}
def await()
{
this.latch.await
}
}
def nanoTime(code: => Unit) =
{
val before = System.nanoTime
code
val after = System.nanoTime
after - before
}
def assertSynchronized(code: => Unit)
{
this.assertThreadSafety(threadSafe = true, millisTimeout = 10L)(code)
}
def assertNotSynchronized(code: => Unit)
{
this.assertThreadSafety(threadSafe = false, millisTimeout = 60L * 1000L)(code)
}
def assertThreadSafety(threadSafe: Boolean, millisTimeout: Long)(code: => Unit)
{
def spawn(code: => Unit) =
{
val result = new Thread
{
override def run = code
}
result.start()
result
}
val gate = new Gate
val lockHolderThread = spawn
{
this.classUnderTest.synchronized
{
// Don't let the other thread start until we've got the lock
gate.open()
// Hold the lock until interruption
try
{
Thread.sleep(java.lang.Long.MAX_VALUE)
}
catch
{
case ignore: InterruptedException => return;
}
}
}
val measuredNanoTime = nanoTime
{
// Don't start until the other thread is synchronized on classUnderTest
gate.await()
spawn(code).join(millisTimeout, 0)
}
val nanoTimeout = millisTimeout * 1000L * 1000L
Assert.assertEquals(
"Measured " + measuredNanoTime + " ns but timeout was " + nanoTimeout + " ns.",
threadSafe,
measuredNanoTime > nanoTimeout)
lockHolderThread.interrupt
lockHolderThread.join
}
}
Теперь предположим, что мы хотим протестировать простой класс:
class MySynchronized
{
def synch = this.synchronized{}
def unsynch = {}
}
Тест выглядит так:
class MySynchronizedTest extends SynchronizedTestTrait
{
val classUnderTest = new MySynchronized
@Test
def synch_is_synchronized
{
this.assertSynchronized
{
this.classUnderTest.synch
}
}
@Test
def unsynch_not_synchronized
{
this.assertNotSynchronized
{
this.classUnderTest.unsynch
}
}
}
Используя отражение, получите объект метода метода и вызовите для него toString (). Ключевое слово synchronized должно появиться в выводе toString ().
Если вы просто хотите проверить, есть ли у метода модификатор synchronized
, помимо очевидного (глядя на исходный код / документацию Javadoc), вы также можете использовать отражение.
Modifier.isSynchronized(method.getModifiers())
Более общий вопрос тестирования, гарантирует ли метод правильную синхронизацию во всех сценариях параллелизма, вероятно, будет неразрешимой проблемой.
Все это ужасные идеи, но вы могли бы сделать это ...
// Substitute this LOCK with your monitor (could be you object you are
// testing etc.)
final Object LOCK = new Object();
Thread locker = new Thread() {
@Override
public void run() {
synchronized (LOCK) {
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException e) {
System.out.println("Interrupted.");
return;
}
}
}
};
locker.start();
Thread attempt = new Thread() {
@Override
public void run() {
// Do your test.
}
};
attempt.start();
try {
long longEnough = 3000 * 1000;// It's in nano seconds
long before = System.nanoTime();
attempt.join(longEnough);
long after = System.nanoTime();
if (after - before < longEnough) {
throw new AssertionError("FAIL");
} else {
System.out.println("PASS");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
locker.interrupt();
Если вы знаете, что методы аргументов всегда вызываются в любой реализации, вы можете передать фиктивный объект, который маскируется под аргумент и вызывает holdLock ().
Примерно так:
class Mock implements Argument {
private final Object LOCK;
private final Argument real;
public Mock(Object obj, Argument real){
this.LOCK=obj;
this.real = real;
}
@Overrides
public void something(){
System.out.println("held:"+Thread.holdsLock(LOCK));
this.real.something();
}
Затем подождите, пока класс вызовет something () для аргумента.