At the end of Part 1 I had a working
On with the main logic…
TestCase subclass: BirthdayServiceTestCase [ <comment: nil> <category: 'BirthdayKata-Tests'> testNoEmployeesWithBirthday [ <category: 'tests'> | repository mailSender kata | repository := InMemoryRepository new. mailSender := MockEmailSender new. kata := BirthdayService new repository: repository; mailSender: mailSender. kata sendGreetings: (Date newDay: 14 month: #Feb year: 2019). self assert: (mailSender count) equals: 0. ] ]
Nice new shiny test, I want to provide the repository and SMTP sender, and execute the code:
$ gst-sunit -v -f BirthdayKataTest.st BirthdayServiceTestCase Loading BirthdayKataTest.st BirthdayServiceTestCase>>##testNoEmployeesWithBirthday did not understand #new TestVerboseLog>>logError: (SUnit.star#VFS.ZipFile/SUnit.st:608) BirthdayServiceTestCase(TestCase)>>logError: (SUnit.star#VFS.ZipFile/SUnit.st:870)
And of course, it goes Boom!
There are at least three classes I need to define.
InMemoryRepositoryneeds to provide employees on-demand.
MockEmailSenderneeds to accept emails for delivery, I added a
countmethod to allow me to find out how many emails it has sent.
BirthdayServiceis the main driver, it uses the repository and email sender, to send emails to employees when it’s their birthday.
Object subclass: InMemoryRepository [ <comment: 'I am just a test repository, I provide employees on demand.'> <category: 'BirthdayKata-Tests'> ] Object subclass: MockEmailSender [ <comment: 'I am just a mock email sender, I record emails I should send.'> <category: 'BirthdayKata-Tests'> count [ "I return the number of emails sent." ^0 ] ]
These are simplest possible versions that I can write to allow me to get that test passing.
Finally, my empty implementation of the BirthdayService driver.
Object subclass: BirthdayService [ <comment: 'I am the main driver for the BirthdayKata.'> <category: 'BirthdayKata'> | employeeRepository mailSender | employeeRepository: anEmployeeRepository [ "Set the value of the receiver's 'employeeRepository' instance variable to the argument anEmployeeRepository." <category: 'accessing'> employeeRepository := anEmployeeRepository. ] mailSender: aMailSender [ "Set the value of the receiver's 'mailSender' instance variable to the argument aMailSender." <category: 'accessing'> mailSender := aMailSender. ] sendGreetings: aDate [ "Send emails to all the employees whose birthday is on aDate." <category: 'accessing'> ] ]
Executing the tests…at the moment, I’m keeping my BirthdayService class in the test file for ease of writing.
$ gst-sunit -v -f BirthdayKataTest.st BirthdayServiceTestCase Loading BirthdayKataTest.st BirthdayServiceTestCase>>##testNoEmployeesWithBirthday . 1 run, 1 passes
Ok, that doesn’t say much…
Implementing basic functionality.
At this point, I got going with a simple implementation of my logic.
I’m not going to reproduce the entire code but some parts are worth illustrating:
This is post-initialization logic.
InMemoryRepository class >> new [ "Answer a new instance of the receiver." <category: 'basic'> ^self basicNew initialize. ] initialize [ "Initialize the receiver's state" <category: 'private-initialization'> employees := Set new. ]
To store the set of dummy employees for the
InMemoryRepository, I added a
Set to store them in, to initialize the set this is the code, there’s no
special method for initialization, and there are a variety of ways of doing
initialize being the common name for this.
new is a class method, and it delegates to
initialize method with the result of
basicNew as the receiver.
UPDATE: I see that the unstable release (from nearly 4 years ago) of GNU Smalltalk
has fixed this issue http://smalltalk.gnu.org/news/gnu-smalltalk-3-2-91
The >>#initialize function will now be called when creating an object.
This is very similar to Ruby’s
initialize mechanism (or constructors in pretty
much every other OO language).
I added the following tests:
I don’t need to reproduce them all here but they look fairly similar to this.
testOneEmployeeWithBirthday [ <category: 'tests'> | repository mailSender kata | repository := InMemoryRepository new. repository addEmployee: (Employee new birthDate: (Date newDay: 14 month: #Feb year: 1990)). mailSender := MockEmailSender new. kata := BirthdayService new employeeRepository: repository; mailSender: mailSender. kata sendGreetings: (Date newDay: 14 month: #Feb year: 2019). self assert: (mailSender count) equals: 1. ]
I added a mechanism for adding employees to that
Set that was added, this
simply delegates to
add: on the employees set.
And in the
MockEmailSender I added a
sendEmail: anEmail [ "Send an email to an employee." <category: 'sending-email'> count := count + 1 ] initialize [ "Initialize the receiver's state" <category: 'private-initialization'> count := 0 ]
This is the simplest thing that works, all it does is increment the number of
emails that have been sent, but before we can do much more, I need to revisit
Employee class, and add some new properties, and do a tiny bit of
Finally…the core business logic…
sendGreetings: aDate [ "Send emails to all the employees whose birthday is on aDate." <category: 'greetings'> (self employeesWithBirthday: aDate) do: [:each | mailSender sendEmail: 'testing'] ] employeesWithBirthday: aDate [ "Answer the employees whose birthday it is." <category: 'private'> ^employeeRepository listEmployees select: [:each | each isBirthday: aDate] ]
employeesWithBirthday: returns a filtered set of employees, and
sendGreetings: gets the filtered set, and delegates to the
Small bit of refactoring
testNoEmployeesWithBirthday [ <category: 'tests'> | repository mailSender kata | repository := InMemoryRepository new.
I was instantiating a new
MockEmailSender in each
test, but, this is sUnit, so…
setUp [ <category: 'setup'> super setUp. repository := InMemoryRepository new. mailSender := MockSMTPSender new. ]
setUp I can easily reduce the repetition.
So, in the next part, back to the
Employee class, employees are more than just a