Ok, I had some ideas this morning when I woke up, so I quickly implemented them.
Changes
Here is the change_log for the new version:
- Bugfix given a DSL attribute without a parameter is called
- DSL definition changed to named parameters/typeconverters
Example
This has simplified the more complicated DSL definitions a LOT; check out the new definition for the AccountContext
:
internal class AccountContext
{
public Account AccountA = new Account();
public Account AccountB = new Account();
public Exception WhenException;
[DSL(@"(?<account>Account[AB])_has_(?<amount>\d+)_m")]
void accountX_has_Ym(Account account, decimal amount)
{
account.Balance = amount * 1m;
}
[DSL(@"it_should_have_(?<amount>\d+)_m_on_(?<account>Account[AB])")]
void should_have_Xm_on_AccountY(Account account, decimal amount)
{
account.Balance.ShouldEqual(amount * 1m);
}
[DSL(@"transfering_(?<amount>\d+)_m_from_(?<from>Account[AB])_to_(?<to>Account[AB])")]
void transfering_xm_from_a_to_b(decimal amount, Account from, Account to)
{
from.Transfer(amount * 1m, to);
}
[DSL(@"the_current_user_is_authenticated_for_(?<account>Account[AB])")]
void authenticate_for_account_x(Account account)
{
account.IsAuthenticated = true;
}
[DSL]
void it_should_fail_with_error()
{
(WhenException != null).ShouldEqual(true);
}
[DSL("(?<name>Account[AB])")]
Account getaccountAB(string name)
{
return this.Get<Account>(name);
}
}
Note the support for typed parameters and also DSL type converters. Due to the implication, the expressiveness has changed a lot: you can now do recursive dsl definitions !!! I'll get into this when I have more time, but really short: when you call a DSL function, the input string is pushed again to the interpreter. In theory, you could define a complete language like this !!!
In the example above, [DSL(@"the_current_user_is_authenticated_for_(?<account>Account[AB])")] is called, and the result for the group <account>
is again pushed into the DSL engine; if a match is found, it is called, and the result of the function is returned; if not, it tries to do a Convert.ChangeyType(xxx,destintationtype);
Finally, for reference the Story as well as the output test results:
class Transfer_money_between_accounts : Story<accountcontext>
{
As_a user;
I_want to_transfer_money_between_accounts;
So_that I_can_have_real_use_for_my_money;
Given AccountA_has_3_m;
Given AccountB_has_2_m;
[Cols("xx", "yy", "zz")]
[Data(1, 2, 3)]
[Data(2, 1, 4)]
[Data(3, 0, 5)]
class Transfer_xx_m_between_2_accounts : Scenario
{
Given the_current_user_is_authenticated_for_AccountA;
When transfering_xx_m_from_AccountA_to_AccountB;
Then it_should_have_yy_m_on_AccountA;
Then it_should_have_zz_m_on_AccountB;
}
class Transfer_too_much : Scenario
{
Given the_current_user_is_authenticated_for_AccountA;
When transfering_4_m_from_AccountA_to_AccountB;
Then it_should_have_3_m_on_AccountA;
Then it_should_have_2_m_on_AccountB;
Then it_should_fail_with_error;
}
class Not_authorized_for_transfer : Scenario
{
When transfering_1_m_from_AccountB_to_AccountA;
Then it_should_have_3_m_on_AccountA;
Then it_should_have_2_m_on_AccountB;
Then it_should_fail_with_error;
}
}
Output
==STORY================================================================
Transfer_money_between_accounts => OK
Transfer_1_m_between_2_accounts => OK
Given AccountA_has_3_m => OK
Given AccountB_has_2_m => OK
Given the_current_user_is_authenticated_for_AccountA => OK
When transfering_1_m_from_AccountA_to_AccountB => OK
Then it_should_have_2_m_on_AccountA => OK
Then it_should_have_3_m_on_AccountB => OK
Transfer_2_m_between_2_accounts => OK
Given AccountA_has_3_m => OK
Given AccountB_has_2_m => OK
Given the_current_user_is_authenticated_for_AccountA => OK
When transfering_2_m_from_AccountA_to_AccountB => OK
Then it_should_have_1_m_on_AccountA => OK
Then it_should_have_4_m_on_AccountB => OK
Transfer_3_m_between_2_accounts => OK
Given AccountA_has_3_m => OK
Given AccountB_has_2_m => OK
Given the_current_user_is_authenticated_for_AccountA => OK
When transfering_3_m_from_AccountA_to_AccountB => OK
Then it_should_have_0_m_on_AccountA => OK
Then it_should_have_5_m_on_AccountB => OK
Transfer_too_much => OK
Given AccountA_has_3_m => OK
Given AccountB_has_2_m => OK
Given the_current_user_is_authenticated_for_AccountA => OK
When transfering_4_m_from_AccountA_to_AccountB => OK
Then it_should_have_3_m_on_AccountA => OK
Then it_should_have_2_m_on_AccountB => OK
Then it_should_fail_with_error => OK
Not_authorized_for_transfer => OK
Given AccountA_has_3_m => OK
Given AccountB_has_2_m => OK
When transfering_1_m_from_AccountB_to_AccountA => OK
Then it_should_have_3_m_on_AccountA => OK
Then it_should_have_2_m_on_AccountB => OK
Then it_should_fail_with_error => OK