Asynchronen Code testen

Preview:

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

a.simon@quagilis.dehttp://www.quagilis.de

Recommended