Upload
ndrssmn
View
137
Download
1
Embed Size (px)
DESCRIPTION
Viele performante und gut skalierbare Architekturen setzen auf asynchrone Verarbeitung. Das Testen des asynchronen Codes stellt Entwickler allerdings vor neue Herausforderungen. Dieser Vortrag bietet Orientierung für einige typischen Fragestellungen. Am Beispiel von NodeJS und Mocha wird gezeigt, wie das Testen beim Einsatz einer nicht-blockierenden Event Loop funktioniert. Anschließend illustriert der Vortrag, wie asynchroner Code mit JUnit auf der JVM – einer klassischen Multithreading-Plattform – getestet werden kann. Insbesondere wird darauf eingegangen, welche Synchronisationsmechanismen genutzt werden können und wie Race Conditions durch Unit Tests aufgedeckt werden können. Code unter: https://github.com/andreassimon/talk-asynchronen-code-testen
Citation preview
AsynchronenCode testen
@ndrssmnAndreas Simon
Synchron
Übertragungszeit
War
teze
it
Bearbeitungszeit
War
teze
itAnt
wor
tzei
t
AsynchronW
arte
zeit
Ant
wor
tzei
t
FehlertoleranzVerfügbarkeitParallelisierungPerformance
Event-Driven Architecture
Listening
Callback
Synchronisation
TIMEOUT
Listening in JUnit
@Test public void
should_reply_with_Fibonacci_numbers() throws Exception { // Arrange NotificationTrace<Integer> trace = new NotificationTrace<>(TIMEOUT); String replyQueue = channel.queueDeclare().getQueue(); FibonacciCalculator.create(connection.createChannel());
new IntegerConsumer( connection.createChannel(), trace::append) .consumeQueue(replyQueue);
// Act publishNumbers(MIN, MAX, replyQueue);
// Assert trace.containsNotification(equalTo(FIB_MIN)); trace.containsNotification(equalTo(FIB_MAX)); }
Listening in Mocha
describe('AMQP Fibonacci service', function() { it('calculates fib(' + MIN + ')', function(done) { connection.on('ready', function () { connection.queue('my-queue', function(q) { q.subscribe(function (message) { try { // Assert message.data.toString().should.eql(FIB_MIN); done(); } catch(e) { done(e); } });
// Act connection.publish( 'calculate-fibonacci', MIN, { replyTo: 'my-queue'} ); }); }); });
Sampling
POST localhost/
CREATED Location: localhost/30
TIMEOUT
GET localhost/30
NOT FOUND
GET localhost/30
NOT FOUND
GET localhost/30
OK :: 832040
@Test public void calculates_fib_30() throws Exception { // Act connection = POST("http://localhost:3000/", "30"); fibLocation = connection.getHeaderField("Location");
// Assert Probe probe = responseTo( fibLocation, equalTo(Integer.toString(FIB_30)) ); new Poller(TIMEOUT, POLL_DELAY).check(probe); }
Sampling in JUnit
public class Poller { […] public void check(Probe probe) { […] while (!probe.isSatisfied()) { […] Thread.sleep(pollDelayMillis); probe.sample(); } }}
public interface Probe { void sample(); boolean isSatisfied(); void describeAcceptanceCriteriaTo(Description d); void describeFailureTo(Description d);}
describe('Fibonacci server', function() { it('should calculate fib(20)', function(done) { var req = http.request(POST_fib, function(res) { res.setEncoding('utf8'); res.statusCode.should.eql(201); res.headers.location.should.be.ok; pollGET(res.headers.location, done); }).on('error', done);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); req.write('n=20\n'); req.end(); });
Sampling in Mocha
function pollGET(url, done) { http.get(url, function(res) { if(200 != res.statusCode) { setTimeout(pollGET, POLL_DELAY, url, done); } res.on('data', function (chunk) { chunk.toString().should.eql('6765'); done(); }); }).on('error', done);}
Sampling in Mocha
Test the test @Test public void is_thread_safe() throws Exception { latch = startStressing(STRESSING_THREADS, () -> { for (int i = 0; i < ITERATIONS; i++) { trace.append("NOT-WANTED"); Thread.sleep(SLEEPTIME); } latch.countDown(); }); scheduler.schedule( () -> trace.append("WANTED"), 100, TimeUnit.MILLISECONDS );
trace.containsNotification(equalTo("WANTED")); latch.await(); assertThat( trace.getAppendCount(), is(equalTo((long) STRESSING_THREADS * ITERATIONS + 1)) ); }
Thread-sicher implementierenpublic class NotificationTrace<T> {
public void append(T message) { synchronized (traceLock) { trace.add(message); traceLock.notifyAll(); } }
public void containsNotification(Matcher<? super T> criteria) throws AssertionError, InterruptedException { Timeout timeout = new Timeout(timeoutMs);
synchronized (traceLock) { stream = new NotificationStream<>(trace, criteria); while (!stream.hasMatched()) { if (timeout.hasTimedOut()) { throw new AssertionError(); } timeout.waitOn(traceLock); } } }
Aufräumen
@After public void tearDown() throws InterruptedException { executorService.shutdownNow(); scheduler.shutdownNow(); }
Fazit
Listening vs. Sampling
Synchronisierungsmechanismen kapseln (und durch Unit-Tests validieren)
Brian Goetz: "Java Concurrency in Practice"
Nat Pryce, Steve Freeman: "Growing Object-Oriented Software"
https://github.com/andreassimon/talk-asynchronen-code-testen
Quality in Agile.
QuagilisAndreas Simon
Lazarettstr. 948147 Münster
Fon +49 (0) 251 - 590 491 55-0Fax +49 (0) 251 - 590 491 55-9
[email protected]://www.quagilis.de