Hi, last time, I tried to show you how to screw up the kata I published some time ago. This time, I'll try to examine the right (or "more right" :-)) way of doing it, using what the specifications told us the last time. The post turned out so long that I thought about splitting it, but hey, we're living in a "Push To Kindle" age, so here it is - the full thing as a single document. Enjoy!
I'll start again from scratch, thus I'll have to repeat some of the stuff from previous post to spare you the effort made on jumping back and forward between this post and the previous one. Don't worry, even the steps that are going to be the same as previously, are written differently, since I was not copy-pasting anything but wrote everything again.
This time, I'll divide each TDD cycle into four stages:
- Create a specification - we create a specification in a Given-When-Then form and translate it into code. At this point, it will probably not compile, often it won't be complete (e.g. will be missing some variable declarations)
- Make it fail for proper reason - at this point, we make our specification compile by supplying all the necessary types, variables etc. Also, just failing is not enough for us - we want the spec to fail either on assertion or mock verification. In other words - the spec must be failing because the expected end result is not supplied by the production code
- Make it pass - at this point, we implement the behavior (of course, the simplest thing that will make the current spec pass and not break any other spec)
- Examine TODO list - when finished, we need to determine our next step. Since TDD is broken down into short cycles, each of them having its own goal, we have to constantly evaluate our list of goals - the TODO list, adding items, removing items and choosing next item to implement.
For educational purposes, I'll not be doing refactoring - a short explanation why is at the end of this post.
Let's begin!
First, we'll need to start with something. Thus, we'll add the following two items to the TODO list:
- Create entry point to the module (top-level abstraction)
- Describe main work-flow of the module
By the way, new items on TODO list will always be in bold.
Let's take on the first item, since it's a prerequisite for everything else.
Specification 1: Creating new instances of MessageProcessing class
What we need first is our top-level abstraction. Since our task is to create a message processing module, I'll name the top-level abstraction: MessageProcessing.
Create a specification
We have to create a need for this class to exist, and we can do this by making the following creational specification:
Given nothing When I create a new instance of MessageProcessing class Then it should not result in any kind of exception
Which, translated into code, looks like this:
[Fact] public void ShouldAllowCreatingNewInstances() { //GIVEN //WHEN Action whenCreatingANewInstanceOfMessageProcessingClass = () => new MessageProcessing(); //THEN Assert.DoesNotThrow(whenCreatingANewInstanceOfMessageProcessingClass); }
With this spec, we created a need to have a public MessageProcessing class with a parameterless constructor. Not much, but not bad either for so few lines of code.
Make it fail for proper reason
First, we have to make it compile. This can be achieved simply by creating a MessageProcessing class. Also, to make sure it can fail, we create a parameterless constructor with an exception inside:
public class MessageProcessing { public MessageProcessing() { throw new Exception(); } }
Now the spec fails for the correct reason - we expect the constructor not to throw, but it does.
Make it pass
In order to make this spec pass, we just remove the parameterless constructor. It was there only to ensure us that the spec can fail for the proper reason and now there's no need for it. And here's the code:
public class MessageProcessing { }
Examine TODO list
After creating our top-level abstraction, our TODO list looks like this:
Create entry point to the module (top-level abstraction)- Implement main work-flow of the module
We have one item left on the TODO list - implement the main work-flow. Let's get right to it!
Specification 2: Performing validation on a frame
The second step is to sketch main work-flow of the module. As the only responsibility of the module we're creating is to validate a frame, we decide this main work-flow to be receiving the fame and submitting it to validation.
Create a specification
Our very first spec, written using a Given-When-Then notation, will look like this
Given any frame When a message processing is performed for it Then the frame should be validated according to supplied rules
Now let's try translating it literally into the code.
Given any frame
can be translated into:
var anyFrame = Any.InstanceOf<Frame>();
When a message processing is performed for it
will look like this:
var messageProcessing = new MessageProcessing(); messageProcessing.PerformFor(anyFrame);
and the last line:
Then the frame should be validated according to supplied rules
may be translated like this:
validationRules.Received().ApplyTo(frame);
So the whole spec, again:
[Fact] public void ShouldValidateMessageCreatedFromFrameWithValidationRules() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); //WHEN var messageProcessing = new MessageProcessing(); messageProcessing.PerformFor(anyFrame); //THEN validationRules.Received().ApplyTo(frame); }
Of course, it does not compile yet, since, for instance, we have not declared any variable called validationRules. It's enough, however, to sketch the behavior and discuss the design.
If you remember last time we did this exercise, the specs told us that the approach with passing whole frame to validation led us to trouble. So let's stop for a minute to examine how we can improve the design. The Frame class is poorly encapsulated and we cannot change it to improve the encapsulation. What we can do, however, is to hide this ugly class from the rest of the application by wrapping it with our own object as soon as it enters the area under our control. Then we will be able to add domain logic into that class in order to hide its structure from general purpose logic (like validation).
Knowing all this, let's rewrite the last line of spec to include our observations:
//GIVEN var anyFrame = Any.InstanceOf<Frame>(); //WHEN var messageProcessing = new MessageProcessing(); messageProcessing.PerformFor(anyFrame); //THEN message.Received().ValidateWith(validationRules);
What's this message in the last line? It's our own wrapper around the frame. We don't have any variable by this name, nor do we have any variable named validationRules. At this point, it's not important - we're merely sketching out the idea and discovering what we need to implement this behavior. In the next stage, we'll figure out where to get all these variables and types from.
By the way, so far we have created a need to have a PerformFor() method inside MessageProcessing class, and two abstractions: message and validationRules
Make it fail for proper reason
Now, the time has come! to supply what's missing and fill the void! Mwahahahaha!
... sorry, I got a little carried away.
Anyway, to make what we have written compile, we'll have to fill the blanks. We always do it in the following order:
- First, we add all missing variable instantiations
- Then, we add all the missing interfaces and classes
- Finally, we add to those types and interfaces all missing methods
1. Add all missing variable instantiations
Let's see... We're missing instantiations of two variables. The first one is message. As I already wrote, message plays active role, so It's gonna be a mock:
var message = Substitute.For<Message>();
Then, there's validationRules. It plays a passive role, so It's going to be just any instance of interface ValidationRules:
var validationRules = Any.InstanceOf<ValidationRules>();
Another thing we need to consider here is how the MessageProcessing is going to wrap our frame with the exact instance of Message class that we just created in our spec. We cannot pass the message to the MessageProcessing constructor (because we don't know the frame yet at this point and we want to wrap each frame with its own message) nor can we pass it through the PerformFor method (because this is the entry point for third-party and if they could pass a wrapped frame to us, we wouldn't need to know about the frame at all).
So, again, how do we make MessageProcessing use our instance of message? The response is a factory! We have to create one, then pass it to MessageProcessing and make it return the message we created in our spec when it's asked to wrap the frame we're feeding to MessageProcessing (by the way, as you see, the factory plays an active role in our spec). Putting it all together, it looks like this:
[Fact] public void ShouldValidateTheFrameWrappedInMessageUsingValidationRules() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); var message = Substitute.For<Message>(); var validationRules = Any.InstanceOf<ValidationRules>(); var factory = Substitute.For<MessageFactory>(); factory.Wrap(anyFrame).Returns(message); //WHEN var messageProcessing = new MessageProcessing(factory, validationRules); messageProcessing.PerformFor(anyFrame); //THEN message.Received().ValidateWith(validationRules); }
And it's a complete spec, at least from syntactical point of view. The design it points to is also very good, which we'll find out going further.
2. Add all the missing interfaces and classes
So, we added all missing variable instantiations, now we can provide the missing types (by the way, note that we discovered some new types when adding missing variable instantiations and now we can provide these as well - that's why the steps are performed in this order).
The following types are missing:
- Message interface
- ValidationRules interface
- MessageFactory interface
Providing them is a piece of cake:
public interface Message { } public interface ValidationRules { } public interface MessageFactory { }
3. Add all methods that do not exist but are used in the specification
Ok, done with second step, now the final one: satisfying the needs for methods. The spec shows us that the following methods are mising:
- Messagefactory: Message Wrap(Frame frame)
- MessageProcessing: void PerformFor(Frame frame)
- MessageProcessing (constructor): MessageProcessing(MessageFactory factory, ValidationRules validationRules)
- Message: void ValidateWith(ValidationRules rules)
Let's provide them:
public class MessageProcessing { public MessageProcessing( MessafeFactory factory, ValidationRules validationRules) { throw new NotImplementedException(); } void PerformFor(Frame frame) { throw new NotImplementedException(); } } public interface Message //TODO implement { void ValidateWith(ValidationRules rules); } public interface ValidationRules //TODO implement { } public interface MessageFactory //TODO implement { Message Wrap(Frame frame); }
I put a TODO next to each interface that does not have an implementation - that's because we'll be adding them to our TODO list in a moment. For now, I just want to signalize it.
Also, we changed the constructor, meaning we have to update the creational specification for the MessageProcessing class, because it does not have a parameterless constructor anymore. We can deal with it in two steps: first correct the specification like this:
[Fact] public void ShouldAllowCreatingNewInstances() { //GIVEN //WHEN Action whenCreatingANewInstanceOfMessageProcessingClass = () => new MessageProcessing( Any.InstanceOf<MessageFactory>, Any.InstanceOf<ValidationRules>); //THEN Assert.DoesNotThrow(whenCreatingANewInstanceOfMessageProcessingClass); }
and then remove the NotImplementedException from the constructor of MessageProcessing class:
public MessageProcessing( MessafeFactory factory, ValidationRules validationRules) { //removed throwing }
We're now finished with all the three steps of filling in the blanks: we added necessary variables instantiations, added definitions for missing types and provided all missing methods in those types. The spec now compiles and fails. Does this mean we're done with this stage? No, not yet! If you see at where the failure is, it points towards the PerformFor method of message processing that throws a NotImplementedException - this is not failing for proper reason, since the proper reason to fail is that everything went as planned except the expected call to ValidateWith method was not made on the message object.
After removing the exception like this:
void PerformFor(Frame frame) { //removed throwing exception }
we get the result we were expecting: the specification expects a call to ValidateWith method at least once, but none was made.
Before we provide an implementation that will satisfy this need, let's see at what we currently have:
public class MessageProcessing { public MessageProcessing( MessafeFactory factory, ValidationRules validationRules) { } void PerformFor(Frame frame) { } } public interface Message //TODO implement { void ValidateWith(ValidationRules rules); } public interface ValidationRules //TODO implement { } public interface MessageFactory //TODO implement { Message Wrap(Frame frame); }
Making it pass
We're going to fill in the implementation of MessageProcessing class. I like starting workflow implementations from the end, i.e. from the expectation. In the spec, we're expecting a call to ValidateWith(validationRules) to be made against message object, so let's write that into the method:
void PerformFor(Frame frame) { message.ValidateWith(validationRules); }
Now, where do we get message from? From the factory, so let's add a previous line:
void PerformFor(Frame frame) { var message = factory.Wrap(frame); message.ValidateWith(validationRules); }
Where do we get the frame from? It's passed into the method. Done. Where do we get the validationRules and factory from? They're constructor arguments, so we'll have to store them as fields when an instance of MessageProcessing is created:
public MessageProcessing( MessafeFactory factory, ValidationRules validationRules) { this.validationRules = validationRules; this.factory = factory; }
Done! The whole implementation of the MessageProcessing class looks like this:
public class MessageProcessing { readonly MessageFactory factory; readonly ValidationRules validationRules; public MessageProcessing( MessafeFactory factory, ValidationRules validationRules) { this.validationRules = validationRules; this.factory = factory; } void PerformFor(Frame frame) { var message = factory.Wrap(frame); message.ValidateWith(validationRules); } }
And the specification passes. Now we can proceed to the next stage:
Examine TODO list
Remember we left some TODOs in the code next to each interface? Let's look at it again:
public interface Message //TODO implement { void ValidateWith(ValidationRules rules); } public interface ValidationRules //TODO implement { } public interface MessageFactory //TODO implement { Message Wrap(Frame frame); }
Now let's add them to the TODO list and cross off the implemented main work-flow:
Create entry point to the module (top-level abstraction)Implement main workflow of the module- Implement Message interface
- Implement MessageFactory interface
- Implement ValidationRules interface
What shall we implement next? Obviouslt, there's no point in implementing ValidationRules, since it doesn't contain any methods yet, so we don't even know what behaviors we need to provide. On the other hand, we've got MessageFactory and Message - each having a single method. It makes more sense to start with the MessageFactory, since its functionality is actually about creating messages, so probably, when we finish with specifying and implementing the factory, we'll also have a class implementing the Message interface already.
Specification 3: Creating new instances of MessageFactory class
Before we can use the factory to create new messages, we need to have a concrete class implementing the MessageFactory interface (I'll name this class LocationMessageFactory). This is gonna be a simple creational specification. Let's go!
Create a specification
The Given-When-Then form of the spec is:
Given nothing When I create a new instance of LocationMessageFactory class Then it should not result in any kind of exception And it should implement the MessageFactory interface
As you see, this is almost the same as the specification 1. Except for the last line. Anyway, translating the spec into the code leads us to the following:
[Fact] public void ShouldAllowCreatingNewInstances() { //GIVEN //WHEN MessageFactory factory = null; Action whenCreatingANewInstanceOfLocationMessageFactoryClass = () => { factory = new LocationMessageFactory(); }; //THEN Assert.DoesNotThrow( whenCreatingANewInstanceOfLocationMessageFactoryClass ); }
Note that we did not declare the factory variable using var keyword, but as a base type. This is also a part of specification - it says that LocationMessageFactory is a subtype of MessageFactory.
Make it fail for proper reason
This spec created a need to have a LocationMessageFactory class that implements MessageFactory interface and has a parameterless constructor. Let's provide it then!
public class LocationMessageFactory : MessageFactory { public LocationMessageFactory() { throw new Exception(); } public Message Wrap(Frame frame) { throw new NotImplementedException(); } }
Note two things: first, we put an exception into the constructor, so that the spec we created can fail on assertion (Assert.DoesNotThrow). The second thing to note - the interface signature forced us to provide an implementation for a method called Wrap - currently it throws NotImplementedException and is out of scope of the current specification. By the way, I treat all NotImplementedExceptions as TODO list items - we'll take care of it when we examine our TODO list in a minute. For now, the spec fails for proper reason, so let's get to the next stage.
Make it pass
We make this spec pass by removing the constructor we introduced earlier. Remember that we don't have to touch the Wrap method, since it's out of scope of current specification. The code looks like this:
public class LocationMessageFactory : Messagefactory { public Message Wrap(Frame frame) { throw new NotImplementedException(); } }
Examine TODO list
We can cross off the item related to implementing MessageFactory class. Now we have all the initial items crossed off, but another one popped out in the meantime - the NotImplementedException inside the Wrap method - it clearly points that we need to provide a behavior over there.
Our current TODO list:
Create entry point to the module (top-level abstraction)Implement main work-flow of the module- Implement Message interface
Implement MessageFactory interface- Implement ValidationRules interface
- Implement behavior required from Wrap method in LocationMessageFactory class
The newly added item on the TODO list is our next target.
Specification 4: wrapping frames with messages
Let's implement the Wrap method in LocationMessageFactory so that it actually creates something useful for us.
Create a specification
A Given-When-Then form of the spec looks like this:
Given a factory And any frame When I ask the factory to wrap the frame Then it should create a LocationMessage object from the frame
Which translates into the following code:
[Fact] public void ShouldWrapFramesWithLocationMessageObjects() { //GIVEN var factory = new LocationMessageFactory(); var anyFrame = Any.InstanceOf<Frame>(); //WHEN var result = factory.Wrap(anyFrame); //THEN Assert.IsType<LocationMessage>(result); }
There's one thing that might seem wierd about this spec - we specify nowhere that the message is wrapping the particular frame we pass to it - it can just create another one or just wrap nothing. Is it wrong? I think not - the creational specs are always awkward and you can almost never specify entirely from which objects another object is created. We will just have to trust the production code that it wraps the frame (by the way, there are some cases where object construction rules are so complex that we DO want to specify them in detail, but this is not such a case).
Make it fail for proper reason
The spec creates a need for a new type - a LocationMessage. An instance of this type needs to be created by the factory in order to fulfill this spec. That's why we won't have to write a separate creational specification for creating LocationMessage objects - we're specifying everything we need in the factory specification already.
Anyway, the first thing to do is to satisfy the newly introduced need and create a LocationMessage. It needs to implement the Message interface, otherwise the code won't compile. Here's the implementation of the LocationMessage class:
public class LocationMessage : Message { public LocationMessage(Frame frame) { //We'll need this anyway, so I'm adding it now. } void ValidateWith(ValidationRules rules) { throw new NotImplementedException(); } }
I added a constructor, although it wasn't forced directly by the specification (such cases should be extremaly rare). Also, implementing the Message interface forced us to provide an implementation of the ValidateWith method. For now we don't care about it, since it's out of scope of the current specification, but we'll be adding it to our TODO list shortly.
Even though we needed the LocationMessage to make the compilation pass, we won't be using it in the factory implementation just yet. The specification expects an object of type LocationMessage to pass, so in order to fail for proper reason, we'll have to make the factory return something different. The quick choice for such cases is null:
public class LocationMessageFactory : Messagefactory { public Message Wrap(Frame frame) { return null; } }
Ok, the specification expects an instance of type LocationMessage, but gets a null instead - the spec fails on assertion, which is proper reason for it to fail and it means it's time to move on to the next stage.
Make it fail for proper reason
In order to do this, we'll just have to return an instance of LocationMessage type and use the constructor we have created:
public class LocationMessageFactory : Messagefactory { public Message Wrap(Frame frame) { return new LocationMessage(frame); } }
Finished! Now to check the TODO list!
Examine TODO list
We can cross off the item related to implementing Wrap method as well as implementing Message interface. Also, by implementing the Message interface with LocationMessage class, which, just to remind you, looks like this:
public class LocationMessage : Message { public LocationMessage(Frame frame) { //We don't need to use the frame just yet } void ValidateWith(ValidationRules rules) { throw new NotImplementedException(); } }
we discovered that we need to supply implementation to the ValidateWith method, which currently throws a NotImplementedException. This item should be added to our list.
So, to sum up, the list looks like this:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interface- Implement ValidationRules interface
Implement behavior required from Wrap method in LocationMessageFactory class- Implement behavior required from ValidateWith method in LocationMessage class
We already know that we have three fields in the class: Speed, Age and Sender, so we can add more details to the last item and split it into three:
Create entry point to the module (top-level abstraction)Implement main workflow of the moduleImplement Message interfaceImplement MessageFactory interface- Implement ValidationRules interface
Implement behavior required from Wrap method in LocationMessageFactory class- Implement behavior required from ValidateWith method in LocationMessage class for Speed field
- Implement behavior required from ValidateWith method in LocationMessage class for Age field
- Implement behavior required from ValidateWith method in LocationMessage class for Sender field
Again, there's no sense in picking ValidationRules interface implementation, since it has no methods, so let's take the three last items, one by one.
Specification 5: Submit Speed field to validation
As I stated previously, the reason Message abstraction exists is to decouple validation rules from the frame structure. The choice of which validation rule to apply to which frame field should be made by implementations of the Message interface - i.e. LocationMessage. The first field that needs to be passed to a validation rule is Speed.
Create a specification
Again, let's use a Given-When-Then notation, to write down the following:
Given any frame And a location message wrapping that frame And validation rules When I validate the message using the validation rules Then validation rule for integer values should be applied to the Speed field
Translated into code, it looks like this:
[Fact] public void ShouldPerformValidationForSpeedFieldWhenValidated() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); var message = new LocationMessage(anyFrame); var validationRules = Substitute.For<ValidationRules>(); //WHEN message.ValidateWith(validationRules); //THEN validationRules.Received().ApplyTo(anyFrame.Speed); }
Note that this is actually the first time we're referencing a field of the Frame class! What's more, we expect it to be passed into the validation rules which don't know that the passed value belongs to the frame. Thus, we have managed to successfully encapsulate the frame structure inside the LocationMessage class and decouple it from validation rules.
Make it fail for proper reason
The specification created a need for one new method inside VlidationRules interface: ApplyTo with an int argument. To satisfy this need, let's provide the necessary signature inside the ValidationRules interface:
public interface ValidationRules { void ApplyTo(int intValue); }
This is great news, because up to now, the ValidationRules interface was empty and we had no clue what to do with it. Now we do, but we'll leave it for later as currently the implementation of ApplyTo method is out of scope.
Another thing to do is to remove the NotImplementedException from the ValidateWith method inside the LocationMessage class. Done. Now it looks like this:
public class LocationMessage : Message { public LocationMessage(Frame frame) { //We don't need to use the frame just yet } void ValidateWith(ValidationRules rules) { } }
The spec fails because a call to ApplyTo method with specific int value was expected on the validationRules, but was not performed, which means that the spec fails for proper reason.
Make it pass
The implementation that makes our code fulfill the spec is really easy, so I'll just type it in:
public class LocationMessage : Message { private readonly Frame wrappedFrame; public LocationMessage(Frame frame) { wrappedFrame = frame; //added } void ValidateWith(ValidationRules rules) { rules.ApplyTo(frame.Speed); //added } }
And that's it.
Examine TODO list
We can cross off Speed field:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interface- Implement ValidationRules interface
Implement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed field- Implement behavior required from ValidateWith method in LocationMessage class for Age field
- Implement behavior required from ValidateWith method in LocationMessage class for Sender field
Looking at the TODO list, it should be very easy to implement the Age field validation, as it's an integer, just like Speed, which we just finished implementing. Let's take it on then!
Specification 6: Submit Age field to validation
Create a specification
The spec is going to be almost the same as previously, so all we need to do is copy the previous one and make a change or two. Hey, we're copy-pasting here! Let's use this as an opportunity to perform an experiment - we'll make an error by correcting only the name of the pasted specification, leaving the validated field name as Speed (not Age, which should be here instead) and watch whether we can get away with it:
[Fact] public void ShouldPerformValidationForAgeFieldWhenValidated() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); var message = new LocationMessage(anyFrame); var validationRules = Substitute.For<ValidationRules>(); //WHEN message.ValidateWith(validationRules); //THEN //Oh, we forgot to change Speed to Age... validationRules.Received().ApplyTo(anyFrame.Speed); }
What happens? The spec passes right away - a warning sign!
It feels really good to be protected somehow from copy-pasting errors :-). Let's quickly correct the spec:
[Fact] public void ShouldPerformValidationForAgeFieldWhenValidated() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); var message = new LocationMessage(anyFrame); var validationRules = Substitute.For<ValidationRules>(); //WHEN message.ValidateWith(validationRules); //THEN //corrected: validationRules.Received().ApplyTo(anyFrame.Age); }
There, this should do it - the spec fails on mock verification which is what we were aiming at.
Make it fail for proper reason
The specification already fails for proper reason. We're expecting validation rules to be applied to field named Age, but there's no such logic inside the LocationMessage's ValidateWith method's implementation.
Make it pass
Just add one line of code and we're all set:
public class LocationMessage : Message { private readonly Frame wrappedFrame; public LocationMessage(Frame frame) { wrappedFrame = frame; } void ValidateWith(ValidationRules rules) { rules.ApplyTo(frame.Speed); rules.ApplyTo(frame.Age); //added this line } }
Ok, it passes - now, wasn't that easy?
Examine TODO list
We can cross off another item from the TODO list - the one about Age field, leaving us with the following:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interface- Implement ValidationRules interface
Implement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age field- Implement behavior required from ValidateWith method in LocationMessage class for Sender field
Only two choices left. Since we already began with the ValidateWith method, let's take on the last one from this series - the one related to Sender field.
Specification 7: Submit Sender field to validation
This case is going to be pretty much the same as with both previous fields, only this time we'll be handling a string instead of an integer.
Create a specification
In order to create a specification in a Given-When-Then notation, let's write down the following:
Given any frame And a location message wrapping that frame And a validation When I validate the message using the validationRules Then validation rule for string values should be applied to the Sender field
The spec is going to look pretty much the same as two previous ones:
[Fact] public void ShouldPerformValidationForSenderFieldWhenValidated() { //GIVEN var anyFrame = Any.InstanceOf<Frame>(); var message = new LocationMessage(anyFrame); var validationRules = Substitute.For<ValidationRules>(); //WHEN message.ValidateWith(validationRules); //THEN validationRules.Received().ApplyTo(anyFrame.Sender); }
Make it fail for proper reason
As I said, the only difference is that this time, Sender is a string value. Yet we still use the ApplyTo name for a method expected to be called, although its declaration looks like this:
public interface ValidationRules { void ApplyTo(int intValue); }
Thus, the spec won't compile. It means that we created a need for an overload of the ApplyTo method, taking a string argument:
public interface ValidationRules { void ApplyTo(int intValue); void ApplyTo(string stringValue); //added line }
That makes our spec compile and fail for proper reason - we were expecting ApplyTo method to be called, but it wasn't.
Make it pass
The implementation of this spec is going to be as easy as the previous one. Just watch:
public class LocationMessage : Message { private readonly Frame wrappedFrame; public LocationMessage(Frame frame) { wrappedFrame = frame; } void ValidateWith(ValidationRules rules) { rules.ApplyTo(frame.Speed); rules.ApplyTo(frame.Age); rules.ApplyTo(frame.Sender); //added this line } }
Ok, that's it.
Examine TODO list
Another item to cross off from our TODO list, this time for implementing the Sender field:
Create entry point to the module (top-level abstraction)Implement main workflow of the moduleImplement Message interfaceImplement MessageFactory interface- Implement ValidationRules interface
Implement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender field
which leaves us with only one item: implementation of the ValidationRules interface.
Specification 8: Creating new instances of SimpleValidationRules class
We decide that the implementation of the ValidationRules interface will be called SimpleValidationRules. We need to drive the existence of this class and the following Given-When-Then specification will help us with this task:
Given nothing When I create a new instance of SimpleValidationRules class Then it should not result in any kind of exception
Without further comments, let's take it to the code:
[Fact] public void ShouldAllowCreatingNewInstances() { //GIVEN //WHEN ValidationRules rules = null; Action whenCreatingANewInstanceOfSimpleValidationRulesClass = () => { rules = new SimpleValidationRules(); }; //THEN Assert.DoesNotThrow( whenCreatingANewInstanceOfSimpleValidationRulesClass ); }
Make it fail for proper reason
We created a need to have a SimpleValidationRules class implementing ValidationRules interface. To satisfy this need, we'll have to create this class. Here we go:
public class SimpleValidationRules : ValidationRules { public void SimpleValidationRules() { throw new NotImplementedException(); } public void ApplyTo(int intValue) { throw new NotImplementedException(); } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
As you see, the interface signature forced us to implement ApplyTo method in two flavors. We do not need to concern ourselves with that yet, but we'll get back to it when evaluating TODO list.
For now, the spec fails on assertion, i.e. for a proper reason. It means that we can proceed with the next stage. By the way, I put an exception in the constructor again just to make the spec fail - if I didn't introduce the constructor at all, it would probably be green by now. Just to remind you, this is to make a point that we can follow a strict set of steps almost mechanically and that specification failing for proper reason is so valuable as a source of feedback, that we're willing to put in a bit of extra code just to attain it.
Make it pass
Piece of cake - just remove the constructor:
public class SimpleValidationRules : ValidationRules { public void ApplyTo(int intValue) { throw new NotImplementedException(); } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
And done - the spec passes. No further comments needed.
Examine TODO list
Fulfilling this specification led us to discovering a new class with two methods not yet implemented - we need to add them. Also, we can cross off implementing ValidationRules interface, since we just did it:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender field- Implement behavior required from ApplyTo method in SimpleValidationRules class for integer values
- Implement behavior required from ApplyTo method in SimpleValidationRules class for string values
But this is not end yet! Let's remind ourselves what exactly hides below these two newly added items by looking back at the table from the exercise description:
type of field | Correctness criteria | When satisfied | When not satisfied |
---|---|---|---|
int | greater than 0 | do nothing | throw exception |
string | not null and not empty | do nothing | throw exception |
From the table, it looks like we have the following exact behaviors to specify and implement inside SimpleValidationRules class:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender field- SimpleValidationRules should throw exception when applied to integer less than or equal 0
- SimpleValidationRules should not throw exception when applied to integer greater than 0
- SimpleValidationRules should throw exception when applied to null string
- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
When we look at the TODO items, there is one more thing that attracts our attention - it's the '0' in both items related to integer validation. In contrast to null and empty string, 0 is not a special kind of value here - it's a number like any other. It may be 0 today, and 5 tomorrow. Leaving it in two specifications will lead us to maintaining two specs whenever this number changes. That's why we'll extract the requirement for this value to be 0 into a separate TODO list item. At the same time, we'll rewrite the two items to be more general:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender field- SimpleValidationRules should throw exception when applied to integer less than minimum allowed integer
- SimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integer
- SimpleValidationRules should treat 1 as minimum allowed integer
- SimpleValidationRules should throw exception when applied to null string
- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
The only thing to note here is that we changed 0 to 1 while adjusting the greater/less conditions (what was previously less than or equal 0, now is less than 1 etc.). We could keep the 0, but thinking in terms of "minimum allowed value" (which is 1) is easier for me than in terms of "maximum disallowed value" (which is 0). You may choose to do otherwise when you perform this kata on your own.
Let's take on the first unimplemented item.
Specification 9: Throwing exceptions when applied to integers that are less than minimum allowed integer value
From now on, we'll move forward without mocks, as we've reached the border of the system. Most of the specification we'll be writing are called functional specifications (as opposed to mock-based work-flow specifications and creational specifications that we were doing so far) and a little bit different practices apply to this kind of specs, as we'll shortly see. Also, we'll speed up a little with more obvious parts.
Create a specification
Our specification in the Given-When-Then form:
Given simple validation rules When I apply them to an integer value less than minimum allowed integer by at least 1 Then an exception should be thrown
This is pretty straightforward. Translating the spec into the code leaves us with the following:
[Fact] public void ShouldThrowExceptionWhenValidatedIntegerIsBelowAllowedMinimum() { //GIVEN var rules = new SimpleValidationRules(); var integerBelowAllowedMinimum = SimpleValidation.MinimumAllowedInteger - 1; //WHEN Action applyingRulesToIntegerBelowAllowedMinimum = () => rules.ApplyTo(integerBelowAllowedMinimum); //THEN Assert.Throws<Exception>(applyingRulesToIntegerBelowAllowedMinimum); }
Why did we use SimpleValidation.MinimumAllowedInteger - 1? because it's the thinnest possible boundary between two behaviors - throwing when value is invalid and not throwing when it's valid. You can read more on this in a great post by Amir Kolsky and Scott Bain.
Now, let's see what's missing...
Making it fail for proper reason
As it stands now, the code does not compile. We're missing the SimpleValidation.MinimumAllowedInteger constant (since the above spec is the first place in the code where it actually appears). Let's introduce it then:
public class SimpleValidationRules : ValidationRules { public const int MinimumAllowedInteger = 12345; //TODO public void ApplyTo(int intValue) { throw new NotImplementedException(); } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
By giving this constant a value of 12345 and placing a TODO next to it, we're signalizing that it doesn't really matter for now what the value of this constant is, but we'll need to take care of it later (by the way, it's already on our TODO list - remember?).
This time the spec not only compiles, but it also passes, thanks to a NotImplementedException thrown from inside of ApplyTo method. I'm sorry, but you know the rules - we have to make it fail :-). In order to do so, we'll remove throwing the mentioned NotImplementedException:
public class SimpleValidationRules : ValidationRules { public const int MinimumAllowedInteger = 12345; //TODO public void ApplyTo(int intValue) { //removed throwing exception from here } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
Great, now the spec fails for the correct reason - we were expecting a call to ApplyTo method to throw an exception, but nothing was thrown.
Make it pass
The specification expects an exception to be thrown, so let's throw it! Only this time, as opposed to the previous throw that was present in the ApplyTo method, we're gonna throw an object of class Exception, not NotImplementedException:
public class SimpleValidationRules : ValidationRules { public const MinimumAllowedInteger = 12345; //TODO public void ApplyTo(int intValue) { throw new Exception(); //added line } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
And we're done!
Examine TODO list
We can cross off throwing exception when validated integer is below allowed minimum from our TODO list:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integer- SimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integer
- SimpleValidationRules should treat 1 as minimum allowed integer
- SimpleValidationRules should throw exception when applied to null string
- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
Now let's specify when the exception should not be thrown.
Specification 10: not throwing exception when validated integer is at least minimum allowed integer
First, a Given-When-Then specification:
Given simple validation rules When I apply them to an integer value less than minimum allowed integer by at least 1 Then an exception should be thrown
Second, its translation into the code:
[Fact] public void ShouldNotThrowAnyExceptionWhenValidatedIntegerIsAtLeastAllowedMinimum() { //GIVEN var rules = new SimpleValidationRules(); var integerAtLeastOfMinimumAllowedValue = SimpleValidation.MinimumAllowedInteger; //WHEN Action applyingRulesToIntegerAtLeastOfAllowedMinimum = () => rules.ApplyTo(integerAtLeastOfMinimumAllowedValue); //THEN Assert.DoesNotThrow(applyingRulesToIntegerAtLeastOfAllowedMinimum); }
We picked SimpleValidation.MinimumAllowedInteger value for this specification because it's a boundary value for specified behavior. Now to make it fail for proper reason...
Make it fail for proper reason
Actually... it already fails for proper reason - the specification expects that no exception is thrown, but it's thrown. All we need to do is to...
Make it pass
Remember that this is a second specification that passes through the ApplyTo method with int argument. Thus, while implementnig this one, we have to take care not to make the previous one fail. The boundary between the two behaviors described by the specs is the SimpleValidation.MinimumAllowedInteger, so let's turn this knowledge into an if statement:
public class SimpleValidationRules : ValidationRules { public const int MinimumAllowedInteger = 12345; //TODO public void ApplyTo(int intValue) { if(intValue < MinimumAllowedInteger) //added { //added throw new Exception(); } //added } public void ApplyTo(string stringValue) { throw new NotImplementedException(); } }
Now both specs pass, which means we can go on.
Examine TODO list
We can cross off another item on the TODO list - the one related to not throwing exceptions when validating integer that's at least of allowed minimum integer value:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integerSimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integer- SimpleValidationRules should treat 1 as minimum allowed integer
- SimpleValidationRules should throw exception when applied to null string
- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
To finish off the integer validation functionality, let's take on treating 1 as minimum allowed integer.
Specification 11: 1 should be used as minimum valid integer
This time, to breathe a bit of fresh air, it's gonna be a constant specification. We'll go faster with this one as it's a really obvious to specify and implement. We don't even have to come up with a Given-When-Then specification.
Here's the spec:
[Fact] public void ShouldTreat1AsMinimumAllowedInteger() { Assert.Equal(1, SimpleValidationRules.MinimumAllowedInteger); }
This fails for proper reason straight away, because the spec expects 1, but it gets 12345, so let's make a change to make it pass:
public class SimpleValidationRules : ValidationRules { public const MinimumAllowedInteger = 1; //... the two ApplyTo methods ... }
Done! we can cross off another item from our TODO list:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integerSimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integerSimpleValidationRules should treat 1 as minimum allowed integer- SimpleValidationRules should throw exception when applied to null string
- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
Ok, the last functionality to implement is the one related to the string validation rules. Let's go!
Specification 12: throwing exceptions when validated string is null
This case is very similar to throwing exceptions during integer validations. If you're bored reading this, you can just skim through this section briefly as there's nothing new to learn here.
Create a specification
Our Given-When-Then specification looks like this:
Given simple validation rules When I apply them to a null string Then an exception should be thrown
which translates into:
[Fact] public void ShouldThrowExceptionWhenAppliedToNullString() { //GIVEN var rules = new SimpleValidationRules(); string nullString = null; //WHEN Action applyingRulesToNullString = () => rules.ApplyTo(nullString); //THEN Assert.Throws<Exception>(applyingRulesToNullString); }
No new methods or classes needed to compile this. Also, it already passes because currently the implementation of ApplyTo for strings already throws an exception:
public void ApplyTo(string stringValue) { throw new NotImplementedException(); }
Let's make it fail for proper reason.
Make it fail for proper reason
We can achieve this by removing throwing the exception from the ApplyTo method like this:
public class SimpleValidationRules : ValidationRules { public const int MinimumAllowedInteger = 1; public void ApplyTo(int intValue) { if(intValue < MinimumAllowedInteger) { throw new Exception(); } } public void ApplyTo(string stringValue) { //removed throwing NotImplementedException } }
The specification expects that exception is thrown, but none is, so the failure is for proper reason.
Make it pass
Putting an exception back (but not NotImplementedException, since we mark TODO items with these) will do it:
public class SimpleValidationRules : ValidationRules { public const int MinimumAllowedInteger = 1; public void ApplyTo(int intValue) { if(intValue < MinimumAllowedInteger) { throw new Exception(); } } public void ApplyTo(string stringValue) { throw new Exception(); } }
The spec passes now, so we're all happy - time to move on.
Examine TODO list
Another item to get rid of from the TODO list - the one related to throwing exception when null string is validated:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integerSimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integerSimpleValidationRules should treat 1 as minimum allowed integerSimpleValidationRules should throw exception when applied to null string- SimpleValidationRules should throw exception when applied to empty string
- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
Great - only two more to go! We're almost there! Let's pick up the next item - throwing exception on empty string.
Specification 13: throwing exceptions when validating empty string
The Given-When-Then specification is almost the same as the previous one:
Given simple validation rules When I apply them to an empty string Then an exception should be thrown
which translates into:
[Fact] public void ShouldThrowExceptionAppliedToEmptyString() { //GIVEN var rules = new SimpleValidationRules(); string nullString = null; //WHEN Action applyingRulesToNullString = () => rules.ApplyTo(nullString); //THEN Assert.Throws<Exception>(applyingRulesToNullString); }
This specification compiles and passes instantly. As you remember, we have to make it fail for proper reason. But wait, this time we cannot remove throwing the exception, because the previous specification will fail. What are we gonna do?
Make it fail for proper reason
You won't like this probably, but we'll add temporary code to ApplyTo method just to make this one specification fail.
Here's this code:
public void ApplyTo(string stringValue) { if(stringValue != string.Empty) //added temporary code { //added temporary code throw new Exception(); } //added temporary code }
You may think I'm insane to put in so much code just to make a specification fail, but remember I'm trying to make a point during this kata ;-).
Make it pass
Now that we know that the spec can fail for proper reason, we can revert the changes we just made and get back to the previous implementation that was actually good enough :-).
public void ApplyTo(string stringValue) { // this is good enough to pass the current spec // and the previous one. throw new Exception(); }
You may think this is really funny - we made some effort just to revert it back. What we gained from it is a new specification and a certainty that it can fail when it's not fulfilled.
Examine TODO list
Another item to cross off:
Create entry point to the module (top-level abstraction)Implement main work-flow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integerSimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integerSimpleValidationRules should treat 1 as minimum allowed integerSimpleValidationRules should throw exception when applied to null stringSimpleValidationRules should throw exception when applied to empty string- SimpleValidationRules should not throw exception when applied to string that's neither null nor empty
Now, it's time for the last one and we're finished!
Specification 13: not throwing exception when validated string is neither null nor empty
Create a specification
The last specification in a Given-When-Then form:
Given simple validation rules When I apply them to a string that's neither null nor empty Then no exception should be thrown
which translates into:
[Fact] public void ShouldNotThrowExceptionWhenAppliedToAStringThatIsNeitherNullNorEmpty() { //GIVEN var rules = new SimpleValidationRules(); string notNullNotEmptyString = Any.MeaningfulString(); //WHEN Action applyingRulesToNotNullNotEmptyString = () => rules.ApplyTo(notNullNotEmptyString); //THEN Assert.DoesNotThrow(applyingRulesToNotNullNotEmptyString); }
Make it fail for proper reason
It already compiles and fails for proper reason - the specification is expecting no exception, but one is thrown so nothing needs to be done at this stage.
Make it pass
Currently, the implementation of ApplyTo method looks like this:
public void ApplyTo(string stringValue) { throw new Exception(); }
I.e. it always throws. What we want is to make it not throw when passed string is neither null nor empty. Actually, implementing it is quite easy:
public void ApplyTo(string stringValue) { if(!string.IsNullOrEmpty(stringValue)) //added { //added throw new Exception(); } //added }
This makes our final specification pass.
Examine TODO list
We cross off the final item from TODO list:
Create entry point to the module (top-level abstraction)Implement main workflow of the moduleImplement Message interfaceImplement MessageFactory interfaceImplement ValidationRules interfaceImplement behavior required from Wrap method in LocationMessageFactory classImplement behavior required from ValidateWith method in LocationMessage class for Speed fieldImplement behavior required from ValidateWith method in LocationMessage class for Age fieldImplement behavior required from ValidateWith method in LocationMessage class for Sender fieldSimpleValidationRules should throw exception when applied to integer less than minimum allowed integerSimpleValidationRules should not throw exception when applied to integer greater or equal to minimum allowed integerSimpleValidationRules should treat 1 as minimum allowed integerSimpleValidationRules should throw exception when applied to null stringSimpleValidationRules should throw exception when applied to empty stringSimpleValidationRules should not throw exception when applied to string that's neither null nor empty
and we're all set! Congratulations - you managed to get with me through this lengthy tutorial :-)
Now, there are few things we need to discuss before we finish
1. What's "better" in this implementation than in the previous, "worse" one?
Compared to the previous approach, we wrote 13 specifications as opposed to 8 last time. This means that we wrote 5 more specs! What's the return of investment?
Let's compare the two solutions against some exemplary changes:
Exemplary change | Previous solution | New solution | Field int Longitude is added to the frame | Maintain 4 existing specs, write 1 new | Write 1 new spec |
---|---|---|
New validation rule is added for Speed field that it has to be less than 100 | Maintain 4 existing specs, write 1 new | Write 3 new specs |
The existing validation rule for integers changes so that now they have to be less than 0 instead of more than 0 | Maintain 4 existing specs | Maintain 2 existing specs |
The difference grows as more fields and more validation rules are added and the advantage of the approach described in this post becomes more obvious. If you don't believe me, try it out and see for yourself.
2. What about refactoring?
That's right - the TDD cycle is red-green-refactor, but I didn't even mention the last one during the kata. This was done on purpose - let me explain myself :-). With the previous, screwed up approach to this kata, I stated that what I wrote should be refactored, but I'd start from scratch next time anyway. So this time I started from scratch and made it so that no refactoring is necessary. This is because most of the TDD tutorials I saw that involve refactoring are hard to follow. So I traded off showing full TDD cycle for the example being easy to follow and understand. In normal circumstances (not educational ones), I'd probably go with the approach shown earlier and refactor to this approach when the specifications would tell me that the design contains smells.
Another reason is that the kata shows a single slice of functionality, and no incremental scope pulling is taking place.
Maybe someday I'll write a post on refactoring so that we get from previous attempt to this one. For now, I'm skipping it.
3. Can't we abstract the invalid values for strings? Isn't the current solution a code smell?
By the way, if you look at some parts of the solution, namely the string validation, you can spot that it can be further abstracted. Instead of hardcoding two cases where a string is invalid (null and empty string), we could turn it into a list of invalid values and implement a general mechanism inside SimpleValidationRules that compares a validated string with such list. This would make the specs even more maintainable, but I consider it an overdesign. Not every mechanism is worth abstracting away and nothing points that more than two values will be forbidden. Of course, if this assumption doesn't hold in the future, the specs will tell me that. For now, I prefer keeping this part simple.
Summary
I hope this post is of any use for anyone - I made a lot of effort to put it together. Of course, it lacks some aspects of TDD (e.g. acceptance specifications or refactoring), but should give a general feel of how we can derive design and responsibilities using executable specifications and test-first approach.
By the way, the post is so long that I probably made at least few mistakes when writing it. If you see any of those, please let me know. Also, I welcome any feedback - feel free to leave a comment!
That's all for now - see ya!
No comments:
Post a Comment