Nell’articolo Reactive Streams Publisher Test è stato utilizzato il test kit TCK fornito da reactive streams per la verifica della corretta implementazione di un Publisher
. Scopo del kit è sostanzialmente quello di consentire allo sviluppatore di accertarsi che l’implementazione di un componente sia conforme alle specifiche reactive streams, le quali sono espresse in un set di regole suddivise in quattro gruppi, uno per ciascuna interfaccia definita dalla specifica: Publisher
, Processor
, Subscriber
e Subscription
.
In questo articolo vediamo come utilizzare TCK per testare l’implementazione della SimpleSubscriber
introdotta nel precedente post Reactive Streams, e che riportiamo di seguito per comodità:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class SimpleSubscriber implements Subscriber<Integer> { private Subscription subscription; public void onSubscribe(Subscription subscription) { System.out.println( this.getClass().getSimpleName() + ": I'm subscribed now" ); this.subscription = subscription; subscription.request(1); } public void onNext(Integer number) { System.out.println( this.getClass().getSimpleName() + ": " + number ); subscription.request(1); } public void onError(Throwable t) { System.err.println( t ); } public void onComplete() { System.out.println( this.getClass().getSimpleName() + ": completed" ); } } |
Fortunatamente il gruppo di regole che la specifica dedica al Subscriber
sono notevolmente meno complicate di quelle associate al Publisher
, ma comunque devono essere verificate. A tale scopo il test kit mette a disposi due classi: SubscriberBlackboxVerification
e SubscriberWhiteboxVerification
.
Black Box Test
La prima è utilizzata per realizzare un Black Box testing, che è una strategia di testing che si applica quando non si ha conoscenza del comportamento interno del componente. Per contro questa classe copre solo un sottoinsieme delle regole e quindi non garantisce la completa conformità del componente alla specifica. Estendendo quindi la classe SubscriberBlackboxVerification
è possibile implementare il test case nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Test public class SimpleSubscriberBlackboxVerification extends SubscriberBlackboxVerification<Integer>{ protected SimpleSubscriberBlackboxVerification() { super(new TestEnvironment()); } public Subscriber<Integer> createSubscriber() { return new SimpleSubscriber(); } public Integer createElement(int element) { return new Integer(element); } public void triggerRequest(Subscriber<? super Integer> subscriber) { super.triggerRequest(subscriber); } } |
I metodi createSubscriber()
e createElement()
sono i soli metodi astratti definiti dalla classe di test e che quindi devono essere implementati nella classe di test SimpleSubscriberBlackboxVerification
. Il metodo triggerRequest()
non è astratto e, nel nostro caso, non sarebbe neppure necessario ridefinirlo. E’ utili però sapere che, se necessario, è possibile gestire esternamente alla chiamata al metodo request()
dell’oggetto Subscription
che, ricordiamolo, non sarebbe in altro modo accessibile trattandosi di un test di tipo Black Box.
White Box Test
La classe SubscriberWhiteboxVerification
, come anticipato, implementa un test kit più completo, ma richiede una interazione più complessa con il Subscriber
, attraverso un oggetto di tipo WhiteboxSubscriberProbe
. Si tratta, come suggerisce il nome stesso, di una sonda che deve essere invocata contestualmente all’invocazione dei metodi del Subscriber
: onSubscribe()
, onNext()
, onError()
e onComplete()
. Il codice sorgente della classe di test è quindi la seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
@Test public class SimpleSubscriberWhiteboxVerification extends SubscriberWhiteboxVerification<Integer>{ protected SimpleSubscriberWhiteboxVerification() { super( new TestEnvironment() ); } public Subscriber<Integer> createSubscriber(WhiteboxSubscriberProbe<Integer> probe) { return new SimpleSubscriber() { public void onSubscribe(Subscription subscription) { super.onSubscribe(subscription); probe.registerOnSubscribe( new SubscriberPuppet() { public void triggerRequest(long elements) { subscription.request( elements ); } public void signalCancel() { subscription.cancel(); } }); } public void onNext(Integer number) { super.onNext(number); probe.registerOnNext( number ); } public void onError(Throwable t) { super.onError(t); probe.registerOnError( t ); } public void onComplete() { super.onComplete(); probe.registerOnComplete(); } }; } public Integer createElement(int element) { return new Integer(element); } } |
Anche la classe astratta SubscriberWhiteboxVerification
richiede quindi la implementazione dei metodo createSubscriber()
e createElement()
. Il primo però è ben più complesso. In tale metodo infatti restituiamo, come al solito, un oggetto di tipo SimpleSubscriber
, ma andiamo anche a ridefinire i suoi metodo al fine di inviare i segnali al WhiteboxSubscriberProbe
. Infine la classe SubscriberPuppet
è utilizzata per intercettare l’invocazione dei metodi request()
e cancel()
sull’oggetto Subscription
, ma anche in questo caso, come visto nel test kit precedente, per la nostra implementazione è totalmente inutile.
Esecuzione dei Test
Per eseguire il test è sufficiente, in Eclipse, aprire il menù contestuale (tasto destro del mouse) su una delle due classi di test viste sopra e selezionare la voce Run As -> TestNG test. In entrambe i casi otteniamo gli stessi 3 fallimenti:
Sottoscrizioni Duplicate
Il primo errore sollevato dai test è associato alla regola 2.5 della specifica, la quale richiede che l’oggetto Subscriber
gestisca una sola sottoscrizione alla volta. Conseguentemente se il metodo onSubscribe()
è invocato mentre ha già una sottoscrizione attiva, deve rispondere chiudendo la sottoscrizione di input. Il codice del metodo diviene quindi:
1 2 3 4 5 6 7 8 9 |
public void onSubscribe(Subscription subscription) { if ( this.subscription != null ) { subscription.cancel(); } else { System.out.println( this.getClass().getSimpleName() + ": I'm subscribed now" ); this.subscription = subscription; subscription.request(1); } } |
Null Pointer Exception
Il secondo test fallito fa invece riferimento alla regola 2.13 della specifica, la quale richiede che nel caso in cui uno dei parametri utilizzati in tutti i metodi del Subscriber
sia nullo, il metodo deve sollevare una eccezione di NullPointerException
. In particolare i due errori segnalano un problema sui metodo onError()
e onNext()
.
Quondi, una volta introdotto il check sui parametri di input di tutti i metodo, la classe SimpleSubscriber
assume il seguente aspetto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public class SimpleSubscriber implements Subscriber<Integer> { private Subscription subscription; public void onSubscribe(Subscription subscription) { Objects.requireNonNull( subscription ); if ( this.subscription != null ) { subscription.cancel(); } else { System.out.println( this.getClass().getSimpleName() + ": I'm subscribed now" ); this.subscription = subscription; subscription.request(1); } } public void onNext(Integer number) { Objects.requireNonNull( number ); System.out.println( this.getClass().getSimpleName() + ": " + number ); subscription.request(1); } public void onError(Throwable t) { Objects.requireNonNull( t ); System.err.println( t ); } public void onComplete() { System.out.println( this.getClass().getSimpleName() + ": completed" ); } } |
Codice Sorgente
Il codice sorgente con l’esempio presentato è scaricabile qui react.