C# NUnit 的 BDD

发布于 2024-10-12 06:46:00 字数 2334 浏览 2 评论 0原文

我一直在使用自制的 BDD Spec 扩展在 NUnit 中编写 BDD 样式测试,我想看看每个人的想法。它能增加价值吗?很烂吗?如果是这样为什么?那里有更好的东西吗?

这是来源: https://github.com/mjezzi/NSpec

我创建这个的原因有两个

  1. :为了使我的测试易于阅读。
  2. 生成简单的英语输出 查看规格。

下面是测试的示例:

- 因为僵尸现在似乎很流行..

给定 Zombie、Peson 和 IWeapon:

namespace Project.Tests.PersonVsZombie
{
    public class Zombie
    {

    }

    public interface IWeapon
    {
        void UseAgainst( Zombie zombie );
    }

    public class Person
    {
        private IWeapon _weapon;

        public bool IsStillAlive { get; set; }

        public Person( IWeapon weapon )
        {
            IsStillAlive = true;
            _weapon = weapon;
        }

        public void Attack( Zombie zombie )
        {
            if( _weapon != null )
                _weapon.UseAgainst( zombie );
            else
                IsStillAlive = false;
        }
    }
}

以及 NSpec 风格的测试:

public class PersonAttacksZombieTests
{
    [Test]
    public void When_a_person_with_a_weapon_attacks_a_zombie()
    {
        var zombie = new Zombie();
        var weaponMock = new Mock<IWeapon>();
        var person = new Person( weaponMock.Object );

        person.Attack( zombie );

        "It should use the weapon against the zombie".ProveBy( spec =>
            weaponMock.Verify( x => x.UseAgainst( zombie ), spec ) );

        "It should keep the person alive".ProveBy( spec =>
            Assert.That( person.IsStillAlive, Is.True, spec ) );
    }

    [Test]
    public void When_a_person_without_a_weapon_attacks_a_zombie()
    {
        var zombie = new Zombie();
        var person = new Person( null );

        person.Attack( zombie );

        "It should cause the person to die".ProveBy( spec =>
            Assert.That( person.IsStillAlive, Is.False, spec ) );
    }
}

您将在输出窗口中获得 Spec 输出:

[PersonVsZombie]

- PersonAttacksZombieTests

    When a person with a weapon attacks a zombie
        It should use the weapon against the zombie
        It should keep the person alive

    When a person without a weapon attacks a zombie
        It should cause the person to die

2 passed, 0 failed, 0 skipped, took 0.39 seconds (NUnit 2.5.5).

I've been using a home brewed BDD Spec extension for writing BDD style tests in NUnit, and I wanted to see what everyone thought. Does it add value? Does is suck? If so why? Is there something better out there?

Here's the source:
https://github.com/mjezzi/NSpec

There are two reasons I created this

  1. To make my tests easy to read.
  2. To produce a plain english output to
    review specs.

Here's an example of how a test will look:

-since zombies seem to be popular these days..

Given a Zombie, Peson, and IWeapon:

namespace Project.Tests.PersonVsZombie
{
    public class Zombie
    {

    }

    public interface IWeapon
    {
        void UseAgainst( Zombie zombie );
    }

    public class Person
    {
        private IWeapon _weapon;

        public bool IsStillAlive { get; set; }

        public Person( IWeapon weapon )
        {
            IsStillAlive = true;
            _weapon = weapon;
        }

        public void Attack( Zombie zombie )
        {
            if( _weapon != null )
                _weapon.UseAgainst( zombie );
            else
                IsStillAlive = false;
        }
    }
}

And the NSpec styled tests:

public class PersonAttacksZombieTests
{
    [Test]
    public void When_a_person_with_a_weapon_attacks_a_zombie()
    {
        var zombie = new Zombie();
        var weaponMock = new Mock<IWeapon>();
        var person = new Person( weaponMock.Object );

        person.Attack( zombie );

        "It should use the weapon against the zombie".ProveBy( spec =>
            weaponMock.Verify( x => x.UseAgainst( zombie ), spec ) );

        "It should keep the person alive".ProveBy( spec =>
            Assert.That( person.IsStillAlive, Is.True, spec ) );
    }

    [Test]
    public void When_a_person_without_a_weapon_attacks_a_zombie()
    {
        var zombie = new Zombie();
        var person = new Person( null );

        person.Attack( zombie );

        "It should cause the person to die".ProveBy( spec =>
            Assert.That( person.IsStillAlive, Is.False, spec ) );
    }
}

You'll get the Spec output in the output window:

[PersonVsZombie]

- PersonAttacksZombieTests

    When a person with a weapon attacks a zombie
        It should use the weapon against the zombie
        It should keep the person alive

    When a person without a weapon attacks a zombie
        It should cause the person to die

2 passed, 0 failed, 0 skipped, took 0.39 seconds (NUnit 2.5.5).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

佞臣 2024-10-19 06:46:00

我将介绍 BDD 的一些用途,而不仅仅是框架,因为我认为对单元级 BDD 的深入理解可能会影响您创建的一些东西。总的来说,我喜欢它。这里是:

