- John naplója
- HaT blog
- Schneier on Security
- Loloke
- Zee
- Lolokene (már megint titkos)
- PET Portál és Blog
- Bakai Balázs Java-közeli élményei
A lenti EasyMock (3.0) tesztben átadunk az executornak egy Runnable példányt. Ez egy mock objektum, és talán azt várnánk, hogy a verify() majd jelzi, hogy az executor (egy másik szálban) engedély nélkül meghívta a mock objektum run() metódusát, azaz hibával elszáll a teszt.
import static org.junit.Assert.assertTrue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.easymock.EasyMock;
import org.junit.Test;
public class EasyMockTest {
@Test
public void testEasyMock() throws Exception {
final Runnable runnable = EasyMock.createMock(Runnable.class);
EasyMock.makeThreadSafe(runnable, true); // default true
EasyMock.replay(runnable);
runInNewThread(runnable);
EasyMock.verify(runnable); // should fail
}
private void runInNewThread(final Runnable runnable)
throws InterruptedException {
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown();
final boolean terminated = executor.awaitTermination(2,
TimeUnit.SECONDS);
assertTrue("terminated", terminated);
}
}
Nem ez történik, a teszt hiba nélkül lefut, a verify() nem jelez hibát. A konzolon viszont megjelenik a hibaüzenet:
Exception in thread "pool-1-thread-1" java.lang.AssertionError: Unexpected method call run(): at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:45) at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:73) at $Proxy4.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:619)
JMock-kal (2.5.1) ugyanez a helyzet:
import static org.junit.Assert.assertTrue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(JMock.class)
public class JmockTest {
private Mockery mockery;
@Before
public void setUp() {
mockery = new Mockery();
}
@Test // should fail
public void testJmock() throws Exception {
final Runnable runnable = mockery.mock(Runnable.class);
runInNewThread(runnable);
}
...
}
A teszt sikeresen lefut, a hibaüzenet itt is csak a hibakonzolon jelenik meg.
Mockitót (1.8.5) használva jobbak az eredmények:
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
public class MockitoTest {
@Test
public void testMociko() throws Exception {
final Runnable runnable = mock(Runnable.class);
runInNewThread(runnable);
try {
verify(runnable, never()).run(); // failed
fail("verify");
} catch (final AssertionError expected) {
}
}
...
}
A verify() hívás hibát dob, ahogy vártuk. A konzolon pedig nem jelenik meg semmi, mert mock() metódus úgynevezett nice mock objektumokat készít, amelyek csak rögzítik a meghívott metódusaikat és sosem dobnak hibát.
Az EasyMock-hoz egy nem túl szép workaround saját ThreadFactory használata a kivételt elkapó UncaughtExceptionHandler-rel, majd a teszt végén az UncaughtExceptionHandler ellenőrzése.
import static org.junit.Assert.assertTrue;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.easymock.EasyMock;
import org.junit.Test;
public class EasyMockExceptionHandlerTest {
@Test
public void testEasyMockWithUncaughtExceptionHandler() throws Exception {
final Runnable runnable = EasyMock.createMock(Runnable.class);
EasyMock.makeThreadSafe(runnable, true); // default true
EasyMock.replay(runnable);
final AtomicBoolean threadError = new AtomicBoolean(false);
final ExecutorService executor = createExecutor(threadError);
executor.execute(runnable);
executor.shutdown();
final boolean terminated = executor.awaitTermination(2,
TimeUnit.SECONDS);
assertTrue("terminated", terminated);
EasyMock.verify(runnable);
assertTrue("thread error", threadError.get());
}
private ExecutorService createExecutor(final AtomicBoolean threadError) {
final ErrorFlagUncaughtExceptionHandler uncaughtExceptionHandler
= new ErrorFlagUncaughtExceptionHandler(threadError);
final ThreadFactory threadFactory = new ThreadFactory() {
final ThreadFactory defaultThreadFactory = Executors
.defaultThreadFactory();
public Thread newThread(final Runnable runnable) {
final Thread thread = defaultThreadFactory.newThread(runnable);
Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
};
final ExecutorService executor = Executors
.newSingleThreadExecutor(threadFactory);
return executor;
}
private class ErrorFlagUncaughtExceptionHandler implements
UncaughtExceptionHandler {
private final AtomicBoolean threadError;
public ErrorFlagUncaughtExceptionHandler(final AtomicBoolean threadError) {
this.threadError = threadError;
}
public void uncaughtException(final Thread thread,
final Throwable throwable) {
threadError.set(true);
}
}
}
JMock-hoz talán a 2.6-os vagy 2.7-es verzióban lesz erre megoldás, a jelenlegi dokumentáció alapján nem igazán támogatott a JMock mock objektumainak többszálú használata. Lásd még: JMOCK-213,
JMOCK-183.
EasyMock: During the replay phase, mocks are by default thread-safe.
Ugyanez a helyzet a Mockitónál is: You can let multiple threads call methods on a shared mock to test in concurrent conditions.
Az igazi megoldást azonban nem mock-olásra használt osztálykönyvtárban kellene implementálni, hanem a tesztelt kódban, hogy a más szálakon keletkező hibákról a hívó értesüljön, valamint a tesztünk is ezen az úton tudja meg, hogy hiba történt. Az EasyMock levelezőlistáján olvasható erről egy levél:
„Let's say the exception would be thrown by a real implementation, and *not* by EasyMock. How would your application know that the calculation went wrong?”
Erre használható például az ExecutorService:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.easymock.EasyMock;
import org.junit.Test;
public class ExecutorServiceTest {
@Test(expected = ExecutionException.class)
public void testEasyMockWithExecutorService() throws Exception {
final Runnable runnable = EasyMock.createMock(Runnable.class);
EasyMock.makeThreadSafe(runnable, true); // default true
EasyMock.replay(runnable);
final ExecutorService executorService = Executors
.newSingleThreadExecutor();
final Future> future = executorService.submit(runnable);
future.get();
}
}
Legutóbbi hozzászólások
7 hét 10 óra
8 hét 1 nap
8 hét 1 nap
8 hét 1 nap
8 hét 6 nap
11 hét 6 nap
12 hét 5 nap
20 hét 6 nap
29 hét 6 nap
30 hét 6 nap