Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Java 8 brings power to testing!

Java 8 brings power to testing!

Java 8 without a doubt is most revolutionary release of last years. Many mechanisms and techniques, recently available only in languages like Groovy or Scala are now used by Java developers on a daily basis. However in order to benefit from them, sometimes it is required to radically change a mindset.

During my presentation I will show some tricks and examples how to take advantage of Java 8 in implementing more consistent and readable tests and how to rewrite existing testing tools to benefit even more.

Marcin Zajączkowski

January 30, 2015
Tweet

More Decks by Marcin Zajączkowski

Other Decks in Programming

Transcript

  1. Java 8 The most revolutionary Java version released in recent

    years Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  2. Java 8 The most revolutionary Java version released in recent

    years Focus on lambda expressions and method references Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  3. Java 8 The most revolutionary Java version released in recent

    years Focus on lambda expressions and method references Constructions known from Groovy or Scala become available (even if they look uglier) Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  4. Java 8 The most revolutionary Java version released in recent

    years Focus on lambda expressions and method references Constructions known from Groovy or Scala become available (even if they look uglier) How can it improve our testing code and testing tools? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  5. AssertJ - Fluent assertions for Java Continuation of FEST-Assert Large

    set of strongly typed assertions a s s e r t T h a t ( d a t e T o A s s e r t ) / / D a t e . i s A f t e r ( d a t e 1 ) . i s N o t B e t w e e n ( d a t e 2 , d a t e 3 ) ; a s s e r t T h a t ( l i s t T o A s s e r t ) / / L i s t , S e t , m a j o r i t y a l s o f o r I t e r a b l e . h a s S i z e ( 5 ) . c o n t a i n s ( " F o o " , " B a r " ) . d o e s N o t C o n t a i n ( " X X X " ) ; a s s e r t T h a t ( m a p T o A s s e r t ) / / M a p . h a s S i z e ( 7 ) . c o n t a i n s K e y ( 4 2 ) . c o n t a i n s E n t r y ( 1 3 , " F r i d a y " ) . d o e s N o t C o n t a i n V a l u e ( " F r e d d y " ) ; Additional modules for Guava, Joda-Time, Neo4j and Android Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  6. AssertJ - Java 8 edition A completely separate branch New

    method signatures New classes Support for new features j a v a . u t i l . O p t i o n a l Date and Time API (JSR 310) Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  7. Manual fields extraction (antipattern) c l a s s F

    e l l o w { p r i v a t e f i n a l S t r i n g n a m e ; p r i v a t e f i n a l R a c e r a c e ; ( . . . ) } c l a s s F e l l o w s h i p i m p l e m e n t s I t e r a b l e < F e l l o w > { p r i v a t e f i n a l L i s t < F e l l o w > f e l l o w s ; ( . . . ) } @ T e s t p u b l i c v o i d s h o u l d D o M a n u a l E x t r a c t i o n I n J a v a 7 ( ) { / / d o n ' t d o t h a t L i s t < S t r i n g > n a m e s = n e w A r r a y L i s t < > ( ) ; f o r ( F e l l o w f e l l o w : f e l l o w s h i p ) { n a m e s . a d d ( f e l l o w . g e t N a m e ( ) ) ; } a s s e r t T h a t ( n a m e s ) . c o n t a i n s ( " G a n d a l f " , " L e g o l a s " ) . d o e s N o t C o n t a i n ( " S a u r o n " ) ; } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  8. AssertJ - fields extraction c l a s s F

    e l l o w { p r i v a t e f i n a l S t r i n g n a m e ; p r i v a t e f i n a l R a c e r a c e ; ( . . . ) } c l a s s F e l l o w s h i p i m p l e m e n t s I t e r a b l e < F e l l o w > { p r i v a t e f i n a l L i s t < F e l l o w > f e l l o w s ; ( . . . ) } @ T e s t p u b l i c v o i d s h o u l d E x t r a c t F i e l d I n J a v a 7 ( ) { a s s e r t T h a t ( f e l l o w s h i p ) . e x t r a c t i n g ( " n a m e " ) . c o n t a i n s ( " G a n d a l f " , " L e g o l a s " ) / / O b j e c t . d o e s N o t C o n t a i n ( " S a u r o n " ) ; / / O b j e c t } Field name as String - error-prone and hard to refactor No strong typing Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  9. AssertJ - fields extraction - Java 8 @ T e

    s t p u b l i c v o i d s h o u l d E x t r a c t F i e l d W i t h M e t h o d R e f e r e n c e ( ) { a s s e r t T h a t ( f e l l o w s h i p ) . e x t r a c t i n g ( F e l l o w : : g e t N a m e ) . c o n t a i n s ( " G a n d a l f " , " L e g o l a s " ) / / S t r i n g . d o e s N o t C o n t a i n ( " S a u r o n " ) ; / / S t r i n g } Method reference - safer, easy to refactor Strong typing Preview in assertj-core-java8 1.0.0m1 Will be available in AssertJ 2.0 Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  10. Manual fields extraction - Java 8 (antipattern) Why not pure

    Java 8 Streams? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  11. Manual fields extraction - Java 8 (antipattern) Why not pure

    Java 8 Streams? @ T e s t p u b l i c v o i d s h o u l d D o M a n u a l E x t r a c t i o n I n J a v a 8 ( ) { / / d o n ' t d o t h a t L i s t < S t r i n g > n a m e s = S t r e a m S u p p o r t . s t r e a m ( f e l l o w s h i p . s p l i t e r a t o r ( ) , . m a p ( F e l l o w : : g e t N a m e ) . c o l l e c t ( C o l l e c t o r s . t o L i s t ( ) ) ; a s s e r t T h a t ( n a m e s ) . c o n t a i n s ( " G a n d a l f " , " L e g o l a s " ) . d o e s N o t C o n t a i n ( " S a u r o n " ) ; } Functional, but still bloated Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  12. Awaitility Smart asynchronous call testing Adaptable waiting for completion Finishes

    as quickly as condition is fulfilled No more s l e e p ( 5 0 0 0 ) in tests Separate support for Groovy and Scala traits and closures Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  13. Awaitility Communication with queue p u b l i c

    i n t e r f a c e M e s s a g e Q u e u e F a c a d e { v o i d s e n d P i n g ( ) ; i n t g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) ; } @ T e s t p u b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e P r o x y E d i t i o n ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l C a l l ( t o ( q u e u e F a c a d e ) . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) , e q u a l T o ( 1 ) ) } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  14. Awaitility Assertions & matchers Clear and verbose error message: C

    o n d i t i o n T i m e o u t E x c e p t i o n : M e s s a g e Q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) e x p e c t e d < 1 > b u t w a s < 0 > w i t h i n 5 s e c o n d s . Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  15. Awaitility Assertions & matchers Clear and verbose error message: C

    o n d i t i o n T i m e o u t E x c e p t i o n : M e s s a g e Q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) e x p e c t e d < 1 > b u t w a s < 0 > w i t h i n 5 s e c o n d s . Drawbacks Not so easy with complicated matching expression I personally prefer AssertJ over Hamcrest Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  16. Awaitility with Java 8 Java 7: @ T e s

    t p u b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l ( g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) , e q u a l T o ( 1 ) ) ; } p r i v a t e C a l l a b l e < I n t e g e r > g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) { r e t u r n n e w C a l l a b l e < I n t e g e r > ( ) { p u b l i c I n t e g e r c a l l ( ) t h r o w s E x c e p t i o n { r e t u r n q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) ; } } ; } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  17. Awaitility with Java 8 Java 8 - lambda expression @

    T e s t p u b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e 8 ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l ( ( ) - > q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) , e q u a l T o ( 1 ) ) ; } Java 8 - method reference a w a i t ( ) . u n t i l ( q u e u e F a c a d e : : g e t N u m b e r O f R e c e i v e d P a c k e t s , e q u a l T o ( 1 ) ) ; Question - do I need Hamcrest in Java 8 world? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  18. Awaitility with Java 8 without Hamcrest @ T e s

    t p u b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e J a v a 8 E d i t i o n ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l ( ( ) - > q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) = = 1 ) ; } Clear and verbose error message? C o n d i t i o n T i m e o u t E x c e p t i o n : D e l a y e d D e l i v e r y M Q F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) e x p e c t e d < 1 > b u t w a s < 0 > w i t h i n 5 s e c o n d s . Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  19. Awaitility with Java 8 without Hamcrest @ T e s

    t p u b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e J a v a 8 E d i t i o n ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l ( ( ) - > q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) = = 1 ) ; } Clear and verbose error message? C o n d i t i o n T i m e o u t E x c e p t i o n : D e l a y e d D e l i v e r y M Q F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) e x p e c t e d < 1 > b u t w a s < 0 > w i t h i n 5 s e c o n d s . Not anymore C o n d i t i o n T i m e o u t E x c e p t i o n : C o n d i t i o n w i t h l a m b d a e x p r e s s i o n i n A w a i t i l i t y A s y n c h T e s t w a s n o t f u l f i l l e d w i t h i n 5 s e c o n d s . Expected 1, but was 0? 2? 3? -1? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  20. Awaitility and AssertJ @ T e s t p u

    b l i c v o i d r e s p o n s e S h o u l d B e A v a i l a b l e A f t e r S o m e T i m e J a v a 8 E d i t i o n 2 ( ) { / / w h e n q u e u e F a c a d e . s e n d P i n g ( ) ; / / t h e n a w a i t ( ) . u n t i l ( ( ) - > a s s e r t T h a t ( q u e u e F a c a d e . g e t N u m b e r O f R e c e i v e d P a c k e t s ( ) ) . i s E q u a l T o ( 1 ) ) ; } Still clear and verbose error message from AssertJ: C o n d i t i o n T i m e o u t E x c e p t i o n : C o n d i t i o n d e f i n e d a s a l a m b d a e x p r e s s i o n i n A w a i t i l i t y A s y n c h T e s t e x p e c t e d : < [ 1 ] > b u t w a s : < [ 0 ] > w i t h i n 5 s e c o n d s . Would it possible? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  21. Awaitility and AssertJ - Internals One evening of hacking p

    u b l i c A s s e r t i o n C o n d i t i o n ( f i n a l R u n n a b l e s u p p l i e r , C o n d i t i o n S e t t i n g s s e t t i n g s ) i f ( s u p p l i e r = = n u l l ) { t h r o w n e w I l l e g a l A r g u m e n t E x c e p t i o n ( " . . . " ) ; } C a l l a b l e < B o o l e a n > c a l l a b l e = n e w C a l l a b l e < B o o l e a n > ( ) { p u b l i c B o o l e a n c a l l ( ) t h r o w s E x c e p t i o n { t r y { s u p p l i e r . r u n ( ) ; r e t u r n t r u e ; } c a t c h ( A s s e r t i o n E r r o r e ) { l a s t E x c e p t i o n M e s s a g e = e . g e t M e s s a g e ( ) ; r e t u r n f a l s e ; } } } ; c o n d i t i o n A w a i t e r = n e w C o n d i t i o n A w a i t e r ( c a l l a b l e , s e t t i n g s ) { @ O v e r r i d e p r o t e c t e d S t r i n g g e t T i m e o u t M e s s a g e ( ) { r e t u r n s u p p l i e r . g e t C l a s s ( ) . g e t N a m e ( ) + " " + l a s t E x c e p t i o n M e s s a g e ; } } ; } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  22. Awaitility and AssertJ - Internals One evening of hacking p

    u b l i c A s s e r t i o n C o n d i t i o n ( f i n a l R u n n a b l e s u p p l i e r , C o n d i t i o n S e t t i n g s s e t t i n g s ) i f ( s u p p l i e r = = n u l l ) { t h r o w n e w I l l e g a l A r g u m e n t E x c e p t i o n ( " . . . " ) ; } C a l l a b l e < B o o l e a n > c a l l a b l e = n e w C a l l a b l e < B o o l e a n > ( ) { p u b l i c B o o l e a n c a l l ( ) t h r o w s E x c e p t i o n { t r y { s u p p l i e r . r u n ( ) ; r e t u r n t r u e ; } c a t c h ( A s s e r t i o n E r r o r e ) { l a s t E x c e p t i o n M e s s a g e = e . g e t M e s s a g e ( ) ; r e t u r n f a l s e ; } } } ; c o n d i t i o n A w a i t e r = n e w C o n d i t i o n A w a i t e r ( c a l l a b l e , s e t t i n g s ) { @ O v e r r i d e p r o t e c t e d S t r i n g g e t T i m e o u t M e s s a g e ( ) { r e t u r n s u p p l i e r . g e t C l a s s ( ) . g e t N a m e ( ) + " " + l a s t E x c e p t i o n M e s s a g e ; } } ; } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/ 1.6.0+
  23. Mockito - argument capturing Capture reference to object passed as

    argument in method call Alternative for matchers Useful for complex object verification A r g u m e n t C a p t o r < S h i p S e a r c h C r i t e r i a > c a p t o r = A r g u m e n t C a p t o r . f o r C l a s s ( S h i p S e a r c h C r i t e r i a . c l a s s ) ; v e r i f y ( t s ) . f i n d N u m b e r O f S h i p s I n R a n g e B y C r i t e r i a ( c a p t o r . c a p t u r e ( ) ) ; a s s e r t T h a t ( c a p t o r . g e t V a l u e ( ) . g e t M i n i m u m R a n g e ( ) ) . i s L e s s T h a n ( 2 0 0 0 ) ; Bloated - 3 lines to assert one value Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  24. Mockito - argument capturing - Java 8 Why not embed

    AssertJ inside a capturing call? v e r i f y ( t s ) . f i n d N u m b e r O f S h i p s I n R a n g e B y C r i t e r i a ( a s s e r t A r g ( s c - > a s s e r t T h a t ( s c . g e t M i n i m u m R a n g e ( ) ) . i s L e s s T h a n ( 2 0 0 0 ) ) ) ; Clear and verbose error message from AssertJ A r g u m e n t a s s e r t i o n e r r o r : t s . f i n d N u m b e r O f S h i p s I n R a n g e B y C r i t e r i a ( < a s s e r t i o n m a t c h e r > E x p e c t i n g : < 3 0 0 0 > t o b e l e s s t h a n : < 2 0 0 0 > ) ; I n t e r a c t i o n s w i t h t h i s m o c k : t s . f i n d N u m b e r O f S h i p s I n R a n g e B y C r i t e r i a ( S h i p S e a r c h C r i t e r i a { m i n i m u m R a n g e = 3 0 0 0 , n u m b e r O f P h a s e r s = 4 } ) ; Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  25. Mockito - argument capturing - internals p u b l

    i c c l a s s A s s e r t i o n M a t c h e r < T > e x t e n d s A r g u m e n t M a t c h e r < T > { ( . . . ) p u b l i c A s s e r t i o n M a t c h e r ( C o n s u m e r < T > c o n s u m e r ) { t h i s . c o n s u m e r = c o n s u m e r ; } @ O v e r r i d e p u b l i c b o o l e a n m a t c h e s ( O b j e c t a r g u m e n t ) { t r y { c o n s u m e r . a c c e p t ( ( T ) a r g u m e n t ) ; r e t u r n t r u e ; } c a t c h ( A s s e r t i o n E r r o r e ) { e r r o r M e s s a g e = e . g e t M e s s a g e ( ) ; r e t u r n f a l s e ; } } @ O v e r r i d e p u b l i c v o i d d e s c r i b e T o ( D e s c r i p t i o n d e s c r i p t i o n ) { . . . } p u b l i c s t a t i c < T > T a s s e r t A r g ( C o n s u m e r < T > c o n s u m e r ) { M o c k i t o . a r g T h a t ( n e w A s s e r t i o n M a t c h e r < T > ( c o n s u m e r ) ) ; r e t u r n h a n d y R e t u r n V a l u e s . r e t u r n S a f e N u l l ( ) ; } Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  26. Testing thrown exceptions - Java 7 @Test(expected = ...) try..catch

    ExpectedException catch-exception fluent-exception-rule Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  27. catch-exception - Java 7 catch and verify exceptions in a

    single line of code / / g i v e n L i s t < S t r i n g > m y L i s t = n e w A r r a y L i s t < > ( ) ; w h e n ( m y L i s t ) . g e t ( 1 ) ; t h e n ( c a u g h t E x c e p t i o n ( ) ) . i s I n s t a n c e O f ( I n d e x O u t O f B o u n d s E x c e p t i o n . c l a s s ) . h a s M e s s a g e ( " I n d e x : 1 , S i z e : 0 " ) . h a s N o C a u s e ( ) ; can use existing AssertJ assertions for exception instances no support for static methods and constructor calls complicated implementation proxy to remember and re-execute method call Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  28. catch-exception - Java 8 What can be simplified with Java

    8? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  29. catch-exception - Java 8 What can be simplified with Java

    8? Everything... Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  30. catch-exception - Java 8 What can be simplified with Java

    8? Everything... From the project webpage: Java 8's lambda expressions will make catch-exception redundant. The project have been deprecated by the original author Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  31. catch-exception - Java 8 The entire library (20+ classes) can

    be replaced with T h r o w a b l e c a p t u r e T h r o w a b l e ( C a l l a b l e c a l l a b l e ) { t r y { c a l l a b l e . c a l l ( ) ; F a i l . f a i l ( " E x p e c t i n g c o d e t o t h r o w a n e x c e p t i o n . " ) ; r e t u r n n u l l ; / / t o s a t i s f y c o m p i l e r } c a t c h ( T h r o w a b l e t h r o w a b l e ) { r e t u r n t h r o w a b l e ; } } And one line exception capture T h r o w a b l e t h r o w n = c a p t u r e T h r o w a b l e ( ( ) - > r i s k y M e t h o d ( ) ) ; a s s e r t T h a t ( t h r o w n ) . . i s I n s t a n c e O f ( I l l e g a l S t a t e E x c e p t i o n . c l a s s ) . h a s M e s s a g e ( " T e s t e x c e p t i o n " ) ; Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  32. catch-exception Lambda approach already implemented in AssertJ Will be available

    in AssertJ 2.0 @ T e s t p u b l i c v o i d s h o u l d C a t c h E x c e p t i o n W i t h G o o d M e s s a g e ( ) { a s s e r t T h a t E x c e p t i o n T h r o w n B y ( ( ) - > r i s k y M e t h o d ( ) ) . i s I n s t a n c e O f ( I l l e g a l S t a t e E x c e p t i o n . c l a s s ) . h a s M e s s a g e ( " T e s t e x c e p t i o n " ) ; } Java 7 people can use maintenance fork by Mariusz Smykuła from Codearte Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  33. Summary More compact and smarter (testing) code with Java 8

    Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  34. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  35. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Lambdas alone do not replace everything Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  36. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Lambdas alone do not replace everything Some tools have to be modified to benefit Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  37. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Lambdas alone do not replace everything Some tools have to be modified to benefit Some tools were made obsolete completely... Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  38. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Lambdas alone do not replace everything Some tools have to be modified to benefit Some tools were made obsolete completely... Sometimes it is required to change a mindset to see an oportunity Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/
  39. Summary More compact and smarter (testing) code with Java 8

    Some things work out of the box Lambdas alone do not replace everything Some tools have to be modified to benefit Some tools were made obsolete completely... Sometimes it is required to change a mindset to see an oportunity There is still a lot work to do in that field maybe it is worth to learn Java 8 features hacking some open source tool? Marcin Zajączkowski @SolidSoftBlog http://blog.solidsoft.info/