I've talked before about TDD (Test Driven Development), and how you end up testing your framework instead of the logic of your app.
In case you haven't read the previous topic, you can find it here: https://dev.to/0xrumple/when-tdd-doesnt-click-something-else-should-click-harder-2h9h
This time, I decided I will do testing no matter what, so I chose an approach that doesn't focus on mocks, fakes, interfaces, dependency injection... but it's all about checking the outcomes, and that's what BDD is for: BEHAVIOR.
Yes, I didn't allow myself to write this post until we (me and my creative friend @alhakem ) finished this dormitory management system (where students can search/reserve rooms and managers can approve/reject reservations and manage their dorms):
Here is the whole project, "features" folder contain the tests: https://github.com/coretabs/dorm-portal
Feel free to PR or create issues.
I worked on a bit complex filtering engine (where there are multiCheckbox/singleCheckbox/integer filtering criteria), and with BDD I finished it in about one week... all tests green, woohoo!
The whole project was done in about two months, and... all green 😉
It's really simple:
The common way is using Gherkin language, now don't get scared cuz it's really intuitive, just look at this sample (should be self explanatory):
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
Depending on your language/framework of choice this might differ; I'm using django (with python of course), and there is a great way of doing it using behave-django package.
Here are the above mentioned acceptance criteria implemented:
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
Now comes the part of writing your actual code, and the goal is to pass that quota test.
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
Nah! believe me, cuz I worked on other projects where adding features or changing one simple thing might break many other parts in your project, it literally becomes like:
You will realize that once you add X feature and the customers come to you shouting: "registration doesn't work"... IT SUCKS TO CODE WITHOUT TESTING.
- Gherkin language: https://docs.cucumber.io/gherkin/reference/
- BDD course on Pluralsight: https://www.pluralsight.com/courses/pragmatic-bdd-dotnet
- Different types of testing explained: https://dev.to/thejessleigh/different-types-of-testing-explained-1ljo