Aubergine .Net BDD : support for named/typed parameters + RECURSIVE DSL + bugfix + no more assertions
Ok, I had some ideas this morning when I woke up, so I quickly implemented them.
Changes (v0.02)
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
Be.Corebvba.Aubergine.Examples_v0.02.zip (16,62 kb)
Edit : v0.03
Still going; next changelog :
* Test Status changed from bool to bool? => null = test implementation error
* No more need for debug.assert; just use "bool It_should_have(amount x) { return amount==x;}
So : no more assertions (ASSERTIONS ARE EVIL >:) ), and if a test fails because there is an implementation error, the status will now return a null value instead of false.
Be.Corebvba.Aubergine.Examplesv0.03.zip (16,87 kb)
Edit : v0.04
Example
This has simplified the more complicated DSL definitions a LOT ; check out the new defintion for the AccountContext :
internal class AccountContext
{
public Account AccountA = new Account();
public Account AccountB = new Account();
public Exception WhenException;
#region Given
[DSL(@"(?<account>Account[AB])_has_(?<amount>\d+)_m")]
void accountX_has_Ym(Account account, decimal amount)
{
account.Balance = amount * 1m;
}
[DSL(@"the_current_user_is_authenticated_for_(?<account>Account[AB])")]
void authenticate_for_account_x(Account account)
{
account.IsAuthenticated = true;
}
#endregion
#region When
[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);
}
#endregion
#region Then
[DSL(@"it_should_have_(?<amount>\d+)_m_on_(?<account>Account[AB])")]
bool should_have_Xm_on_AccountY(Account account, decimal amount)
{
return account.Balance == amount * 1m;
}
[DSL]
bool it_should_fail_with_error()
{
return WhenException != null;
}
#endregion
#region Recursive DSL
[DSL("(?<name>Account[AB])")]
Account getaccountAB(string name)
{
return this.Get<Account>(name);
}
#endregion
}
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 in to 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 agina 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