Asynchronen 
Code testen 
@ndrssmn 
Andreas Simon
Synchron 
Übertragungszeit 
Wartezeit 
Bearbeitungszeit 
Wartezeit 
Antwortzeit
Asynchron 
Antwortzeit 
Wartezeit
Fehlertoleranz 
Verfügbarkeit 
Parallelisierung 
Performance 
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 
GET localhost/30 
NOT FOUND 
GET localhost/30 
NOT FOUND 
GET localhost/30 
OK :: 832040 
TIMEOUT
Sampling in JUnit 
@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); 
}
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); 
}
Sampling in Mocha 
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=20n'); 
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); 
}
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 implementieren 
public 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
Quagilis 
Andreas Simon 
Lazarettstr. 9 
48147 Münster 
Fon +49 (0) 251 - 590 491 55-0 
Fax +49 (0) 251 - 590 491 55-9 
a.simon@quagilis.de 
http://www.quagilis.de 
Quality in Agile.

Asynchronen Code testen

  • 1.
    Asynchronen Code testen @ndrssmn Andreas Simon
  • 2.
    Synchron Übertragungszeit Wartezeit Bearbeitungszeit Wartezeit Antwortzeit
  • 3.
  • 4.
    Fehlertoleranz Verfügbarkeit Parallelisierung Performance Event-Driven Architecture
  • 5.
  • 7.
  • 8.
    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)); }
  • 9.
    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'} ); }); }); });
  • 10.
    Sampling POST localhost/ CREATED Location: localhost/30 GET localhost/30 NOT FOUND GET localhost/30 NOT FOUND GET localhost/30 OK :: 832040 TIMEOUT
  • 11.
    Sampling in JUnit @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); }
  • 12.
    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); }
  • 13.
    Sampling in Mocha 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=20n'); req.end(); });
  • 14.
    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); }
  • 15.
    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)) ); }
  • 16.
    Thread-sicher implementieren publicclass 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); } } }
  • 17.
    Aufräumen @After publicvoid tearDown() throws InterruptedException { executorService.shutdownNow(); scheduler.shutdownNow(); }
  • 18.
    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
  • 19.
    Quagilis Andreas Simon Lazarettstr. 9 48147 Münster Fon +49 (0) 251 - 590 491 55-0 Fax +49 (0) 251 - 590 491 55-9 a.simon@quagilis.de http://www.quagilis.de Quality in Agile.