How Does TDD Really Work? (Pt 2)

Real world Test Driven Development

Sean
5 min readDec 19, 2023

How many web tutorials have you watched where someone builds a simple todo list?

How many simple todo lists have you been asked to build for money? Not many? No, me neither.

Previous episode

To work!

I’ve been working on an awesome fix for slow tests this week. Because I have to use e2e tests on my server rendered code I also have to write extra database provisioning for each test, which is taking ages. My pipeline takes about 40 mins to finish!

Also it was slowing down my development process because I’d have to wait an extra 5 to 10 seconds every time I wanted to re-run a test while my database population script ran.

So to fix it I’ve added a test only collection to my database that stores the last population script I ran. If it’s the same as the previous run, I don’t run the database population script again.

Now my e2e tests are running almost as fast as a unit test.

What does it look like?

Cypress uses a command called cy.task that takes a task name as a string. This matches a function in your plugins file to run server-side code. You can use it update your test database in any way you want, as well as other use-cases.

I’ve made a new task called checkIfRerun in my plugins file which accepts a task name and returns true if this task has already run and false if it hasn’t.

async checkIfRerun({ task }) {
const testState = await TestState.findOne({ task });
const rerun = !!testState;
return rerun;
}

I’ve also made a new database collection, only for test runs called TestState. This just holds a task string storing the name of the task that provisions my database.

Next I make another task called createTestState that updates this database record…

createTestState({ task }) {
return TestState.create({ task });
}

Now, in my test specs I can call these both in the before hook…

describe('my test spec', () => {

before(() => {
// Pretend this is my population task name...
const task = 'populateDbWithOrders';
cy.task('checkIfRerun', { task }).then(rerun => {
if (!rerun) {
cy.task(task).then(() => {
cy.task('createTestState', { task });
});
}
});
});

});

The job

Today I’m working on a new table for internal staff, so they can see any direct debit payment that’s failed. This can happen for various reasons but mainly because a customer has cancelled the direct debit from their bank and it’s difficult for us to detect automatically when that’s happened.

I’m not totally sure how this feature should look and there’s no criteria written (welcome to real world programming), so I go into the master branch and start playing around by creating a new route on the server first to see what I can retrieve from the database to make this feature useable.

This isn’t TDD but I find that if I start something like this by make a test first I won’t even know what it is I’m testing and I could just waste a lot of time hypothesising about what the table will have in it and then just scrap all that work later anyway.

Making a stub route

I make a route on my server that returns some json, at this stage I’m just forming the data to look how I want it to and working out which records I need to retrieve from the db and in what order.

After that’s done, I go further and start making the actual view, without going to any tests.

What are you doing!!

I know, this isn’t TDD any more. Here’s my issue… e2e. For me to write tests for this feature I have to have my database populated with a pretty complex set of records and I don’t have time to write test code that populates those records.

So, should I even be doing this feature if I don’t have time?

This is a problem I come across quite a lot. It’s one that I don’t think developers discuss often enough. When I’m working for a company that has a lot of resources I’m expected to write tests for all my features because the priority is this: can my work be maintained, added to and updated by other developers?

I agree that we should be writing code to a certain standard with certain shared principles because amongst other benefits it makes it easier for different developers to work in the same project together.

But I don’t work for a company with a lot of resources, time and money. I work for a small company where I’m the only developer. It’s my responsibility to make sure that my features allow the company to move forward. If I spend too long on a feature, it might not be worth the money the company is spending on me to make it.

Is this bad business management? Should my boss be prioritising different features that are worth the money and the time? Possibly. Although I’m not a business manager and it’s not my responsibility to decide. So, possibly not.

To test or not to test

Because of this conundrum I’m constantly weighing up the time spent making tests and time spent delivering new features/bug fixes. The balance is a difficult one to get right. Early on my tests where very detailed, I would test every single feature I wrote. But I found this was taking up way too much time and I’d often spend up to 90% of the time to make the test, which I think is a bit much.

Is this a problem with e2e? Often the time I spend on a test is taken up by populating the test database with the right combination of records. I don’t have a solution for unit testing pug templates at the moment so instead I’m opting to go lighter on the tests for some features.

My rule of thumb is this:

  • If I’m building features internal to the company then I can go light on the testing.
  • If I’m building for customers I write more tests and make them more thorough.

I continue to find that reading blogs, watching videos and following super-devs only gets me so far; and I follow their practices sometimes to my own detriment. It’s not that I think they’re not useful or give bad advice it’s more that the situations and jobs I find myself in are complex and unique. I’ve noticed that, in the past, I would attempt to find a one-size-fits-all format and try to impose that on situations where it wasn’t appropriate. I would waste time and energy and then have to backtrack to get out of trouble.

I think everybody experiences this to some extent. I know other devs that do the same thing, and I can see when it’s happening to them. A friend of mine recently started working in a company with a large team of developers. He said; it’s a relief to work with and for other devs because they understand how difficult things are and the pressure is way less because of that.

I think a lot of problems come from underestimating software development. It’s a complicated and difficult job, let’s just admit that first. If there was a technique for making it easy well, we’d start getting paid less for it that’s for sure!

Let’s all keep getting paid shall we? 😜

--

--