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
9 év 12 hét
10 év 1 hét
10 év 5 hét
10 év 23 hét
11 év 25 hét
11 év 30 hét
11 év 30 hét
11 év 32 hét
11 év 42 hét
12 év 12 hét