bdd 框架
I’m a big fan of TDD. BDD has always been a bit elusive to me in the sense that I wish I could use it more often. However, I always seem to end up struggling to express the same nuances through BDD that come effortlessly through a standard test case.
我是TDD的忠实粉丝。 从某种意义上说,BDD一直让我难以捉摸,我希望我可以更多地使用它。 但是,我似乎总是尽力通过BDD表达与标准测试用例毫不费力地产生的细微差别。
Truth be told, I have’t touched BDD in a while. However, this question has been bouncing around my head lately:
说实话,我已经有一段时间没有碰BDD了。 但是,这个问题最近在我脑海中回荡:
Why is it often so difficult to express simple systems through BDD?
为什么通过BDD表达简单系统常常如此困难?
Code based tests are really good at describing how we complete a task. BDD is really good at describing what a task is trying to achieve. After thinking about it, I came to the conclusion that the fundamental problem is state:
基于代码的测试非常擅长描述我们如何完成任务。 BDD确实擅长描述任务要实现的目标。 经过思考,我得出结论,根本问题是状态 :
B
乙
一个简单的例子 (A Simple Example)
Let’s consider a simple example of a bank account. Here’s the test:
让我们考虑一个银行帐户的简单示例。 这是测试:
And here’s one way we might express the same behavior in BDD:
这是我们可以在BDD中表达相同行为的一种方式 :
There’s nothing inherently wrong with either approach. However, the devil is in the details. This is a very simple, straight-forward example and yet I can already feel the abstraction gap taking me too far away from the code.
两种方法本质上都没有错。 但是,细节在于魔鬼。 这是一个非常简单,直接的示例,但是我已经可以感觉到抽象鸿沟让我离代码太远了。
As tests get more complicated (what I’m really talking about is increasing the amount of state) it becomes increasingly difficult to express with language. Eventually the language becomes more complicated to understand than the code itself.
随着测试变得越来越复杂(我真正要说的是增加状态数量),使用语言来表达变得越来越困难。 最终,语言变得比代码本身更复杂。
以前的方法 (Previous Approaches)
BDD scenarios don’t do a very good job at managing state because there is a disconnect between the language that describes the steps and the variables that a step implementation might want/need. I’ve seen two primary ways to approach this problem:
BDD方案在状态管理方面做得不好,因为在描述步骤的语言与步骤实现可能需要/需要的变量之间存在脱节。 我已经看到了解决此问题的两种主要方法:
将步骤直接保留为代码,并依赖变量的范围 (Keep the steps directly as code and rely on the scope of variables)
Some frameworks that work this way are Ginkgo and GoConvey. I don’t want to say this is a bad approach. I have use Ginkgo extensively in the past and ultimately this just feels like tests that are nested. Personally, it doesn’t feel like it’s separating the behavior from the implementation. It is a better way to describe steps of the scenario, though.
一些以这种方式工作的框架是Ginkgo和GoConvey 。 我不想说这是一个坏方法。 过去,我已经广泛使用Ginkgo,最终感觉就像是嵌套的测试。 就个人而言,这并不意味着它将行为与实现分开。 不过,这是描述场景步骤的更好方法。
代码生成器 (Code generators)
The godog framework is a great example of this. It takes actual cucumber feature files and generates a bunch of empty functions that you can place your implementations into. I personally haven’t used godog, but I’m very familiar with this approach from back in my days of using cucumber with Ruby.
godog框架就是一个很好的例子。 它需要实际的Cucumber功能文件,并生成一堆空函数,您可以将其放入实现中。 我个人还没有使用过godog,但是从我将Cucumber与Ruby一起使用的那一刻起,我就对这种方法非常熟悉。
I think this is great approach when the feature files are read or maintained outside of the code. This works really well for automation and processes that require a high level of abstraction in the first place. However, this doesn’t really solve my problem which is that I’d like to use BDD for the lower level stuff in my system.
我认为,在代码之外读取或维护功能部件文件时,这是一种很好的方法。 首先,这对于要求高抽象水平的自动化和过程非常有效。 但是,这并不能真正解决我的问题,因为我想对系统中的较低层次的东西使用BDD。
另一种方法 (Another Approach)
There is certainly merit in both strategies (and no doubt even more that I have’t seen) depending on works best for your workflow. For me, I wanted something that worked well for what I traditionally used to do with unit tests. Or, at least explore what this might look like.
这两种策略当然都有优点(毫无疑问,我什至没有看到更多优点),这取决于最适合您工作流程的方法。 对我来说,我想要一种可以很好地用于传统上用于单元测试的工具。 或者,至少探索一下它的外观。
I first want to be clear that these examples haven’t been battle tested in a large codebase so I can’t tell you the long term nuances and issues, but these are my initial thoughts.
首先,我想清楚一点,这些示例尚未在大型代码库中进行过实战测试,因此我无法告诉您长期的细微差别和问题,但这只是我最初的想法。
I’ll start by showing you the entire framework:
首先,向您展示整个框架:
Yep, it’s really that light. In fact, it’s mostly just a little syntactic sugar for writing the steps in a Gherkin-like way.
是的,真的就是那盏灯。 实际上,以类似Gherkin的方式编写步骤主要只是一点语法上的糖。
Now, back to our bank account example:
现在,回到我们的银行帐户示例:
Let’s express the two scenarios using this framework:
让我们使用此框架表达两种情况:
You can run the example here. It’s broken down into three parts:
The state (that’s the three variables at the top). These are shared by all steps, and can even be used as inputs for steps themselves.
状态 (即顶部的三个变量)。 这些由所有步骤共享,甚至可以用作步骤本身的输入。
The steps are functions or closures that are the reusable parts of your scenarios(s).
这些步骤是功能或闭包,它们是方案的可重用部分。
The scenarios at the bottom become the named subtests.
底部的方案成为命名的子测试。
I would recommend still using a testing framework like testify for the assertions and other features.
我建议仍然对断言和其他功能使用像testify这样的测试框架。
What I like about this solution is:
我喜欢这个解决方案的地方是:
- It doesn’t rely on any runtime magic, so you get better understanding and protection from the compiler. 它不依赖任何运行时魔术,因此您可以更好地理解和保护编译器。
No external tools, files or anything outside of good ol’
go test
. Each scenario is a subtest, and you can see that in the verbose output — if you’re in to that kind of thing.请勿进行外部工具,文件或任何其他未经
go test
。 每个场景都是一个子测试,如果您喜欢这种情况,则可以在详细输出中看到它。- I feel like I get the separation of the scenario from the implementation of the steps in a reasonable way. 我觉得我以合理的方式将场景与步骤的执行分开了。
注意事项 (Caveats)
- This is wholly untested beyond a few trivial examples. I don’t know how effective it would be with more complex setup. If this looks like something you’d like to try I’ve love to know if works (or doesn’t work) for you. 除了一些琐碎的例子,这完全未经测试。 我不知道使用更复杂的设置会有多么有效。 如果您想尝试一下,我很乐意知道您是否可行(或不可行)。
- Even though each scenario runs as a separate test, scenarios cannot be run in parallel and care needs to be taken when setting up structures so that tests don’t effect each other. This actually may be a positive if you want to maintain state between scenarios. 即使每个方案都是作为单独的测试运行的,但这些方案也不能并行运行,并且在设置结构时要格外小心,以使测试不会相互影响。 如果您想保持场景之间的状态,那么实际上这可能是一个积极的选择。
- Variables used in step closures need to be passed by reference (even if they are already a pointer). This is a pretty minor downside, but it’s worth noting. 步骤闭包中使用的变量需要通过引用传递(即使它们已经是指针)。 这是一个很小的缺点,但值得注意。
翻译自: https://levelup.gitconnected.com/an-alternative-approach-to-bdd-in-go-776bbbc24be9
bdd 框架