BDD和“何时”的位置

发布于 2024-08-11 22:23:57 字数 777 浏览 2 评论 0原文

在我看来,我已经看到了两种 BDD 方法。差异取决于“何时”的位置:

在方法 1 中,何时是规范的一部分:

AnEmptyStack.isNoLongerEmptyAfterPush

在纯粹的“给定何时”术语中,这是:

“给定一个空堆栈,当它被压入时,它就不再是空的。”

因此,“when”是规范方法的一部分:

isNoLongerEmptyAfterPush(){
     stack.push(anObject);
     Assert.notEmpty(stack);
}

在方法 2 中,when 是在类级别定义的。也就是说,when 通常在设置中被调用。

class WhenAnEmptyStackIsPushed(){

   setup(){
      stack.push();
   }

   public void thenItIsNotEmpty(){
      assert(stack.notEmpty())
   }
}

有优选的方法吗?就纯粹的行为测试而言,第二个选项对我来说似乎更可取,因为测试装置的重点是行为。

但是,为了便于测试,我倾向于第一种方法。我在测试中发现的大部分痛苦在于设置。也就是说,我必须在特定状态下获得 SUT。一旦处于该状态,通常只有一行代码可以实际调用其上的某些行为。因此,每个类(即每个设置上下文)具有多个行为可以利用类的一次性设置。

所以,我正在寻找想法。一种方法优于另一种方法吗?

I have seen what it seems to me are two approaches to BDD. The difference hinges on the location of the "when":

In approach 1 the when is part of the specification:

AnEmptyStack.isNoLongerEmptyAfterPush

In pure "given when then" terminology this is:

"Given an empty stack, when it is pushed, then it is no longer empty."

So the "when" is part of the specification method:

isNoLongerEmptyAfterPush(){
     stack.push(anObject);
     Assert.notEmpty(stack);
}

In approach 2 the when is defined at the class level. That is, the when is usually invoked in the setup.

class WhenAnEmptyStackIsPushed(){

   setup(){
      stack.push();
   }

   public void thenItIsNotEmpty(){
      assert(stack.notEmpty())
   }
}

Is there a preferred method? In terms of pure testing of behaviour, the second option seems preferable to me, since the focus of the test fixture is on the behaviour.

However, for ease of testing, I'm leaning towards the first method. Most of the pain I find in testing is the setup. That is, I have to get a SUT in a particular state. Once in that state usually its only one line of code to actually invoke some behaviour on it. So, having multiple behaviours per class (that is, per setup context) leverages the one time setup of the class.

So, I'm looking for thoughts. Is one approach preferred over the other?

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

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

发布评论

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

评论(2

箹锭⒈辈孓 2024-08-18 22:23:57

根据您的测试框架,您也许可以两全其美。

当我围绕 sut 创建一组测试时,我首先声明一个类来包装整套规范,然后声明一个抽象类:

public class SomethingDoerSpecs
{

    public abstract class concern : observations_for_a_sut_with_a_contract<IDoSomething,SomethingDoer>
    {
        // here I can define setup that will be common to all subsequent tests
        context c = () => ...
    }

    public class When_asked_to_do_something : concern
    {
        context c = () =>
        {
            // setup specific to this context goes here
        };

        because b = () => sut.DoSomething();

        it should_open_a_database_connection =
             () => mock_db_connection.was_told_to(x => x.Open());

        it should_set_the_result_value_to_true =
             () => sut.Result.should_be_true();

        // etc.
    }

   public class When_asked_to_do_something_but_the_database_is_unavailable
        : When_asked_to_do_something
    {
       context c = () =>
         {
            // additional context
         };

         because b = doing(() => sut.DoSomething());

         it should_throw_a_custom_exception = () =>
         {
            exception_thrown_by_the_sut.should_not_be_null();
             exception_thrown_by_the_sut
                 .should_be_an_instance_of<CouldNotDoSomethingException>();
         };

    }
}

这只是为了说明测试类通常可以嵌套,因此您仍然可以执行“ big" When... 并在需要更大的上下文特异性时重用之前通过继承设置的状态。当然,您必须确保您的框架将重置断言集之间的设置。

顺便说一句,我在这里展示的整个委托语法来自 Jean-Paul Boodhoo 的 DevelopWithPassion.Bdd 库,您可以在 Github 上找到该库。

Depending on your testing framework, you can perhaps have the best of both worlds.

When I create a set of tests around a sut, I first declare a class that will wrap the whole set of specifications, then an abstract class:

public class SomethingDoerSpecs
{

    public abstract class concern : observations_for_a_sut_with_a_contract<IDoSomething,SomethingDoer>
    {
        // here I can define setup that will be common to all subsequent tests
        context c = () => ...
    }

    public class When_asked_to_do_something : concern
    {
        context c = () =>
        {
            // setup specific to this context goes here
        };

        because b = () => sut.DoSomething();

        it should_open_a_database_connection =
             () => mock_db_connection.was_told_to(x => x.Open());

        it should_set_the_result_value_to_true =
             () => sut.Result.should_be_true();

        // etc.
    }

   public class When_asked_to_do_something_but_the_database_is_unavailable
        : When_asked_to_do_something
    {
       context c = () =>
         {
            // additional context
         };

         because b = doing(() => sut.DoSomething());

         it should_throw_a_custom_exception = () =>
         {
            exception_thrown_by_the_sut.should_not_be_null();
             exception_thrown_by_the_sut
                 .should_be_an_instance_of<CouldNotDoSomethingException>();
         };

    }
}

This is just to illustrate that test classes can often be nested, so you can still do the "big" When... and reuse the state that you have set up previously by inheriting when you need greater context specificity. Of course, you have to be sure that your framework is going to reset the setup between sets of assertions.

By the way, the whole delegate syntax I'm showing here is from Jean-Paul Boodhoo's DevelopWithPassion.Bdd library, which you can find on Github.

疧_╮線 2024-08-18 22:23:57

我认为你的选择2是更好的选择。在我看来,每个测试类都应该将SUT设置为一种状态,然后每个测试方法都是对该对象的观察。我认为如果你在课堂上添加更多的观察结果会更有意义。如果每个观察只是观察而没有额外的操作,我想你会看到所有观察如何自然地结合在一起。

如果您选择替代方案 1,您不会对观察进行分组,因为它们观察同一对象(状态)的不同方面,而是因为它们具有一些您想要重用的共同初始状态。不要将测试分组以重用代码,将测试分组是因为它们属于在一起,并使用其他方式将代码重用为辅助类/方法甚至继承(即所有与堆栈相关的类都可以从创建空堆栈的类继承)。

I think your alternative 2 the preferable one. In my opinion each test class should setup the SUT in one state and each test method is then observations on that object. I think it makes more sense if you add a number of more observations to the class. If each observation is only and observation without additional actions I think you'll see how all observations naturally belong together.

If you go with alternative 1 you're not grouping observations because they observe different aspects of the same object (state) but rather because they have some common initial state you want to reuse. Don't group tests to reuse code, group tests because they belong together and use other means to reuse code as helper classes/methods or even inheritance (i.e. all Stack related classes could inherit from a class that creates an empty stack).

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