我不会将它们称为 PersonAttacksZombieTests,而是将它们称为 PersonTests 甚至 PersonBehaviour。通过这种方式,可以更轻松地找到与特定类关联的示例,并让您将它们用作文档。

看起来 IsStillAlive 并不是您想要对一个人设置的那种东西;而是一种内在属性。小心地让这样的事情公开。您正在添加不需要的行为。

调用 new Person(null) 似乎不是特别直观。如果我想创建一个没有武器的人,我通常会寻找一个构造函数 new Person()。 BDD 的一个好技巧是编写您想要的 API,然后让下面的代码完成艰苦的工作 - 让代码易于使用,而不是易于编写。

对我来说,这种行为和责任似乎也有点奇怪。为什么决定人生死的是人而不是僵尸?我更愿意看到这样的行为:

  • 一个人可以装备武器(通过 person.Equip(IWeapon Weapon))。
  • 一个人如果没有武器,就从拳头开始。
  • 当人攻击僵尸时,该人对僵尸使用武器。
  • 武器决定僵尸的生死。
  • 如果僵尸还活着,它就会发起反击。僵尸会杀死这个人(通过person.Kill)。

在我看来,这似乎将行为和责任放在了更好的位置。使用不同类型的武器进行无用的攻击,而不是检查 null,还可以让您避免使用 if 语句。你需要不同的测试:

  • 拳头在对抗僵尸时不应该杀死它
  • 电锯在对抗僵尸时应该杀死僵尸 一个
  • 人在攻击僵尸时应该使用他们装备的武器 一个人
  • 应该配备拳头,如果他们有没有其他武器
  • 僵尸应该在还活着的时候进行反击。
  • 如果僵尸死了,它就不会反击。
  • 僵尸如果被杀就应该死。
  • 一个人如果被杀就应该死。

除此之外,它看起来很棒。我喜欢您使用模拟的方式、字符串的流程以及测试方法本身的措辞。我也很喜欢ProveBy;它完全按照其声明进行操作,并且很好地结合了提供行为示例和将它们作为测试运行之间的区别。

I'm going to call out some uses of BDD rather than just the framework, as I think having a really great understanding of unit-level BDD might affect some of the things you create. Overall, I like it. Here goes:

Rather than calling them PersonAttacksZombieTests, I'd just call them PersonTests or even PersonBehaviour. It makes it much easier to find the examples associated with a particular class this way, letting you use them as documentation.

It doesn't look like IsStillAlive is the kind of thing you'd want to set on a person; rather an intrinsic property. Careful making things like this public. You're adding behaviour that you don't need.

Calling new Person(null) doesn't seem particularly intuitive. If I wanted to create a person without a weapon, I would normally look for a constructor new Person(). A good trick with BDD is to write the API you want, then make the code underneath do the hard work - make code easy to use, rather than easy to write.

The behaviour and responsibilities also seem a bit odd to me. Why is the person, and not the zombie, responsible for determining whether the person lives or dies? I'd prefer to see behaviour like this:

  • A person can be equipped with a weapon (via person.Equip(IWeapon weapon)).
  • A person starts with a fist if they have no weapon.
  • When the person attacks a zombie, the person uses the weapon on the zombie.
  • The weapon determines whether the zombie lives or dies.
  • If the zombie is still alive, it attacks back. The zombie will kill the person (via person.Kill).

That seems to me as if it's got the behaviour and responsibilities in a better place. Using a different kind of weapon for useless attacks, rather than checking for null, also allows you to avoid that if statement. You'd need different tests:

  • A fist should not kill a zombie when used against it
  • A chainsaw should kill a zombie when used against it
  • A person should use their equipped weapon when attacking a zombie
  • A person should be equipped with a fist if they have no other weapon
  • A zombie should attack back when it's still alive.
  • A zombie should not attack back if it's dead.
  • A zombie should die if killed.
  • A person should die if killed.

Other than that, it looks great. I like the way you've used the mocks, the flow of strings, and the phrasing of the test methods themselves. I also quite like ProveBy; it's doing exactly what it says on the tin, and nicely ties up the difference between providing examples of behaviour and running them as tests.

羞稚 2024-10-19 06:46:00

最近我已经多次提出此类问题。有很多合理的选项,您可以轻松创建自己的选项,如本文的一些答案所示。我一直致力于开发 BDD 测试框架,目的是使其能够轻松扩展到任何单元测试框架。我目前支持 MSTest 和 NUnit。它被称为 Given,并且是开源的。基本思想非常简单,Given 提供了通用功能集的包装器,然后可以为每个测试运行器实现这些功能。

以下是 NUnit 给定测试的示例:

[Story(AsA = "car manufacturer",
       IWant = "a factory that makes the right cars",
       SoThat = "I can make money")]
public class when_building_a_toyota : Specification
{
    static CarFactory _factory;
    static Car _car;

    given a_car_factory = () =>
                              {
                                  _factory = new CarFactory();
                              };

    when building_a_toyota = () => _car = _factory.Make(CarType.Toyota);

