How TDD and BDD Cultivate an Agile Environment

Kevin Hu
9 min readNov 17, 2018

--

Photo by chuttersnap on Unsplash

Applications take a long time to write. Unit testing is like the 1000 word extra credit essay offered within a grueling exam. You don’t know how long it’ll take to make it through this exam, so why would you even entertain writing this essay. This could be true if this essay question was on a completely isolated and unrelated topic to the rest of the exam.

What if I told you that unit testing actually solves many of the problems that you have yet to encounter? What if I told you that front-loading the additional time with Test Driven Development and Behavior Driven Development could actually reduce the overall amount of time it takes to write your application? Have I piqued your interest yet?

First, what is Test Driven and Behavior Driven Development?

These two approaches to development often work in tandem with one another. The best TDD is informed by BDD and vice versa. Both asks the question, what is your goal for a particular piece of your application?

Test Driven Development (TDD) is purely technical and operates with a very formulaic procedure. Talk through the test, write the test, fail the test, pass the test, and if necessary refactor the test. Wash, rinse, repeat. The two elements that TDD primarily cares about is the input of the test and the output of the test. Let us take a look at a very basic example of this using Mocha Chai to test our database models:

  1. Talk — We would like a Product model that has a name. The type of the name should be a string and the name should be unique.
  2. Write — Mocha Chai has a three-tier structure that is very easy to use. Write a describe block that passes into the parameter the description of your test, an it block that passes in the description of the particular piece you are isolating to test, and an expect block that passes in an input and the output expected. This third tier is called the assertion and mocha offers 3 assertion libraries between expect, should, and assert. For my purely preferential purpose, we will be using expect. When the blocks are written, it’ll look like this:

3. Fail — For now, don’t worry about the beforeEach hook in the describe block. We will get back to that later when we talk about organizing scripts. So, the tests are failing at this point because our Product model is not yet defined. This is what my prompt is telling me:

4. Pass — We want to see two things from our Product model — that it is a type string and that it is unique. Let’s define the Product model with a name field that has a type string. We see now that one test is passing:

5. Refactor — Though our model meets the criteria of the first test, it needs to also pass the second. Let’s add the validator for an instance to be rejected if it is not unique and test it again.

Now our tests are passing. This basically is the cyclical process we walk through as we write out each of our model definitions.

Behavior Driven Development (BDD) tries to solve kind of what it sounds like it does. It imagines the code in a realtime environment with realtime variables. Under various conditions, what is the output we would like to see? BDD, unlike TDD, does not aim to answer the technical questions of how an output is to be produced through written code and does not assume the kind of linear flow of TDD — if a variable goes in this way, it’ll naturally come out that way. In fact, BDD does not require any written code. BDD simply wants to know whether or not we can produce the desired output under any of the possible conditions this feature might encounter within the application. Hence, how it behaves.

So the lifecycle that unit testing might look like:

  • First round of TDD for basic model definition and RESTful APIs
  • Initial round of BDD to ask what conditions our data might encounter in realtime
  • Refactor code to make code dynamic so that we can consistently see the desired output under any condition discussed in BDD

Now what is Agile Development?

In the shortest summary possible, Agile Development is a philosophy of development that operates with a cross-functional, self-organizing team which aims to bring the product owner continuous delivery of said product — in our case, the software or application we are writing. This is often times done through a methodology that is popularly referred to as a Scrum Framework. The Scrum framework is essentially a blueprint of best practices that include a backlog, sprint, daily sprint (check-ins), and retrospective (or review). A more thorough review of agile can be read in the Agile Manifesto.

When Agile is your modus operandi in any team, each team member tunes their own work to the grand scope of what the product intends to provide. Work will continue to be done in these isolated sprints to maintain modularity, but not without the lifecycle of this specific sprint throughout the entire application.

Without Test Driven and Behavior Driven Development, it would be easy to ignore the Agile mindset in order to check items off the list. TDD and BDD doesn’t allow you to write any piece of code until it is 1) working properly without breaking your console, and 2) functioning the way it needs to for your specific purposes.

