Getting MSpec is pretty easy. Pull the source code from the github repo and build it. Once you have everything built you should just reference the dll in your C# project: Machine.Specifications.
I wanted to keep my solution as simple as possible so I kept both the TicketCalculator and its specs in the same .cs file.
The kata specifies the method names that you start with so I went ahead and created the class:
1 2 3 4 5 | public class TicketCalculator { public void StartPurchase( int runtime, DayOfWeek day, bool isParquet, bool is3D) {} public void AddTicket( int age, bool isStudent) {} public decimal FinishPurchase() { return 0m;} } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_non_student{ private static TicketCalculator _calculator; Establish setup_expectation = () => { _calculator = new TicketCalculator(); _calculator.StartPurchase(115, DayOfWeek.Monday, true , false );}; Because trigger = () => _calculator.AddTicket(33, false ); It verify = () => _calculator.FinishPurchase().ShouldEqual(11m); } |
TryMSpec (1 test), 1 test failed
Basic admission rates, Purchasing general ticket as an adult non student (1 test), 1 test failed
verify, Failed: Machine.Specifications.SpecificationException: Should equal [11] but is [0]
Since refactoring in the "red" is forbidden, I did the simplest thing that would make my test to pass: I returned 11 from the "FinishPurchase()" method.
I am looking at both the MSpec code and the output and they are ugly: it's really hard to read. To me a test is readable when I can show it to someone and can pretty much tell what's going on in there.
My spec passed so I started cleaning up my code. The first thing I did was introducing new aliases for the delegate names. Establish, Because and It felt awkward. I always think about Given-When-Then state transitions in my tests and this change just felt more natural to me.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using Given = Machine.Specifications.Establish; using When = Machine.Specifications.Because; using Then = Machine.Specifications.It; [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_non_student{ private static TicketCalculator _calculator; Given setup_expectation = () =>{ _calculator = new TicketCalculator(); _calculator.StartPurchase(115, DayOfWeek.Monday, true , false );}; When trigger = () => _calculator.AddTicket(33, false ); Then verify = () => _calculator.FinishPurchase().ShouldEqual(11m); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_non_student{ private static TicketCalculator _calculator; Given i_buy_a_standard_movie_ticket = () =>{ _calculator = new TicketCalculator(); _calculator.StartPurchase(115, DayOfWeek.Monday, true , false );}; When i_purchase_it_for_an_adult_non_student = () => _calculator.AddTicket(33, false ); Then i_pay_11_bucks = () => _calculator.FinishPurchase().ShouldEqual(11m); } |
Given - I buy a standard movie ticket
When - I purchase it for an adult non-student
Then - I pay $11
I don't have much space on this page, but if you indent the lambda a little more the Given - When - Then words will become more apparent.
Let's look at the second scenario: a standard movie ticket for a student is $8.
Here is my spec for that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_student{ private static TicketCalculator _calculator; Given i_buy_a_standard_movie_ticket = () => { _calculator = new TicketCalculator(); _calculator.StartPurchase(115, DayOfWeek.Monday, true , false );}; When i_purchase_it_for_an_adult_student = () => _calculator.AddTicket(33, true ); Then i_pay_8_bucks = () => _calculator.FinishPurchase().ShouldEqual(8m); } |
TryMSpec (2 tests), 1 test failed
Basic admission rates, Purchasing general ticket as an adult non student (1 test), Success
i pay 11 bucks, Success
Basic admission rates, Purchasing general ticket as an adult student (1 test), 1 test failed
i pay 8 bucks, Failed: Machine.Specifications.SpecificationException: Should equal [8] but is [11]
Again, I did the simplest thing that could possibly work:
1 2 3 4 5 6 7 8 9 10 11 | public class TicketCalculator{ private decimal _ticket_price = 11m; public void StartPurchase( int runtime, DayOfWeek day, bool isParquet, bool is3D) {} public void AddTicket( int age, bool isStudent){ if (isStudent) _ticket_price = 8m; } public decimal FinishPurchase(){ return _ticket_price; } } |
TryMSpec (2 tests), Success
Basic admission rates, Purchasing general ticket as an adult non student (1 test), Success
i pay 11 bucks, Success
Basic admission rates, Purchasing general ticket as an adult student (1 test), Success
i pay 8 bucks, Success
Notice we have duplication in our specs: the "Given" delegate is duplicated the exact same way in both of them. Time to DRY up the code a little bit. I created a base class called SpecBase and moved the field "_calculator" and the "Given" delegate into it.
1 2 3 4 5 6 7 8 | public class SpecBase{ protected static TicketCalculator _calculator; Given i_buy_a_standard_movie_ticket = () => { _calculator = new TicketCalculator(); _calculator.StartPurchase(115, DayOfWeek.Monday, true , false ); }; } |
1 2 3 4 5 6 7 8 | [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_student : SpecBase{ When i_purchase_it_for_an_adult_student = () => _calculator.AddTicket(33, true ); Then i_pay_8_bucks = () => _calculator.FinishPurchase().ShouldEqual(8m); } |
1 2 3 4 5 6 | [Subject( "Basic admission rates" )] public class Purchasing_general_ticket_as_an_adult_student : Given_i_buy_a_standard_ticket{ When i_purchase_it_for_an_adult_student = () => _calculator.AddTicket(33, true ); Then i_pay_8_bucks = () => _calculator.FinishPurchase().ShouldEqual(8m); } |
I'd like to list a couple of things that you should see - and maybe follow - based on this example:
- look at the length of the specs, none of them are more than 3 lines of code,
- there is no code duplication,
- there is only one assertion in each one of them,
- the spec tells you a story.