The long way through Software Craftsmanship

Exploring Testing with Java 8 Lambdas

Jul 30, 2015 - 4 minute read - Comments - draftjavalambdaexplorationclient

At a client, we were testing a simple feature, but the resulting tests had much subtle repetition:

(source code is here)

@Test
public void log_when_sending_greeting_letters() {

	sut.send(mock(GreetingLetter.class));

	verify(logger).sentGreetingLetter();
}

@Test
public void log_when_sending_love_letters() {

	sut.send(mock(LoveLetter.class));

	verify(logger).sentLoveLetter();
}

and the production code:

public class MailSender {
	private final EventLogger eventLogger;

	public MailSender (final EventLogger eventLogger) {
		this.eventLogger = eventLogger;
	}

	public void send (final GreetingLetter letter) {
		// more business logic
		eventLogger.sentGreetingLetter();
	}

	public void send (final LoveLetter letter) {
		// more business logic
		eventLogger.sentLoveLetter();
	}
}

Note: this is an adapted code, so the business logic is not complete and seems simple.

So I decided to explore a bit on refactoring these tests to use lambdas and reduce repetition:

private MailSender sut;
private Consumer<MailSender> arrange;
private Consumer<EventLogger> verify;

@Test
public void log_greetings_letter() {
	arrange = (MailSender sut) -> sut.send(mock(GreetingLetter.class));

	verify = EventLogger::sentGreetingLetter;

	assertAndVerify();
}

@Test
public void log_love_letter() {
	arrange = (MailSender sut) -> sut.send(mock(LoveLetter.class));

	verify = EventLogger::sentLoveLetter;

	assertAndVerify();
}

private void assertAndVerify() {
	arrange.accept(sut);
	verify.accept(logger);
}

Some comments, mine and my teammates:

  • The tests are not very readable, maybe because we’re still not used to java 8 lambdas
  • assertAndVerify has multiple purposes / responsibilities
  • The gain of usability does not outweight the loss of readability

Finally, the refactor was discarded and we are using the initial version.

Later, in the dojo, Manuel and I have refactored this a little bit more. Source code is here

public class MailSenderShould {

	private EventLogger eventLogger;
	private MailSender mailSender;

	@Before
	public void setUp () {
		eventLogger = mock(EventLogger.class);
		mailSender = new MailSender(eventLogger);
	}

	@Test
	public void log_greetings_letter() {
		checkThat(aLoggingLine().forA(greetingLetter()).wasLoggedWhen(aGreetingLetterWasSent()));
	}

	@Test
	public void log_love_letter() {
		checkThat(aLoggingLine().forA(loveLetter()).wasLoggedWhen(aLoveLetterWasSent()));
	}

	private void checkThat (final Check check) {
		check.checkFor(mailSender, eventLogger);
	}

	private Consumer<EventLogger> aGreetingLetterWasSent () {
		return EventLogger::sentGreetingLetter;
	}

	private Consumer<MailSender> greetingLetter () {
		return (MailSender sut) -> sut.sendGreetingLetter(mock(GreetingLetter.class));
	}

	private Consumer<MailSender> loveLetter () {
		return (MailSender sut) -> sut.sendLoveLetter(mock(LoveLetter.class));
	}

	private Consumer<EventLogger> aLoveLetterWasSent () {
		return EventLogger::sentLoveLetter;
	}
}

(the logger is here)

Later, we have created a simpler functional DSL for the logging, joining the act and verify into an object called Check

public class MailSenderShould {

	private EventLogger eventLogger;
	private MailSender mailSender;

	@Before
	public void setUp () {
		eventLogger = mock(EventLogger.class);
		mailSender = new MailSender(eventLogger);
	}

	@Test
	public void log_greetings_letter() {
		mailSenderLogs(whenSendingAGreetingLetter());
	}

	@Test
	public void log_love_letter() {
		mailSenderLogs(whenSendingALoveLetter());
	}

