NBehave行为驱动测试关于story和scenarios

原文:

Behavior-Driven Development with NBehave

这里模拟了一个“银行账户”的类

一个余额属性,一个存款方法,一个撤销账户的方法,一个转账的方法。
public sealed class Account
{
  private int balance;

  public int Balance
  {
    get { return balance; }
    set { balance = value; }
  }

  public void Deposit(int amount)
  {
  }

  public void Withdraw(int amount)
  {
  }

  public static void Transfer(Account from, Account to, int amount)
  {
  }
}

初始化测试类( 注:引用了NBehave的dll
public class AccountTest : SpecBase
{
  public Account account;
  public Account account2;

  [SetUp]
  public void Initialize_before_each_test()
  {
    account = new Account();
    account2 = new Account { Balance = 100 };
  }
}



Stories

(关于BDD)所有的内容都从一个‘故事’(Story)开始。

        很早之前,NBehave的开发者们就开始尝试写出一个叫Story的类,这个类被设计出来的意义就是为了能够描述一组特定的应用场景,以用来模拟我们想要具体测试的业务。

举个例子,假设我们把这个story描述成一个存款业务,于是:

[Story, That, Should("Increase account balance when money is deposited")]
public void Deposit_should_increase_account_balance()
{
  Story story = new Story("Deposit");
  story.AsA("User")
    .IWant("The bank account balance to increase by the amount deposited")
    .SoThat("I can deposit money");

  // scenarios here
}
(注:这个story从上往下读,按字符串的描述可以读成:

存款业务应该增加账户余额

作为一个用户,我想要让账户内的余额增加,那么我可以选择通过存款实现

PS:我的语序混乱了,但原文的例子是这样写的

整段代码其实并没有技术实现上的实际意义,它只不过是利用了一种类似第三方单元测试的语法描述了业务的特性(原文:the attributes that decorate the test method are really using the familiar xUnit testing attributes 而对于上个story,文章使用了NBehave),对于BDD而言,其目的是为了让整个业务的逻辑性和可读性增强,当然我们也可以换成别的语法风格,比如

using That = MbUnit.Framework.TestAttribute;
using Describe = MbUnit.Framework.CategoryAttribute;
using For = MbUnit.Framework.CategoryAttribute;
using Wrote = MbUnit.Framework.DescriptionAttribute;
using Should = MbUnit.Framework.DescriptionAttribute;
其实不论那种风格,目的都是为了让业务和逻辑变的可读,而NBehave的风格已经成为了一种趋势,因为如上面所演示的一样,他的可读性太棒了。

现在回到那个story里,发现没有,整个story是没有任何test存在的,它仅仅是使用了文字与NBehave提供的接口就完成了一个完整业务的描述。

于是story的定义出来了,他就是为了更清晰的反映出一个特定需求(注意,是一个特定的需求,而不是一个无序的故事,在实际的开发中,这需要业务人员悉心的分离出来),然后当这个story送到了开发人员的手上时,他们就能有针对性的并能更容易决定写出哪些测试。


Scenarios

我们有了一个story,现在可以开始写测试了。

我们根据story,然后提供一些叫做scenario的内容-描述一个可能发生的事件(原文:we have to provide something called a scenario – a description of one possible thing that can happen

就银行存款案例而言,可能发生这些情况:你的账户能正常存款(这最好);你的账户被注销了(这当然就不能存款了);或者你输入了一个负的数字(一般人当然不会);我们要对每一种可能出现的场景做到覆盖率100%的测试,让我们先从简单的开始:

story.WithScenario("Money deposit")
  .Given("My bank account is empty", () => { account.Balance = 0; })
  .When("I deposit 100 units", () => account.Deposit(100))
  .Then("The account balance should be 100", () => account.Balance.ShouldEqual(100));

这个小片段大概已经告诉我们什么是BDD了,是的,一个前提,一个后置条件,最后是结果

  • 首先, WithScenario() 告诉系统这是一个什么样的scenario。这段信息将被设置到测试工具中,之后的所有(测试)内容都请遵循这个场景的限定。
  • 然后, 使用 Given() 来定义一个前提-即初始化一个空的账户。这里有两个参数,一个 string 用来描述你将做什么, 另一个 Action 将实现你之前的定义(即给定/初始化条件)。
  •  When() 方法描述一个行为,当一个行为动作发生时该怎样处理的,这就是我们之后想要测试的内容。
  • 最后, Then() 方法将对我们的方法进行测试。
(通顺的翻译:
在“存款”这个场景中,
我们有一个余额为0的账户,
现在我往账户里存入100元,
于是我的账户余额应该变成了100)


你可能已经发现了,在上面的测试中,包含了大量了lambda表达式,这是NBehave的特点之一,可以灵活的配置每一个Action。

现在来看看测试结果(注:文章中使用的是MbUnit,写出这个测试的主要目的是让大家可以很清晰的看到NBehave所带来的好处——结构清晰,通俗易懂

*** DebugTrace ***
Story: Deposit

Narrative:
    As a User
    I want The bank account balance to increase by the amount deposited
    So that I can deposit money

    Scenario 1: Money deposit
        Given My bank account is empty
        When I deposit 100 units
        Then The account balance should be 100 - FAILED


MbUnit.Core.Exceptions.NotEqualAssertionException:
  Equal assertion failed: [[0]]!=[[100]]

很显然,因为我们根本没有实现 Deposit方法,测试当然不能通过,现在我们想写办法(添加一些内容)来让测试通过。


public void Deposit(int amount)
{
  balance += amount;
}

测试通过了,瞧,多简单!通过业务分析,描述场景以及测试,我们实现了一个业务方法了。

再看一个复杂点的,在存款时尝试存一个负的数

story.WithScenario("Negative amount deposit")
  .Given("My bank account is empty", () => { account.Balance = 0; })
  .When("I try to deposit a negative amount", () => { })
  .Then("I get an exception",
        () => typeof(Exception).ShouldBeThrownBy(() => account.Deposit(-100)))
  .And("My bank account balance is unchanged",
       () => account.Balance.ShouldEqual(0));
这里注意两件事,一是在 Then() 使用 扩展方法 ShouldBeThrownBy() ,确保调用了 Deposit() 方法添加一个负数, 另一个是使用And来验证余额(注:And相当于重载了一次Then()方法,它在使用在Given(),When()的后面时,是同样的意义,表示重载上一个方法

(关于为什么使用 When("I try to deposit a negative amount", () => { })这样一个空方法,作者的解释大概是说想要通过这样一个不合理的方法来捕捉到一个异常,具体不翻译了,和NBehave关系不大,是他例子中的一种想法)

跑一遍测试,然后看输出:

*** DebugTrace ***
Story: Deposit

Narrative:
    As a User
    I want The bank account balance to increase by the amount deposited
    So that I can deposit money

    Scenario 1: Money deposit
        Given My bank account is empty
        When I deposit 100 units
        Then The account balance should be 100

    Scenario 2: Negative amount deposit
        Given My bank account is empty
        When I try to deposit a negative amount
        Then I get an exception - FAILED
我们得到了一个异常,第二个场景没有通过,于是我们继续想办法来让测试通过:
public void Deposit(int amount)
{
  if (amount <= 0)
    throw new Exception();
  balance += amount;
}
同样是一个很简单的方法,继续通过分析,举例场景以及测试,又完成了一个业务需求。

上面那个场景的语法很明显是不易懂的,不但使用了一个扩展方法,并且还没有一个行为发生(空的When()方法),很不合逻辑

更改一下场景:

story.WithScenario("Valid transfer")
  .Given("I have 100 dollars", () => { account.Balance = 100; })
  .And("You have 100 dollars", () => { account2.Balance = 100; })
  .When("I give you 50 dollars",
        () => Assert.DoesNotThrow(() => Account.Transfer(account, account2, 50)))
  .Then("I have 50 dollars left", () => account.Balance.ShouldEqual(50))
  .And("You have 150 dollars", () => account2.Balance.ShouldEqual(150));
整个例子完成了,没有扩展方法,没有奇怪的语句,整个测试和实现就是由语句和NBehave提供的接口来完成的。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值