2. 2 / 65
Introduction
Many modern programming languages are
popular due to the libraries and frameworks that
are written on them. Where would Ruby be
without Rails?
3. 3 / 65
The Java visiting card is primarily
Spring and Hibernate
6. 6 / 65
Kotlin... To be honest, it seems to me that the
ratings of this language are artificially
overstated.
7. 7 / 65
So, Spock
To begin with, this test framework is written in
Groovy.
For large projects, this language is not
recommended, like and Scala, but for small
scripts and tests it is very suitable.
8. 8 / 65
Groovy
In fact, this language is a superset of Java, that
is, if you don’t know how to write-write in Java.
But knowledge of Groovy syntax can save you
time.
Groovy is a kind of script version of Java. It's
simpler, more compact, but at the same time
provides all the power of the JVM.
9. 9 / 65
A classic example is
`hello world`
Java:
System.out.println("Hello world!");
Groovy:
println 'Hello world!'
10. 10 / 65
Economy
println 'Hello world!'
The length of the method name
Two brackets
Quotes. Single quotes are easier to type
Semicolon
11. 11 / 65
Reading data from a file:
BufferedReader br = new BufferedReader(new
FileReader("file.txt"));
try {
StringBuilder sb = new StringBuilder();
String line = br.readLine();
while (line != null) {
sb.append(line);
sb.append(System.lineSeparator());
line = br.readLine();
System.out.println("line = " + line);
}
} finally {
if (br != null) {
br.close();
}
}
12. 12 / 65
This hulk can be placed
in one line
new File("file.txt")
.eachLine { println "line = $it" }
But most of all I like Groovy's ability to laconically
process collections.
Even revolutionary innovations in Java 8 do not
approach them.
13. 13 / 65
Working with a collection
Java:
List<Integer> results =
Stream.of(1, 2, 3)
.map(v -> v * 2)
.collect(Collectors.toList());
Groovy:
List results = [1, 2, 3]
.collect { it * 2 }
14. 14 / 65
Example of filtering
Java:
List<Integer> evenNumbers =
Stream.of(1, 2, 3, 4).filter(v -> v %
2 == 0).collect(Collectors.toList());
Groovy:
List evenNumbers = [1, 2, 3, 4]
.findAll { it % 2 == 0 }
15. 15 / 65
Let's get back to testing
In the world of Java, JUnit rules in conjunction
with any of the available frameworks for
mocking (Mockito, EasyMock, PowerMock,
etc.).
This is enough to write a test of any complexity,
but this classic approach has drawbacks.
16. 16 / 65
There is no way to give tests a normal and
understandable names. The names of
methods in the style of Camel Case are not
considered, and Junit 5 has not yet received
the proper distribution.
‘shouldAddToCartIfItemIsAvailaibleAndThe
LimitIsNotExceededAnd…..’.
17. 17 / 65
Tests are overloaded with verbose constructs,
such as Collections.singletonList(),
Verify(…), any(MyAwesomeAbstract-
FactoryBaseClass.class) and so on.
18. 18 / 65
Because these frameworks don’t allow a
normal way of dividing the testing process into
a configuration phase, testing itself and
checking the result, very often the tests look
just like a pile of incomprehensible, and
bizarrely mixed code.
19. 19 / 65
In order to test incoming arguments, you need
to build cumbersome structures.
20. 20 / 65
There is no normal support for parameterized
tests. As a result, to cover all cases, we need
to write many similar tests.
21. 21 / 65
When the tests begin to bring a lot of pain, we
try not to use them, or we write trashy, and as a
result we then face even greater pain.
Bad tests don’t guarantee that any new code
change will not break anything else.
22. 22 / 65
Tests written on Groovy look smoother, they are
easier to read and understand. But some
people did not think it was enough, and after a
while a group of enthusiasts wrote their test
framework, Spock.
23. 23 / 65
Spock Framework
For many years, programmers have come to the
conclusion that the more code resembles the
usual sentences of the human language, the
easier and faster it can be understood.
24. 24 / 65
In programming practice, this is not always
applicable. But in testing, due to the fact that all
tests are similar in their idea, this practice is
successfully used.
Programming languages that allow you to flexibly
change your syntax, were able to realize this idea
to the fullest extent.
25. 25 / 65
Currently, tests can be started by customers
who are far from programming. All that is
needed is to write the business requirements in
a compact form. Next, programmers will simply
translate this test into code.
For example: "When the user press the start
button. then the menu is shown as in the
picture".
26. 26 / 65
A simpler example
Addition of numbers 2 and 2 must return 4
This short formulation can already be used as a
test name. It remains only to describe his body.
As in any other test, we have the initial data,
the operation that we need to test and check
the result.
27. 27 / 65
This can be described in three words:
given, when, then:
Addition of numbers 2 and 2 must return 4
given: 2, 2
when: 2 + 2
then: 4
This approach is called BDD (Behavior Driven
Development).
28. 28 / 65
Spock is a modern framework for testing. It's
written in Groovy, includes tools for stubbing
and mocking and embodies the idea of BDD. In
fact, this is a domain specific language (DSL),
created specifically for writing tests.
It is an extension of the Junit runner and you do
not have to change anything in the
infrastructure to run these tests.
29. 29 / 65
Advantages
Groovy is not as verbose as Java
The special syntax is aimed specifically at testing
Built-in support for Stubs and Mocks
Extends Junit runner
Simple testing with parameters
Keywords for all testing phases (given, when, then …)
Ease of describing methods
Many other specific features
32. 32 / 65
Fixture Methods
Run before every feature method:
def setup() {}
Run after every feature method:
def cleanup() {}
Run before the first feature method:
def setupSpec() {}
Run after the last feature method:
33. 33 / 65
Blocks Order
given: data initialization goes here (includes
creating mocks)
when: invoke your test subject here and assign it
to a variable
then: assert data here
cleanup: optional
where: optional: provide parameterized data
(tables or pipes)
37. 37 / 65
Data Tables
class Math extends Specification {
def "maximum of two numbers"(int a, int
b, int c) {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3 // passes
7 | 4 || 4 // fails
0 | 0 || 0 // passes
38. 38 / 65
Unrolling
A method annotated with @Unroll will have its
rows from data table reported independently:
@Unroll
def "maximum of two numbers"() { ... }
39. 39 / 65
Result with Unroll
maximum of two numbers[0] PASSED
maximum of two numbers[1] FAILED
Math.max(a, b) == c
| | | | |
| 7 0 | 7
42 false
40. 40 / 65
Result Without Unroll
We have to figure out which row failed manually
maximum of two numbers FAILED
Condition not satisfied:
Math.max(a, b) == c
| | | | |
| 7 0 | 7
42 false
41. 41 / 65
Data Pipes
Right side must be Collection, String or Iterable.
where:
a << [3, 7, 0]
b << [5, 0, 0]
c << [5, 7, 0]
42. 42 / 65
Multi-Variable Data Pipes
where:
[a,b,c] << sql.rows("select a,b,c from maxdata")
where:
row << sql.rows("select * from maxdata")
// pick apart columns
a = row.a
b = row.b
c = row.c
43. 43 / 65
Ignore some variable
where:
[a,b] << [[1,2,3],[1,2,3],[4,5,6]]
[a, b, _, c] << sql.rows("select * from maxdata")
44. 44 / 65
Combine data tables, pipes and
assignments
where:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > b ? a : b
48. 48 / 65
Create Mock
Mocks are Lenient (return default value for undefined mock
calls)
Subscriber subscriber = Mock()
def subscriber2 = Mock(Subscriber)
49. 49 / 65
Using Mock
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello") //subsriber should call
receive with "hello" once.
1 * subscriber2.receive("hello")
}
50. 50 / 65
Cardinality
1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three
calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls,
including zero
52. 52 / 65
Target
A call to `subscriber`:
1 * subscriber.receive("hello")
A call to any mock object:
1 * _.receive("hello")
53. 53 / 65
Method
A method named 'receive'
1 * subscriber.receive("hello")
A method whose name matches the given regular
expression. Here: method name starts with 'r' and
ends with 'e'.
1 * subscriber./r.*e/("hello")
54. 54 / 65
Argument
1 * subscriber.receive("hello") // an argument that is
equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is
unequal to the String "hello"
1 * subscriber.receive() // the empty argument list
(would never match in our example)
1 * subscriber.receive(_) // any single argument
(including null)
1 * subscriber.receive(*_) // any argument list
(including the empty argument list)
55. 55 / 65
Argument
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument
that is-a String
1 * subscriber.receive({ it.size() > 3 }) // an argument that
satisfies the given predicate
// (here: message length is greater than 3)
61. 61 / 65
Disadvantages of Spock
Due to some differences between Java and Groovy, for
example, different numeric data types, some tests on Groovy
look more cumbersome because type casting is required.
Also Spock is not good friends with the Spring Framework.
Here is an example of how you can use objects from the
Spring context:
62. 62 / 65
Spring Boot
The recommended way to use Spock mocks in
@WebMvcTest tests, is to use an embedded config
annotated with @TestConfiguration and to create the
mocks using the DetachedMockFactory.
63. 63 / 65
@WebMvcTest
class WebMvcTestIntegrationSpec extends Specification {
@Autowired
MockMvc mvc
@Autowired
HelloWorldService helloWorldService
def "spring context loads for web mvc slice"() {
given:
helloWorldService.getHelloMessage() >> 'hello world'
expect: "controller is available"
mvc.perform(MockMvcRequestBuilders.get("/"))
.andExpect(status().isOk())
.andExpect(content().string("hello world"))
}
@TestConfiguration
static class MockConfig {
def detachedMockFactory = new DetachedMockFactory();
@Bean
HelloWorldService helloWorldService() {
return detachedMockFactory.Stub(HelloWorldService)
}
}
}