Organize IntelliJ IDEA’s import as Eclipse ones:
While working at a client, where everyone is using eclipse, while I’m using IntelliJ IDEA, there are subtle formatting differences. One of them is in the order of the imports.
To solve it, see this link
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 Consumer
s:
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();
}
I’ve found this set of tweets about the craftsmanship analogy by Sarah Mei:
We’ve read the fourth chapter from the Practical Object-Oriented Design in Ruby, by Sandi Metz.
These are the concepts and links brought up during the discussion:
- The craftsmanship analogy, as exposed in these tweets
- Difference between function and method
- What vs How (also present in the GOOS)
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
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.
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