BDD 而非 TDD:面向结果的测试
TDD 不起作用,给我解决方案
我之前谈过 TDD(测试驱动开发),以及如何测试你的框架而不是应用程序的逻辑。
如果您还没有阅读上一个主题,可以在这里找到:https://dev.to/0xrumple/when-tdd-doesnt-click-something-else-should-click-harder-2h9h
BDD(行为驱动开发)来拯救
这一次,我决定无论如何都要进行测试,因此我选择了一种不关注模拟、伪造、接口、依赖注入的方法……而是全部关注检查结果,这就是 BDD 的用途:行为。
我試過了嗎?
是的,直到我们(我和我的创意朋友@alhakem)完成这个宿舍管理系统(学生可以搜索/预订房间,管理人员可以批准/拒绝预订并管理他们的宿舍)之后,我才允许自己写这篇文章:
这是整个项目,“features”文件夹包含测试:https://github.com/coretabs/dorm-portal
请随意 PR 或创建问题。
有趣的事实
我开发了一个有点复杂的过滤引擎(其中有 multiCheckbox/singleCheckbox/integer 过滤标准),并使用 BDD 在大约一周内完成了它......所有测试都通过了,哇哦!
整个项目大约用了两个月的时间完成,而且......全部都是绿色的😉
我该如何做 BDD?
这真的很简单:
1. 编写验收标准(场景)
常见的方式是使用 Gherkin 语言,现在不要害怕,因为它非常直观,只需看看这个示例(应该是不言自明的):
Feature: Reservation
Scenario: As a student
I want to make reservations
So that I get a room to stay in
Given we have 2 dormitories (and 1 room each available to reserve)
Given two students who reserved nothing
When create a reservation
Then quota of the room should decrease
2. 实施步骤
根据您选择的语言/框架,这可能会有所不同;我正在使用 django(当然还有 python),并且有一个很好的方法可以使用behave-django 包来实现。
以下是上述已实施的验收标准:
from behave import given, when, then
from api.engine.models import RoomCharacteristics
from features.steps.factory import (create_alfam_dovec_with_4_rooms,
create_student,
create_reservation)
@given('we have 2 dormitories (and 1 room each available to reserve)')
def arrange(context):
create_alfam_dovec_with_4_rooms(context)
@given('two students who reserved nothing')
def arrange(context):
context.user1 = create_student(context, 'Owen')
context.user2 = create_student(context, 'Tia')
@when('create a reservation')
def act(context):
context.previous_quota = context.room1.allowed_quota
context.reservation1 = create_reservation(context.room1, context.user1)
@then('quota of the room should decrease')
def test(context):
context.room1 = RoomCharacteristics.objects.get(pk=context.room1.id)
assert context.room1.allowed_quota == context.previous_quota - 1
3. 让它们变得绿色
现在到了编写实际代码的部分,目标是通过配额测试。
from django.db import (models as django_models, transaction)
from .exceptions import NoEnoughQuotaException
class Dormitory(django_models.Model):
name = django_models.CharField(max_length=60)
class RoomCharacteristics(django_models.Model):
allowed_quota = django_models.PositiveIntegerField(default=0)
dormitory = django_models.ForeignKey(
Dormitory, related_name='room_characteristics', on_delete=django_models.CASCADE)
def decrease_quota(self):
if self.allowed_quota == 0:
raise NoEnoughQuotaException()
self.allowed_quota -= 1
class Reservation(django_models.Model):
user = django_models.ForeignKey(
User, related_name='reservations', on_delete=django_models.CASCADE)
room_characteristics = django_models.ForeignKey(
RoomCharacteristics, related_name='reservations', on_delete=django_models.CASCADE)
@classmethod
def create(cls, *args, **kwargs):
room_characteristics = kwargs['room_characteristics']
result = cls(*args, **kwargs)
with transaction.atomic():
room_characteristics.decrease_quota()
result.save()
room_characteristics.save()
return result
真的吗?听起来太无聊了!
不!相信我,因为我在其他项目中工作过,在这些项目中,添加功能或更改一个简单的东西可能会破坏项目中的许多其他部分,它实际上变成了这样:

图片来源:giphy
您会意识到,一旦您添加了 X 功能,客户就会向您大喊:“注册不起作用”......未经测试的代码很糟糕。
何时进行 BDD 而不是 TDD?
参考
- Gherkin 语言:https://docs.cucumber.io/gherkin/reference/
- Pluralsight 上的 BDD 课程:https://www.pluralsight.com/courses/pragmatic-bdd-dotnet
- 不同类型的测试说明:https://dev.to/thejessleigh/different-types-of-testing-explained-1ljo