Let us hark back to our Product model example from earlier. We are building an e-commerce application where each product will have many Reviews. We are not sure how the Front-end team wants to handle loading the reviews. We know that each time a single product is requested, it’ll need access to its reviews, but there would also be many ways to go about this. We ask our Front-end team and find out that they do not want to make too many unnecessary calls to the server so we decide it would be best to eager-load what all the products will need from the initial call to retrieve all the products. We end up defining our model this way:

This seems like a very minute detail. I mean, we barely added a line of code. But if we decided how we wanted a specific piece of code to perform its output through its entire lifecycle in an application with TDD and BDD as we build the backend of our application, we are already self-organizing, self-tuning, and cross-functioning because we are never isolating just the technicalities of our specific task. We are building the backend with a fullstack mindset.

In this specific scenario, if we were not to take the steps that we did, we will still eventually have to load our reviews with our products. But it’ll just be in a much less controlled environment. A bug will communicate this to us. We will take the most immediate steps to bandaid our problem, but will likely skip the question of whether or not we prioritize less or more calls to our server. This may lead to more AJAX calls and a slower program altogether depending on how big the application is.

When we begin our applications using BDD and TDD, it lays the groundwork for us to naturally function with an Agile mindset.

Now, a note about organizing scripts

Unit testing can get messy when we begin testing too many models and too many route files. Remember that beforeEach hook that I said we would return to? Well, now is that time. When we test with Mocha Chai — and another framework we did not have time to cover, Supertest — what we are attempting to do is to mock or simulate a real environment so that we can isolate our features for testing. In an e-commerce website, we might see Product Review LineItem and User models. Likewise, we’ll likely have 4 files for the APIs for each model. Throughout testing, lots of instances will be defined and we don’t want to have to keep track of what is in our database already and what isn’t because our goal of mocking the environment really is to isolate very explicitly what we are testing.

What I am requesting is before each it block that follows, I want the describe block to return me a newly sync’d Product model. Including the {force:true} simply asks to wipe out anything already in the database within this model. Another way to wipe out the database is instead of including a {force:true} we can include an afterEach block following the beforeEach that requests for the Product model to be truncated:

If we had one behemoth file for testing every model and every route, we can safely write one script for testing in our package.json. But then we would not be following the principle of Modularity which produces incredibly disorganized code. Otherwise, we will end up having multiple testing files that each have their separate beforeEach and afterEach hooks. How should we go about testing these files on our script?

If you wrote a script that tested more than one file at a time that will resync the same models and/or db, your bash shell will likely scream at you with a message like this:

Our database gets a little confused when we try to resync it or wipe it from multiple places at once while running each test. All of the tests could possibly be written correctly, but our database is receiving too many of the same requests all at once from different files so it’ll go berzerk. My script currently looks like this:

If you look all the way at the last script "test": "mocha -w db/testing/*, we are asking the test to run on the mocha framework on all files within the db/testing file path, and for it to be continuously watched for saved changes. In view of our multiple testing files, this script will attempt to run 4 model files at once and this will be a constraint disaster.

Modularizing your testing scripts as shown above and testing each model and each API file in its own script often times guarantee no constraint errors unless they are happening within the it blocks of a certain file. Only one sync request will be sent to the database at a time.

Modularity can also be done by including all model tests in one db.spec.js file and all API tests in one api.spec.js file, each with their individual scripts. Or if you’re getting really fancy and want to use Enzyme for front-end testing, you can include a test:Client that specifically looks at your client-side testing file.

Conclusion

An Agile Development Environment cannot be guaranteed by a written formula of practices. Each team will have their own rhythm of what works between individuals in the team and how to create their own Scrum workflow. But if we want to operate with the DNA of Agile from the genesis of our project, TDD and BDD will be an excellent launchpad.

--

--

Kevin Hu

Writer, Storyteller. Writing has appeared in Asian American Writers Workshop, Inheritance Magazine, The Lit Pub, amongst others.