The rising popularity of techniques such as refactoring, test-driven development (TDD), and continuous integration (CI) mean that if we’re going to use lambda expressions in our day-to-day programming, we need to understand how to test code using them and written with them.
A wealth of material has been written on how to test and debug computer programs, and this chapter isn’t going to revisit all that material. If you’re interested in learning how to do TDD properly, I highly recommend the books Test-Driven Development by Kent Beck and Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce (both from Addison-Wesley).
I am going to cover techniques specific to using lambda expressions in your code, and when you might not want to (directly) use lambda expressions at all. I’ll also talk about some appropriate techniques for debugging programs that heavily use lambda expressions and streams.
We’re first going to look at some examples of how to refactor an existing
code base into using lambda expressions. I’ve talked a bit already about how to
do local refactoring operations, such as replacing a for
loop with a stream
operation. Here we’ll take a more in-depth look at how non-collection code
can be improved.
The process of refactoring code to take advantage of lambdas has been given the snazzy name point lambdafication (pronounced lambda-fi-cation, practitioners of this process being “lamb-di-fiers” or “responsible developers”). It’s a process that has happened within the Java core libraries for Java 8. When you’re choosing how to model the internal design of your application, it’s also really worth considering which API methods to expose in this way.
There are a few key heuristics that can help you out when identifying an appropriate place to lambdify your application or library code. Each of these can be considered a localized antipattern or code smell that you’re fixing through point lambdification.
In Example 7-1, I’ve repeated our example code from Chapter 4 about
logging statements. You’ll see that it’s pulling out the Boolean value from
isDebugEnabled
only to check it and then call a method on the Logger
.
If you find that your code is repeatedly querying and operating on an object
only to push a value back into that object at the end, then that code belongs in the class of the object that you’re modifying.
Logger
logger
=
new
Logger
();
if
(
logger
.
isDebugEnabled
())
{
logger
.
debug
(
"Look at this: "
+
expensiveOperation
());
}
Logging is a good example of where this has historically been difficult to achieve, because in different locations you’re trying to provide different behavior. In this case, the behavior is building up a message string that will differ depending upon where in your program you’re logging and what information you’re trying to log.
This antipattern can be easily solved by passing in code as data. Instead of
querying an object and then setting a value on it, you can pass in a lambda
expression that represents the relevant behavior by computing a value. I’ve
also repeated the code solution in Example 7-2, as a
reminder. The lambda expression gets called if we’re at a debug level and the
logic for checking this call remains inside of the Logger
itself.
Logger
logger
=
new
Logger
();
logger
.
debug
(()
->
"Look at this: "
+
expensiveOperation
());
Logging is also a demonstration of using lambda expressions to do better object-oriented programming (OOP). A key OOP concept is to encapsulate local state, such as the level of the logger. This isn’t normally encapsulated very well, as isDebugEnabled
exposes its state. If you use the lambda-based approach, then the code outside of the logger doesn’t need to check the level at all.
In this code smell, you subclass solely to override a single method. The
ThreadLocal
class is a good example of this. ThreadLocal
allows us to
create a factory that generates at most one value per thread. This is an easy
way of ensuring that a thread-unsafe class can be safely used in a concurrent
environment. For example, if we need to look up an artist from the database
but want to do it once per thread, then we might write something like the code in Example 7-3.
ThreadLocal
<
Album
>
thisAlbum
=
new
ThreadLocal
<
Album
>
()
{
@Override
protected
Album
initialValue
()
{
return
database
.
lookupCurrentAlbum
();
}
};
In Java 8 we can use the factory method withInitial
and pass in a
Supplier
instance that deals with the creation, as shown in Example 7-4.
ThreadLocal
<
Album
>
thisAlbum
=
ThreadLocal
.
withInitial
(()
->
database
.
lookupCurrentAlbum
());
There are a few reasons why the second example would be considered
preferable to the first. For a start, any existing instance of Supplier<Album>
can be used here without needing to be repackaged for this specific case, so it
encourages reuse and composition.
It’s also shorter to write, which is an advantage if and only if all other things are equal. More important, it’s shorter because it’s a lot cleaner: when reading the code, the signal-to-noise ratio is lower. This means you spend more time solving the actual problem at hand and less time dealing with subclassing boilerplate. It also has the advantage that it’s one fewer class that your JVM has to load.
It’s also a lot clearer to anyone who tries to read the code what its intent is. If you try to read out loud the words in the second example, you can easily hear what it’s saying. You definitely can’t say this of the first example.
Interestingly, this wasn’t an antipattern previously to Java 8—it was the idiomatic way of writing this code, in the same way that using anonymous inner classes to pass around behavior wasn’t an antipattern, just the only way of expressing what you wanted in Java code. As the language evolves, so do the idioms that you use when programming.
Write Everything Twice (WET) is the opposite of the well-known Don’t Repeat Yourself (DRY) pattern. This code smell crops up in situations where your code ends up in repetitive boilerplate that produces more code that needs to be tested, is harder to refactor, and is brittle to change.
Not all WET situations are suitable candidates for point lambdification. In some situations, couple duplication can be the only alternative to having an overly closely coupled system. There’s a good heuristic for situations where WET suggests it’s time to add some point lambdification into your application. Try adding lambdas where you want to perform a similar overall pattern but have a different behavior from one variant to another.
Let’s look at a more concrete example. On top of our music domain, I’ve decided
to add a simple Order
class that calculates useful properties about some
albums that a user wants to buy. We’re going to count the number of musicians,
number of tracks, and running time of our Order
. If we were using imperative
Java, we would write some code like Example 7-5.
public
long
countRunningTime
()
{
long
count
=
0
;
for
(
Album
album
:
albums
)
{
for
(
Track
track
:
album
.
getTrackList
())
{
count
+=
track
.
getLength
();
}
}
return
count
;
}
public
long
countMusicians
()
{
long
count
=
0
;
for
(
Album
album
:
albums
)
{
count
+=
album
.
getMusicianList
().
size
();
}
return
count
;
}
public
long
countTracks
()
{
long
count
=
0
;
for
(
Album
album
:
albums
)
{
count
+=
album
.
getTrackList
().
size
();
}
return
count
;
}
In each case, we’ve got the boilerplate code of adding some code for each album
to the total—for example, the length of each track or the number of
musicians. We’re failing at reusing common concepts and also leaving ourselves
more code to test and maintain. We can shorten and tighten this code by
rewriting it using the Stream
abstraction and the Java 8 collections library.
Example 7-6 is what we would come up with if we directly translated
the imperative code to streams.
public
long
countRunningTime
()
{
return
albums
.
stream
()
.
mapToLong
(
album
->
album
.
getTracks
()
.
mapToLong
(
track
->
track
.
getLength
())
.
sum
())
.
sum
();
}
public
long
countMusicians
()
{
return
albums
.
stream
()
.
mapToLong
(
album
->
album
.
getMusicians
().
count
())
.
sum
();
}
public
long
countTracks
()
{
return
albums
.
stream
()
.
mapToLong
(
album
->
album
.
getTracks
().
count
())
.
sum
();
}
It still suffers from the same reuse and readability issues, because there are certain abstractions and commonalities that are only expressible in domain terms. The streams library won’t provide a method for you to count the number of a certain thing per album—that’s the kind of domain method that you should be writing yourself. It’s also the kind of domain method that was very hard to write before Java 8 because it’s doing a different thing for each method.
Let’s think about how we’re going to implement such a function. We’re going to
return a long
with the count of some feature for all the albums. We also
need to take in some kind of lambda expression that tells us what the number
for each album is. This means we need a method parameter that returns us a
long
for each album; conveniently, there is already a ToLongFunction
in the Java 8 core libraries. As shown in Figure 7-1, it
is parameterized by its argument type, so we’re using
ToLongFunction<Album>
.
Now that we’ve made these decisions, the body of the method follows naturally. We take
a Stream
of the albums, map each album to a long
, and then sum them.
When we implement the consumer-facing methods such as countTracks
, we pass in
a lambda expression with behavior specific to that domain method. In this
case, we’re mapping the album to the number of tracks. Example 7-7 is
what our code looks like when we’ve converted the code to use this domain-appropriate method.
public
long
countFeature
(
ToLongFunction
<
Album
>
function
)
{
return
albums
.
stream
()
.
mapToLong
(
function
)
.
sum
();
}
public
long
countTracks
()
{
return
countFeature
(
album
->
album
.
getTracks
().
count
());
}
public
long
countRunningTime
()
{
return
countFeature
(
album
->
album
.
getTracks
()
.
mapToLong
(
track
->
track
.
getLength
())
.
sum
());
}
public
long
countMusicians
()
{
return
countFeature
(
album
->
album
.
getMusicians
().
count
());
}
Unit testing is a method of testing individual chunks of code to ensure that they are behaving as intended.
Usually, when writing a unit test you call a method in your test code that gets called in your application. Given some inputs and possibly test doubles, you call these methods to test a certain behavior happening and then specify the changes you expect to result from this behavior.
Lambda expressions pose a slightly different challenge when unit testing code. Because they don’t have a name, it’s impossible to directly call them in your test code.
You could choose to copy the body of the lambda expression into your test and then test that copy, but this approach has the unfortunate side effect of not actually testing the behavior of your implementation. If you change the implementation code, your test will still pass even though the implementation is performing a different task.
There are two viable solutions to this problem. The first is to view the lambda expression as a block of code within its surrounding method. If you take this approach, you should be testing the behavior of the surrounding method, not the lambda expression itself. Let’s take look Example 7-8, which gives an example method for converting a list of strings into their uppercase equivalents.
public
static
List
<
String
>
allToUpperCase
(
List
<
String
>
words
)
{
return
words
.
stream
()
.
map
(
string
->
string
.
toUpperCase
())
.
collect
(
Collectors
.<
String
>
toList
());
}
The only thing that the lambda expression in this body of code does is directly call a core Java method. It’s really not worth the effort of testing this lambda expression as an independent unit of code at all, since the behavior is so simple.
If I were to unit test this code, I would focus on the behavior of the method. For example, Example 7-9 is a test that if there are multiple words in the stream, they are all converted to their uppercase equivalents.
@Test
public
void
multipleWordsToUppercase
()
{
List
<
String
>
input
=
Arrays
.
asList
(
"a"
,
"b"
,
"hello"
);
List
<
String
>
result
=
Testing
.
allToUpperCase
(
input
);
assertEquals
(
asList
(
"A"
,
"B"
,
"HELLO"
),
result
);
}
Sometimes you want to use a lambda expression that exhibits complex functionality. Perhaps it has a number of corner cases or a role involving calculating a highly important function in your domain. You really want to test for behavior specific to that body of code, but it’s in a lambda expression and you’ve got no way of referencing it.
As an example problem, let’s look at a method that is slightly more complex than converting a list of strings to uppercase. Instead, we’ll be converting the first character of a string to uppercase and leaving the rest as is. If we were to write this using streams and lambda expressions, we might write something like Example 7-10. Our lambda expression doing the conversion is at .
Should we want to test this, we’d need to fire in a list and test the output for every single example we wanted to test. Example 7-11 provides an example of how cumbersome this approach becomes. Don’t worry—there is a solution!
@Test
public
void
twoLetterStringConvertedToUppercaseLambdas
()
{
List
<
String
>
input
=
Arrays
.
asList
(
"ab"
);
List
<
String
>
result
=
Testing
.
elementFirstToUpperCaseLambdas
(
input
);
assertEquals
(
asList
(
"Ab"
),
result
);
}
Don’t use a lambda expression. I know that might appear to be strange advice in a book about how to use lambda expressions, but square pegs don’t fit into round holes very well. Having accepted this, we’re bound to ask how we can still unit test our code and have the benefit of lambda-enabled libraries.
Do use method references. Any method that would have been written as a lambda expression can also be written as a normal method and then directly referenced elsewhere in code using method references.
In Example 7-12 I’ve refactored out the lambda expression into its own method. This is then used by the main method, which deals with converting the list of strings.
public
static
List
<
String
>
elementFirstToUppercase
(
List
<
String
>
words
)
{
return
words
.
stream
()
.
map
(
Testing:
:
firstToUppercase
)
.
collect
(
Collectors
.<
String
>
toList
());
}
public
static
String
firstToUppercase
(
String
value
)
{
char
firstChar
=
Character
.
toUpperCase
(
value
.
charAt
(
0
));
return
firstChar
+
value
.
substring
(
1
);
}
Having extracted the method that actually performs string processing, we can cover all the corner cases by testing that method on its own. The same test case in its new, simplified form is shown in Example 7-13.
A pretty common part of writing unit tests is to use test doubles to describe the expected behavior of other components of the system. This is useful because unit testing tries to test a class or method in isolation of the other components of your code base, and test doubles allow you to implement this isolation in terms of tests.
Even though test doubles are frequently referred to as mocks, actually both stubs and mocks are types of test double. The difference is that mocks allow you to verify the code’s behavior. The best place to understand more about this is Martin Fowler’s article on the subject.
One of the simplest ways to use lambda expressions in test code is to implement lightweight stubs. This is really easy and natural to implement if the collaborator to be stubbed is already a functional interface.
In Behavioral Write Everything Twice, I discussed how to refactor our common domain logic into
a countFeature
method that used a lambda expression to implement different
counting behavior. Example 7-14 shows how we might go about unit testing part of its
behavior.
@Test
public
void
canCountFeatures
()
{
OrderDomain
order
=
new
OrderDomain
(
asList
(
newAlbum
(
"Exile on Main St."
),
newAlbum
(
"Beggars Banquet"
),
newAlbum
(
"Aftermath"
),
newAlbum
(
"Let it Bleed"
)));
assertEquals
(
8
,
order
.
countFeature
(
album
->
2
));
}
The expected behavior is that the countFeature
method returns the sum of
some number for each album it’s passed. So here I’m passing in four different
albums, and the stub in my test is returning a count of 2 features for each
album. I assert that the method returns 8—that is, 2×4. If you expect to
pass a lambda expression into your code, then it’s usually the right thing to
have your test also pass in a lambda expression.
Most test doubles end up being the result of more complex expectation setting.
In these situations, frameworks such as Mockito are often used to easily
generate test doubles. Let’s consider a simple example in which we want to
produce a test double for a List
. Instead of returning the size of the List
,
we want to return the size of another List
. When mocking the size
method
of the List
, we don’t want to specify just a single answer. We want our answer to
perform some operation, so we pass in a lambda expression (Example 7-15).
List
<
String
>
list
=
mock
(
List
.
class
);
when
(
list
.
size
()).
thenAnswer
(
inv
->
otherList
.
size
());
assertEquals
(
3
,
list
.
size
());
Mockito uses an Answer
interface that lets you provide alternative
implementation behavior. In other words, it already supports our familiar
friend: passing code as data. We can use a lambda expression here because
Answer
is, conveniently, a functional interface.
Using a debugger typically involves stepping through statements of your program or attaching breakpoints. Sometimes you might encounter situations using the streams library where debugging becomes a little bit more complex, because the iteration is controlled by the library and many stream operations are lazily evaluated.
In the traditional imperative view of the world, in which code is a sequence of actions that achieve a goal, introspecting state after or before an action makes perfect sense. In Java 8, you still have access to all your existing IDE debugging tools, but sometimes you need to tweak your approach a little in order to achieve good results.
Let’s say you’re performing a series of operations on a collection and you’re trying to debug the code; you want to see what the result of an individual operation is. One thing you could do is print out the collection value after each step. This is pretty hard with the Streams framework, as intermediate steps are lazily evaluated.
Let’s take a look at how we might log intermediate values by taking a look at an imperative version of our nationality report from Chapter 3. In case you’ve forgotten, and who doesn’t sometimes, this is code that tries to find the country of origin for every artist on an album. In Example 7-16 we’re going to log each of the nationalities that we find.
Set
<
String
>
nationalities
=
new
HashSet
<>();
for
(
Artist
artist
:
album
.
getMusicianList
())
{
if
(
artist
.
getName
().
startsWith
(
"The"
))
{
String
nationality
=
artist
.
getNationality
();
System
.
out
.
println
(
"Found nationality: "
+
nationality
);
nationalities
.
add
(
nationality
);
}
}
return
nationalities
;
Now we could use the forEach
method to print out the values from the stream,
which would also cause it to be evaluated. However, this way has the downside that we
can’t continue to operate on that stream, because streams can only be used once.
If we really want to use this approach, we need to recreate the stream. Example 7-17 shows how ugly this can get.
album
.
getMusicians
()
.
filter
(
artist
->
artist
.
getName
().
startsWith
(
"The"
))
.
map
(
artist
->
artist
.
getNationality
())
.
forEach
(
nationality
->
System
.
out
.
println
(
"Found: "
+
nationality
));
Set
<
String
>
nationalities
=
album
.
getMusicians
()
.
filter
(
artist
->
artist
.
getName
().
startsWith
(
"The"
))
.
map
(
artist
->
artist
.
getNationality
())
.
collect
(
Collectors
.<
String
>
toSet
());
Fortunately, the streams library contains a method that lets you look at each
value in turn and also lets you continue to operate on the same underlying
stream. It’s called peek
. In Example 7-18, we have rewritten
the previous example using peek
in order to print out the stream values without
having to repeat the pipeline of stream operations.
Set
<
String
>
nationalities
=
album
.
getMusicians
()
.
filter
(
artist
->
artist
.
getName
().
startsWith
(
"The"
))
.
map
(
artist
->
artist
.
getNationality
())
.
peek
(
nation
->
System
.
out
.
println
(
"Found nationality: "
+
nation
))
.
collect
(
Collectors
.<
String
>
toSet
());
It’s also possible to use the peek
method to output to existing logging systems
such as log4j
, java.util.logging
, or slf4j
in exactly the same way.
Logging is just one of many tricks that the peek
method has up its sleeve. To
allow us to debug a stream element by element, as we might debug a loop step by
step, a breakpoint can be set on the body of the peek
method.
In this case, peek
can just have an empty body that you set a breakpoint in.
Some debuggers won’t let you set a breakpoint in an empty body, in which case I just map a value to itself in order to be able to set the breakpoint.
It’s not ideal, but it works fine.
peek
method is very useful for logging out intermediate values when
debugging.