	private Check whenSendingALoveLetter () {
		return new Check(
			(MailSender sut) -> sut.send(letter(LoveLetter.class)),
			EventLogger::sentLoveLetter
		);
	}

	private void mailSenderLogs (Check check) {
		check.checkFor(mailSender, eventLogger);
	}

	private Check whenSendingAGreetingLetter () {
		return new Check(
			(MailSender sut) -> sut.send(letter(GreetingLetter.class)),
			EventLogger::sentGreetingLetter
		);
	}

	private <T> T letter (final Class<T> typeOfLetter) {
		return mock(typeOfLetter);
	}
}

and the Check, just a placeholder for two Consumers:

public class Check {

	private final Consumer<MailSender> act;
	private final Consumer<EventLogger> verify;

	public Check (final Consumer<MailSender> act, final Consumer<EventLogger> verify) {
		this.act = act;
		this.verify = verify;
	}

	public void checkFor(MailSender mailSender, EventLogger eventLogger) {
		act.accept(mailSender);
		verify.accept(eventLogger);
	}
}

Comparison

Comparison between the original form and the three refactored forms of the test:

@Test
public void log_when_sending_greeting_letters() {

	sut.send(mock(GreetingLetter.class));

	verify(logger).sentGreetingLetter();
}

This reads like a classical JUnit tests, using the interaction (with mocks).

@Test
public void log_greetings_letter() {
	arrange = (MailSender sut) -> sut.send(mock(GreetingLetter.class));

	verify = EventLogger::sentGreetingLetter;

	assertAndVerify();
}

This is the first step towards more cohesion, at the cost of readability. You save the verify at the end, but the repeated method assertAndVerify is still present.

@Test
public void log_greetings_letter() {
	checkThat(aLoggingLine().forA(greetingLetter()).wasLoggedWhen(aGreetingLetterWasSent()));
}

This one reads more like a classical DSL, but is harder to read compared to a classical test.

@Test
public void log_greetings_letter() {
	mailSenderLogs(whenSendingAGreetingLetter());
}

This one reads more like a business requirement.

Other

Other ideas we have tested:

Builder.aNew().sendGreetingLetter()

assertAndVerifyThat(
	theLoggingLine()
		.for(aGreetingLetter())
		.isLoggedWhen(greetingLetterIsSent()
	)
);

public Consumer<MailSender> aGreetingLetter(){
	return (MailSender sut) -> sut.sendGreetingLetter(mock(GreetingLetter.class));
}

public Consumer<EventLogger> greetingLetterIsSent(){
	return EventLogger::sentGreetingLetter;
}
assertAndVerifyThat(
	theLoggingLine()
		.for(
			(MailSender sut) -> sut.sendGreetingLetter(mock(GreetingLetter.class))
		)
		.isLoggedWhen(EventLogger::sentGreetingLetter)
	)
);
public void assertAndVerifyThat(TestContext testContext){
	testContext.assert();
	testContext.verify();
}

Craftsmanship analogy

Jul 27, 2015 - 3 minute read - Comments - analogycraftsmanshipsarah-meistage

I’ve found this set of tweets about the craftsmanship analogy by Sarah Mei:

Faster site generation for Octopress 2

Jul 13, 2015 - 2 minute read - Comments - metaoctopress-2octopress-3octopress

2016-04 update: I’ve discovered that the original Rakefile does a similar job. See here

After a while, the generation of the static site using Octopress 2 was starting to be slow (around 2 minutes for 85 posts) for the kind of fast feedback cycle I was expecting.

Inspired by their new features in Octopress 3, one of them being faster site generation, I decided to retrofit my installation with the same feature.

Octopress 3 now has a different structure of posts and drafts. You can specify you only want to publish to your local preview your current draft instead of all of them, therefore being it faster. When you finish editing, all the posts are recovered and publishing to production uploads all of them

For Octopress 2, you can do something similar in a manual way. Create a copy of your working environment:

cp -R blog_source blog_post_factory

Git ignore all existing markdown files in the folder source/_posts

cd blog_post_factory/source/_posts
git update-index --assume-unchanged $(ls *markdown)

