Puede que hayas oído hablar de Spock, el framework basado en Groovy que te permite hacer tus tests Java más legibles y expresivos. Pero seguramente no hayas visto una comparación de código real de tests hechos en librerías de test típicas como son JUnit y Mockito y cómo se harían en Spock, algo que realmente te haga consciente de lo que ganas, y lo que pierdes, al hacer tus tests con Spock.
En esta charla no solo mostraremos eso, sino que repasaremos algunas recomendaciones de buenas prácticas típicas para los tests unitarios o de integración en Java, y veremos cómo se ven afectadas al usar Spock. Cómo algunas pierden importancia y hasta sentido, y otras nuevas aparecen para reemplazarlas. Cómo ahora puedes pensar más en el qué que en el cómo, cómo todo es más sencillo. Y cómo pierdes también cosas por el camino.
¿Te atreves a cambiar la forma de hacer tus tests?
4. A testing framework
Tests are written in
100% compatible with Java code
Runs on
http://spockframework.org/
5. A basic JUnit test
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
}
6. A basic Spock test
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
7. A basic Spock test
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
8. A basic Spock test
DSL
def "Add a positive number to zero returns the same positive number"() {
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
}
9. String literals
def "Add a positive number to zero returns the same positive number"() {
(...)
}
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
(...)
}
10. String literals
def "Add a positive number to zero returns the same positive number"() {
(...)
}
@Test
public void thatAddAPositiveNumberToZeroReturnsTheSamePositiveNumber()
throws Exception {
(...)
}
def "En un lugar de la Mancha, de cuyo nombre no quiero acordarme,
no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero,
adarga antigua, rocín flaco y galgo corredor. "() {
(...)
}
11. Spock Blocks
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
Programming vs. Specification
12. Spock Blocks
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
// Given
int positiveNumber = 23;
int zero = 0;
// When
int result = zero + positiveNumber;
// Then
assertEquals(positiveNumber, result);
Compilation check
Programming vs. Specification
13. Documented Spock Blocks
given: "A positive number"
def positiveNumber = 23
and: "Well... a SPECTACULAR zero"
def zero = 0
when: "The number is added to zero"
def result = zero + positiveNumber
then: "The result is the same number"
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
Documents purpose
Helps thinking
14. expect block
given: "A positive number"
def positiveNumber = 23
expect: "That added to zero results the same number"
zero + positiveNumber == positiveNumber
Replaces when-then for simple
functional tests
15. Expectations (then / expect)
given:
def positiveNumber = 23
def zero = 0
when:
def result = zero + positiveNumber
then:
result == positiveNumber
int positiveNumber = 23;
int zero = 0;
int result = zero + positiveNumber;
assertEquals(positiveNumber, result);
27. Responses declaration
@Test
public void testSuccessfulPaymentWithNewCreditCard() throws Exception {
when(customerRegistry.getCustomerData(SUBSCRIPTION_ID)).thenReturn(CUSTOMER_DATA);
(...)
PaymentResult result = paymentCoordinator.doPayment(paymentData);
(...)
}
def "Succesful payment with new credit card"() {
given: "A registered customer"
customerRegistry.getCustomerData(SUBSCRIPTION_ID) >> CUSTOMER_DATA
(...)
when: "A payment is requested"
def result = paymentCoordinator.doPayment(paymentData)
(...)
}
28. Interaction expectations
@Test
public void testSuccessfulPaymentWithNewCreditCard() throws Exception {
(...)
when(paymentInteractor.performPaymentInProvider(inputFields))
.thenReturn(PAYMENT_SUCCESSFUL_OUTPUT);
PaymentResult result = paymentCoordinator.doPayment(paymentData);
verify(paymentInteractor).performPaymentInProvider(inputFields)).
(...)
}
def "Succesful payment with new credit card"() {
(...)
when: "A payment is requested"
def result = paymentCoordinator.doPayment(paymentData)
then: "It is sent to the payment provider with successful result"
1 * paymentInteractor.performPaymentInProvider(inputFields) >>
PAYMENT_SUCCESSFUL_OUTPUT
(...)
}
29. Mocks and Stubs
then:
0 * _
Semantic
Lenient: default
values
Only return values
Stubs: empty objects
Stubs: no interaction
expectations
Mocks: nulls
50. ●
Increase abstraction level
Not “programming tests” ® specify test cases
Easy + powerful
Expressivity ® test is also documentation
● Easy to run in continuous integration systems / IDEs
● Better error detection info
Advantages?
51. ● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
52. ● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
class BaseIntegrationSpecification extends TIntegrationSpecification {
@InjectOverride MercadopagoClient mercadopago = Mock()
@Inject PaymentNotificationsService paymentNotificationsServiceMock
(...)
@TIntegrationTestsModule
static class MockedBoundariesModule extends SpockMocksModule {
(...)
}
}
53. ● Code Refactors not so safe
● Mocks can only be created in the Spec class
Integration tests with dependency injection overrides ... more
difficult, but possible!
Disadvantages?
class BaseIntegrationSpecification extends TIntegrationSpecification {
@InjectOverride MercadopagoClient mercadopago = Mock()
@Inject PaymentNotificationsService paymentNotificationsServiceMock
(...)
@TIntegrationTestsModule
static class MockedBoundariesModule extends SpockMocksModule {
(...)
}
}
Buenas a todos, gracias por venir.
Java Testing ==> cuánta gente realmente prueba?
- opensource, std. Groovy
Fácil pensar – no te da tanto, ¿por qué cambiar?
Ejemplo parecido – salto de altura
Dick Fosbury – Mexico 68
Hasta años después no se popularizó del todo – quizá lo que pensaban entonces el resto de saltadores es que no te da tanto, que por qué cambiar, que ellos llevaban toooda la vida … saltando así.