    [then]
    public void it_should_create_a_car()
    {
        _car.ShouldNotBeNull();
    }

    [then]
    public void it_should_be_the_right_type_of_car()
    {
        _car.Type.ShouldEqual(CarType.Toyota);
    }
}

我尽力忠实于 Dan North 的介绍中的概念BDD 博客,因此,一切都是使用给定的、when、then 规范风格完成的。它的实现方式允许您有多个给定,甚至多个时间,并且它们应该按顺序执行(仍在检查这一点)。

此外,Given 中直接包含一整套 Should 扩展。这使得像上面看到的 ShouldEqual() 调用这样的事情成为可能,但是充满了用于集合比较和类型比较等的好方法。对于那些熟悉 MSpec 的人,我基本上将它们撕掉并做了一些进行修改以使它们在 MSpec 之外工作。

不过,我认为回报在于报告。测试运行器充满了您创建的场景,因此您可以一目了然地了解每个测试实际执行的详细信息,而无需深入研究代码:
Test Runner

此外,根据每个程序集的测试结果,使用 t4 模板创建 HTML 报告。具有匹配故事的类都嵌套在一起,并且打印每个场景名称以供快速参考。对于上述测试,报告将如下所示:
报告示例

失败的测试将显示为红色,可以单击以查看异常详细信息。

差不多就这样了。我在我正在从事的几个项目中使用它,所以它仍在积极开发中,但我认为核心非常稳定。我正在寻找一种通过组合而不是继承来共享上下文的方法,因此这可能是接下来的变化之一。提出批评吧。 :)

I've been giving this sort of question a lot of though recently. There are a lot of reasonable options out there, and you can create your own easily, as displayed in some of the answers in this post. I've been working on a BDD testing framework with the intent being to make it easily extended to any unit testing framework. I currently support MSTest and NUnit. Its called Given, and it's opensource. The basic idea is pretty simple, Given provides wrappers for common sets of functionality which can then be implemented for each test runner.

The following is an example of an NUnit Given test:

[Story(AsA = "car manufacturer",
       IWant = "a factory that makes the right cars",
       SoThat = "I can make money")]
public class when_building_a_toyota : Specification
{
    static CarFactory _factory;
    static Car _car;

    given a_car_factory = () =>
                              {
                                  _factory = new CarFactory();
                              };

    when building_a_toyota = () => _car = _factory.Make(CarType.Toyota);

    [then]
    public void it_should_create_a_car()
    {
        _car.ShouldNotBeNull();
    }

    [then]
    public void it_should_be_the_right_type_of_car()
    {
        _car.Type.ShouldEqual(CarType.Toyota);
    }
}

I tried my best to stay true to the concepts from Dan North's Introducting BDD blog, and as such, everything is done using the given, when, then style of specification. The way it is implemented allows you to have multiple givens and even multiple when's, and they should be executed in order (still checking into this).

Additionally, there is a full suite of Should extensions included directly in Given. This enables things like the ShouldEqual() call seen above, but is full of nice methods for collection comparison and type comparison, etc. For those of you familiar with MSpec, i basically ripped them out and made some modifications to make them work outside of MSpec.

The payoff, though, I think, is in the reporting. The test runner is filled with the scenario you've created, so that at a glance you can get details about what each test is actually doing without diving into the code:
Test Runner

Additionally, an HTML report is created using t4 templating based on the results of the tests for each assembly. Classes with matching stories are all nested together, and each scenario name is printed for quick reference. For the above tests the report would look like this:
Report Example

Failed tests would be colored red and can be clicked to view the exception details.

That's pretty much it. I'm using it in several projects I'm working on, so it is still being actively developed, but I'd describe the core as pretty stable. I'm looking at a way to share contexts by composition instead of inheritance, so that will likely be one of the next changes coming down the pike. Bring on the criticism. :)

葵雨 2024-10-19 06:46:00

我的问题是 "something".ProveBy() 与稍后显示的文本不匹配(“当......它应该......”)。我认为BDD的理念是让测试措辞和测试报告尽可能保持相似。

My issue with this is having "something".ProveBy() does not match the text being displayed later ("When ... it should ..."). I think the concept of BDD is to keep the test wording and the test report as similar as possible.

被翻牌 2024-10-19 06:46:00

试试这个,

UBADDAS - 用户行为和领域驱动的接受故事

在这里找到 - http://kernowcode.github.io/ UBADDAS/

它会产生这样的控制台输出

I want to register a new user 
  So that Increase customer base
       As user
    Given Register customer
     When Confirm customer registration
     Then Login customer

Try this one,

UBADDAS - User Behaviour and Domain Driven Acceptance Stories

found here - http://kernowcode.github.io/UBADDAS/

It produces console output like this

I want to register a new user 
  So that Increase customer base
       As user
    Given Register customer
     When Confirm customer registration
     Then Login customer
夏有森光若流苏 2024-10-19 06:46:00

You might also have a look at the small library:
https://www.nuget.org/packages/Heleonix.Testing.NUnit/
You can describe tests in Given/When/Then or Arrange/Act/Assert styles.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文