Delete all existing markdown files

rm *markdown

Check that git doesn’t realize about the difference:

git status

# On branch source
nothing to commit (working directory clean)

Create a new post:

rake new_post["Faster site generation for Octopress 2"]
# On branch source
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	2015-07-13-faster-site-generation-for-octopress-2.markdown
nothing added to commit but untracked files present (use "git add" to track)

Edit as much as needed, reviewing changes in local. Push to the remote repository.

In the original (blog_source) folder, pull, then generate:

rake generate && rake deploy

[Optional: change the remote URL for the cloned repository, so the git push is faster.]

After each new post, you have to ignore old files again. I’ve created a new task in the Rakefile for this purpose:

desc "Cleans all *.markdown files for a faster site generation"
task :ignore do
  cd "#{source_dir}/#{posts_dir}"
  system "git update-index --assume-unchanged $(ls *.markdown)"
  system "rm $(ls *.markdown)"
end

Conclusion

I’ve reduced the amount of time to generate files from 2 minutes for 85 files to ~0.62 seconds for 1 file.

Regenerating: 1 file(s) changed at 2015-07-13 08:03:29 ...done in 0.617269045 seconds.

What defines a dependency

Jul 13, 2015 - 1 minute read - Comments - goosjavacodefragmentdependencynotificationgooslistenerobject-stereotype

A friend and I were arguing about this code (fragment):

public void register (final String userName)  {
    try {
        registeredUsers.add(new User(userName));
    } catch (AlreadyRegisteredUserException e) {
        resultListener.alreadyRegistered(userName);
    }
}

I would have said that resultListener is a dependency as, first, it was injected by the constructor, second, it is necessary for the execution (negative case).

He suggested that:

  • being injected through the constructor is usually what happens with dependencies, but does not make it one (i.e., something can be injected by the constructor and not be a dependency)
  • what defines a dependency is related to the business: if this behavior is mandatory for the business, then it is a dependency.

As a conclusion, resultListener would be a notification, as business does not require it.

On a later commit, we decided to inject it to registeredUsers (fragment)

public void add (User user, final RegistryResultListener resultListener) {
    if(contains(user)) {
        resultListener.alreadyRegistered(user.name());
        return;
    }

    users.add(user);
    resultListener.successfullyRegistered(user.name());
}

Note: for cases when no one needs to be informed about the users being registered or not, a null-object pattern will suffice, as there is no check for null reference in the parameter resultListener.

Note: more information on object stereotypes can be found here

Object Peer Stereotypes

Jul 13, 2015 - 2 minute read - Comments - goosobject-peer-stereotypedependencynotificationadjustment

We categorize an object’s peers (loosely) into three types of relationship. An object might have:

  • Dependencies: Services that the object requires from its peers so it can perform its responsibilities. The object cannot function without these services. It should not be possible to create the object without them. For example, a graphics package will need something like a screen or canvas to draw on–it doesn’t make sense without one.
  • Notifications: Peers that need to be kept up to date with the object’s activity. The object will notify interested peers whenever it changes state or performs a significant action. Notifications are “fire and forget”; the object neither knows nor cares which peers are listening. Notifications are so useful because they decouple objects from each other. For example, in a user interface system, a button component promises to notify any registered listeners when it’s clicked, but does not know what those listeners will do. Similarly, the listeners expect to be called but know nothing of the way the user interface dispatches its events.
  • Adjustments: Peers that adjust the object’s behavior to the wider needs of the system. This includes policy objects that make decisions on the object’s behalf (the Strategy pattern in [Gamma94]) and component parts of the object if it’s a composite. For example, a Swing JTable will ask a TableCellRenderer to draw a cell’s value, perhaps as RGB (Red, Green, Blue) values for a color. If we change the renderer, the table will change its presentation, now displaying the HSB (Hue, Saturation, Brightness) values.

Source is Growing Object-Oriented Software, Guided by Tests > Chapter 6, Object-Oriented Style > Object Peer Stereotypes, page 52