Why two databases are better than one
Welcome to No Compromises. A peek into the mind of two old web devs who have seen some things. This is Joel.
And this is Aaron.
I am not too proud to admit that over the many years now, Aaron, that we've worked together, there have been ideas or practices that you convinced me of or introduced to me that maybe even at first I resisted, and in hindsight, I adopt them as my own now. And now I go on podcasts and talk about all these amazing ideas I have, but really, they came from you. How do you feel about that?
That's great. I'm happy to participate and contribute to the community.
Okay. Today is one example of that. The topic specifically is related to testing but even more specifically about having a separate test database from what you use from local development. And I'm thinking of a project in particular, which actually was my very first Laravel project. This is 10 years old, maybe. Maybe even a little bit older than that, the years keep ticking on here. But it's where I cut my teeth on Laravel, so that's my hedge for like if I did something dumb, you can't really make fun of me because it was my first one. So let's get that out of the way.
Right. And then every once in a while, I'd be like, "Come on, Joel." And you're like, "Look at the git commit history, it probably wasn't me."
I know. And then sometimes it is and I'm like, "Don't look at that because I just applied a coding standard. I didn't actually write that," anyways. But I was happily, blissfully unaware of why I might want to have a separate database. And I will tell you, this was a shift that when I made it, it very quickly made sense to me and I immediately saw, "Oh, this is really nice." But at the same time, I don't see a ton of teams doing this. So it might be good for us to talk through, like, why do we like to separate our test environment from our local development environment.
Well, I think the very first thing, I want to put a little tiny bit of context in here and tell you a little bit about my rationale and reasoning before you go out and explain kind of the rest. Is, I have a reasonably good memory I think, but the way I work is I want to get to a point when I'm done working on the project, even for the day, I don't have to remember anything anymore. So I always want to be in little... I always want to set up every project and be in every single workflow where I can always just be like, okay, when I'm done, I can be done. And when I come back, I don't have to remember the context because guess what, I won't. I won't remember. So I can just do a set of tasks to get me back to a starting situation. And for me, a lot of times that set of tasks is I'm going to pull the most recent branch, do a composer install, an npm install, build my assets, do a set of data migrations and seed, and I'm good to go. I've blown away everything, I've updated everything. I'm starting from fresh and that's my day for my development on this project because maybe I haven't even been through it for a week or so. I think we've talked a little bit about this too. That's kind of a process that, you know we're known to do now. But that's a process I've been doing for a long time, which is like I just want to blow everything away, start from fresh because I remember the pain of coming into projects that aren't ready. So I always want to always make sure my projects are ready to do that. I just wanted to share that context because that's important to understand that I came in to start working with you with this context in mind and you had the alternate thought. Which is, I have this test, I have this database locally that has a bunch of scenarios set up that I know about because I've been doing local development. Why would I not write my tests against this scenarios that I've set up because I've been working on it locally and I know my local test data?"
It is interesting because as you were saying that, I never directly related this topic to that broader framing. I think it's useful and it kind of has inspired a couple thoughts for this conversation. Because one of the things I noticed you didn't say, but maybe you do this when you're kind of rejoining a project for the week or day or whatever. After you do all those setup things, do you also make sure the test pass or is that?
Yeah. I run the test and the coding standards and I just make sure everything that is available to the project is good and fresh and new and running.
And I've picked that up, and again maybe I picked this up from you, but it's a sanity checkpoint that there's nothing worse than starting a feature branch and running into something that's broken and thinking you broke it because of the feature work you're doing. And no, it was broken in the branch you checked out or you had something goofy in your setup that causes it to break. So I love just running that. We call it composer ci, it runs all the checks. You know, the PHPStan coding standards, all the things that will run in ci tests just before I make a single change just to know I'm starting from a clean foundation. So the piece that you were talking about where you like to start fresh, there's an implicit, "I can't start fresh approach from how I was doing it." Because my tests assumed a certain shape of the database and really even my local development practices kind of like, "Oh, here's an admin user I can log in as. Here's, for this particular environment, like a student versus an employer," you know, all these different things. And if I blew that away that was bad. So this broadens out even further. My tests would not use things that we use now, like refresh database, because I didn't want to blow away my dev database. So I was sort of in this gridlock of needing the database to be in a certain way and I got it that way manually. And that was this whole set of constraints that I was living within that I never stopped to question. Is that a fair way of summarizing it?
Yeah, I would definitely say that. And when you say manually created it, I mean you either created it in a database yourself or you did it through the UI of your system through multiple clicks and setups and stuff. Things you wouldn't want to do again.
Yes, exactly. So, all right, that was the starting point on everything that shifts as a result of separating these two databases kind of grows out of that. So by separating tests... Let's just use a specific example. Let's say our database name n.env is called app, right? That's kind of the default for Laravel anyways. Well, in our test configuration, we'd call it app-test. So it's still talking to MySQL if that's what we're using in our project. It's talking to the same database host, it's using the same database user and password. All we've done is we've created two databases. We have one that we're going to use for tests and one that we're going to use for local development. But as soon as I make that switch, the first thing I get out of that is I get way less flaky tests because I can use that refresh database trait, which gives... This connected the dots for me as you were talking. Just as we talked about liking that fresh starting point for our development, why not have that fresh starting point literally for every test that runs in our suite? Because as soon as something bleeds through from one test to the next, that's setting this up for a flaky test. Where, you know, on February 28th on a leap year, it's going to fail because of this or that or the other thing. Or, if we run the test in a different order they're going to start failing. So that makes a lot of sense to me and that is the biggest value I have felt by separating these out. The other thing I'd want to talk about was kind of the flip side of this. Which is, by having the two separate databases now... Okay, the test database can get blown away, it literally is getting blown away to a certain extent for every single test run. But now my dev database, in order for that to be done efficiently, I have to have a good set of factories and some reasonable states to make it easy to set up all these different scenarios in my tests because I can't rely on seeded data. But once I have all those handy factories, now I can create a dev data seeder. And I'm saying dev specifically because it only runs for our local development environment and not for our tests. But now all those little manual things I was doing to create a user of this type or a user of that password is not set yet, or a user that's subscription has expired. Like, all these different scenarios probably line up with the factories I created so now it's just a matter of creating a seeder and doing that so I don't have to worry. Now both my dev and my test database, I can blow them away or somebody new joining the project can clone it and get all of that set up on day one for them to use as they're working in the application.
I think that's what I said at the beginning, right?
It is. I'm going full circle because at first... I'll be honest-
Oh, it's your idea now. Okay, got it.
Yes. It sounds better when I say it. This works in my marriage too but anyways. I'm trying to think of a way to phrase this. But I didn't really... I always thought if you have them combined, it's saving you effort. But I guess what I'm saying is it is to a certain extent, but by putting in a little bit of effort and that effort is setting up the factories and then for your dev environment, the seeder, it's sort of a one-time investment that really accelerates your speed going forward and eliminates a lot of flakiness.
Yeah, there is a tipping point, I think, on, like you said, where it's saving you effort or not, and that tipping point of having maybe your development combined with your testing database. And this is before you've developed this muscle, you know? Like, for me now it's harder to use this same database, I don't think I'd ever want to. But let's just say that you're in that situation where you are that tipping point where it's much harder or where it'd be easier if you just had your separate database, it's probably already passed you.
Probably. Yeah, that's a good way of looking at it. What is it? Sunk cost fallacy. One of these logical traps where it's just like, "Oh, I don't want to spend time on this," but every day you are spending time on it by default. One other thing, and this is kind of a side point. But I was trying to think of one other point why having a separate test database is good, as hard as it is for me to believe. But I've seen this in practice with projects we've joined, where their migrations literally don't run or they run and they don't produce the database that's being used in production. So the reason I think that's connected to this... First of all, I hope everybody sees the danger in that scenario. Like, you can't trust your migrations, you certainly can't rely on them for local development. But by forcing your tests to create the database fresh each time, hopefully, if you're writing reasonably effective tests, you'll catch those things and you'll have to fix them. And it will kind of bring the quality of your project up as a whole for the team.
So I know we as humans try to fit in a lot of times when we're in a group of people or something's going on. You know, the whole thing in the elevator, if someone walks in you all look the same direction? That sort of thing. We're all trying to fit in, we're trying to get along. We do weird things though that if you really stop and think about it, they are weird but I don't know the alternative, what we would do instead. So one of them that I find really awkward, and maybe you have an answer to me because I've had this happen for me. That sounds like I'm going to be bragging now and I don't mean that. But when you are in a group of people and you're being recognized for something and they all start clapping for you. What do you do? I've noticed that a lot of people will start clapping themselves as well. Like, if everyone's looking at you and you're just like... The whole room is clapping and they're just like, "Oh, I'll start clapping too." And then there's other people that look at like, "Really?" Oh, look down, look embarrassed. Like, what are you... Are you supposed to wave? What are you supposed to do when there's a room full of people clapping? Let's just say, you've done talks, Joel. Do you bow? What do you do?
You just walk off the stage, you just leave the room. No, for sure if it's literally like a speaking engagement where there's an audience, I think it would be very weird if you clapped along. But if it's more of an informal, like I'm in the group. When you said that, the first thing I thought of, I watched Jeopardy. A lot of times the winner will also clap but it's sort of, "We all did good." But if it's specifically about you, I guess-
That Jeopardy thing feels weird because you know that there's two people that are clapping that didn't win, that are probably just grinding their teeth going like, "Yes, let me clap." And maybe then there's a person who won that's happy they clap at all, so, "This is awesome. I have all this money and now I have to clap and look forward to smile and not look left or right to the people I demolished."
I think it's worth the little bit of awkwardness to win. But in the social setting I don't think I would join in with the clapping, but I probably would deflect or something. But there's no good answer here. I just want to say one more thing though. When you set up the situation and you mentioned the elevator, I thought, how completely unhinged would it be if I walked into an elevator and there was three people in it and I just continued facing them? That would deeply unsettle me. I'm going to be thinking about that now as I go to sleep.
You should do that and then start clapping. We talk about a lot of things in a newsletter, not just testing.
If you'd like that in your inbox each day, head to masteringlaravel.io and sign up for our daily tip newsletter.