Django supports testing out of the box with some cool extensions to unittest. However, many people are using pytest for their Django testing, mostly using the pytest-django plugin.

Adam Parkin, who is known online as CodependentCodr, joins us to talk about migrating an existing Django project from unittest to pytest. Adam tells us just how easy this is.

Transcript for episode 110 of the Test & Code Podcast

This transcript starts as an auto generated transcript.
PRs welcome if you want to help fix any errors.

00:00:00 Django support testing right of the box with some cool extensions to unit test. However, many people are using Pi test for their Django testing, mostly using the Pi Test Django plugin. Adam Parkin, who is known online as Codependent coder, joins us to talk about migrating and existing Django project from unit test to pytest. Adam tells us just how easy this is. Thank you, Patreon supporters, for your continued support of the show. And thank you, PyCharm for sponsoring this episode.

00:00:40 Welcome to Testin Code Python testing for software engineers.

00:00:45 Welcome to Test and Code. I am really excited today to have Adam Parkin on the show. Welcome, Adam.

00:00:51 Hey, thanks for having me.

00:00:52 And you go online. You go by codependent coder. Is that correct?

00:00:56 That’s correct. Okay, that’s correct.

00:00:58 We’re going to talk about testing and Django and just run from there first. Who are you? Adam, what do you do?

00:01:05 Hi, I’m Adam. I’m currently a senior software developer at the company called Bambora. We do online payments. And as you mentioned, like in my spare time, I have a blog called The Codependent Coder where I kind of write a bit about things I’ve learned mostly in Python because that’s sort of where my passion lies and also testing and other topics like that.

00:01:24 Okay, I think you have an explanation somewhere about why codependent coder.

00:01:28 Yeah, that’s kind of a funny story. So years ago it was actually one of my first tech jobs and a colleague of mine put up a pull request and was requesting a review on it. And she said to me, Adam, can you please review my code? Because the codependent coder and me needs validation and something about that, just like the name, just like it sounded really kind of funny and just jive with me at the time. I want to start a Twitter handle that was for tweeting out developer related stuff. I already have a sort of a personal Twitter account, but I want something where I could just post stuff related development.

00:02:00 So I went to get the Twitter handle codependent coder, like with Er at the end. But unfortunately that was just a little bit too long for a Twitter handle, so I had to drop a letter. So I dropped the E between the D and the R at the end. That was how it started.

00:02:14 Okay, so you’ve got two words, codependent encoder, and the coder is the one you thought to make shorter.

00:02:21 Yeah, I kind of regret that decision, actually. And the domain for my blog is the same thing. It’s like codependent coder, like Codr. I should probably should grab the domain C-O-D-E-R as well, just so that there’s less confusion.

00:02:35 Yeah, that’s fine. It works for me. Anyway, so we were going to talk about Django and testing, but do you use Django mostly on personal stuff or do you use it at work?

00:02:47 Kind of both. I’m at now. When I started there the team I landed on, we had five different little microservices that we were managing, and three of them were at that time were written in Django. The other two, one was written in Flask and one was actually an AWS Lambda function. We eventually migrated the two of those to also be Django projects. At this point, all five are Dango based, and most of my day to day development work is in Django.

00:03:10 Now, even some applications went from a Flask service to a Django micro service.

00:03:16 Yeah, we did that because we sort of reached a point where there’s a bit of a cognitive switch when you switch from one framework to the other. Right. So most of our work was being done in Django, and then there would be the odd ticket that would come up and we’d have to do something on that service with certain Flask. And then you’d start writing some code and you’d do the Django thing and you’d be like, oh, wait, this isn’t a Django project. Then you’d have to make that mental switch in your head. Okay, now I have to do this the Flask way.

00:03:37 Okay.

00:03:38 That sort of overhead kind of became significant over time. And so we wanted to sort of have sort of this uniform structure in all our projects and sort of that sort of same familiarity if you jump from one project to another. And so we just aligned on Django, that makes sense.

00:03:52 I think that I wanted to touch on that and come back to it because the time and mental energy to switch back into different mindsets is nonzero.

00:04:03 It makes sense. Even if any particular project have made more sense on a different or just be fine in Flask or in any other framework or maybe Fast API or something. Sometimes it’s not the computer time or whatever that makes more sense. It’s the people involved.

00:04:21 Yeah.

00:04:23 Okay. Sorry I cut you off, but you also I think you were continuing on. You use it for personal stuff as well. Yeah.

00:04:31 Just because I’m kind of a fan of the framework, when I do stuff in my personal time, it tends to be Jangle related as well.

00:04:38 Okay.

00:04:38 Just because I’m a big fan of the framework.

00:04:39 All right, cool. I got to admit, I’m very much a novice. I’ve used it just a little bit, and people that listen to the show are probably annoyed that I’m saying that I’m just getting into it and have been a novice for about two years now. But I’ve got a lot of other stuff going on, man.

00:04:55 Honestly, I can relate to that. I do it for my day job, and I still feel like I’m learning parts of the framework every day.

00:05:00 Yeah. With testing wise, you showed me a little example of some example test and code, some of the differences between testing Django with Pi test and unit test. So let’s talk about that a little bit about that transition.

00:05:18 Yeah.

00:05:19 So by default, like a normal Django project, like if you read the Django documentation, the Quick Start tutorial, it walks you through writing some unit tests for a basic Django application.

00:05:28 And sort of the convention in Django land is you use the classic Unit Test style. You create a class inherits from a particular class, and then your tests or methods are defined on that class.

00:05:40 In Djangoland, they actually provide you a couple you don’t inherit from the Test Case class in the Unit Test library, you actually inherit from a class called Test Case that’s in the Django provides that actually inherits from the Unit Test Test Case class. This inherited entire car. I’m waving my hands here. It would be nice if I could draw a picture. But anyways, this Django Test Case class that they provide provides a whole bunch of little nice things for you. Like, for example, the set up and tear down on that class will spin up a database for you and tear down that database when you’re done. There’s a sort of a client object that it provides that allows you to sort of simulate Web requests that’s very handy for testing, in particular your views.

00:06:20 And all those niceties come for free with you if you just inherit from that base test class, Test Case Test class that Django provides. And so that’s fine. When I started at the company I’m at now, we had these projects that were written in Django, and they were all using that default test runner in that Django comes with.

00:06:38 And we reached a point where rather I reached a point where we were doing performance goals for the year. And I had worked on another project where we decided to use Pi Test. And I was really enthusiastic about that tool, and I wanted to try using it, bringing that tool to the projects where we were now managing. And so I set a performance goal for myself that I negotiate with my manager to transition all these services. We had to using pytest as the test runner. And we started down that journey, and it started with actually one of those microservices that was not Django based. A colleague of mine did the work to convert to a Django project, and he put the code up for review.

00:07:16 And I thought, well, this is my perfect opportunity to try and convince somebody to start switching something to Pi Test. And I left a comment on the review saying, So I see right now it’s using the default test runner. What would it take to switch this to Pi test? And it was really interesting because initially there was a few people who are very resistant to that change. And I think a lot of that is just one of the things about Django is it’s a very opinionated framework. And so if you do things the Django way, it tends to be very easy and you’re working with the framework, whereas when you try to sort of move outside of the sort of norm with Django, it tends to feel very uncomfortable and awkward and it feels like you’re fighting the framework. And I think there was a perception that if we switch the Test Runner from the one that Django provides to Pi Test, we’re going to run to that tension and that friction. There was initially a bit of resistance to that. And so this colleague of mine, he was great about it. He actually took the time to dive into trying it out and he sat down and he took this project and switched the Test Runner to Pi Test. And he actually found that it was actually quite easy.

00:08:15 And so it was a nice, really early, easy win.

00:08:18 And that’s what got us started on that path. And then throughout the rest of the I think it was a few months. We eventually migrated all five services to now they’re all using pytest as the Test Runner.

00:08:27 Okay, when you say Test Runner, is it also switching the test to not use the Test Case pattern?

00:08:35 That’s a good question. We haven’t rewritten any of the existing tests. We’re now at the point now we’re in the last month or two, we’re starting to it really depends on the person writing the code. Like some people are still writing tests that inherit from Test Case and following that stone, that’s fine. That works, right? Pi Test will happily run those unit tests for you. Yeah. And some people are really gravity like myself included, are gravitating towards the Python. You write test functions. And we haven’t gone back and rewritten the old test, largely because there really hasn’t been any need to. I mean, Pi Test will run those fine and they still fail when they are supposed to fail and pass when they’re supposed to pass. So.

00:09:13 Thank you PyCharm for sponsoring this episode. Pycharm 2021 is out and oh, what a treat. Get integration was already amazing, but now it’s even better. You can do interactive rebasing. That’s cool. You can choose to have the commit window appear as a tool window next to your code instead of a pop up. I really like that branch. Searching is now easier and you can even install Get right from PyCharm. Heck, you can now even install Python right from PyCharm. There’s improvements to the virtual environment support and improved support for adding packages to requirements text files if you use those. There’s even a new PyCharm command line tool that opens a light edit version for quick edit jobs that don’t require all the bells and whistles. You can split the terminal window now to see the output of two commands at once, you can configure the status bar. There’s improved database support with SQL script run configurations. They’ve even improved the font with the JetBrains mono type face. Now that’s attention to detail. You got to check this out and you may as well start with the pro version. It’s free for four months only if you go to Test And Code. Compycharsavetime. Use PyCharm.

00:10:18 Yeah. The initial push for that initial pilot project was just switching the runner over and running pytest instead of unit test.

00:10:27 Yeah.

00:10:28 Okay.

00:10:29 For a non web project, that’s like a non zero. I mean, that’s just a pretty easy switch.

00:10:34 Yeah.

00:10:34 You just run by test instead of unit test on the command line or whatever. Is it more than that with Django then?

00:10:41 Potentially.

00:10:42 So for the most part, no. For the most part, you can just go Pi test space and then point it at the directory where your tests are and it will just run the existing tests where you might run into issues is if you write Pi test style unit test and code want those Django niceties, like that client object or having it set up and tear down a database before every test.

00:11:08 That’s all defined on that test class that Jenko provides. So if you’re writing test functions suddenly, well, you don’t have those niceties anymore.

00:11:15 All right. Yeah.

00:11:16 So the question kind of becomes I want those things. Those things are very, very helpful for writing robust tests. So how do I get those if I’m going to write Pi test all tests? And that’s where the pytjangle plugin comes in.

00:11:29 Are you using the pytestjangle plug in then?

00:11:31 Yeah, technically speaking. Like if you write all your tests in the sort of classic you inherited from test case style, pytest will run those fine. So you technically don’t need pytest Django if you’re going to adhere to that style. But if you’re using Pytest and you want to go full bore on using Pytest and you want things like parameterized testing and all those niceties that Pi test provides, you pretty much at some point have to start writing Pi test style test like the test functions, in which case you kind of then do need Pytestjango if you want all those nice cities that you get for free with Django.

00:12:06 Okay. So pytestjango will provide things like ways to start up your database and things then.

00:12:10 Yeah. So the big one is it provides a pytest Mark. I think it’s Django. Db. And so if you go like Pi testmark equals Pi test Django underscore DB. Now all the tests in that module will get that database set up and tear it down for free.

00:12:24 Okay.

00:12:25 You can also apply it as a decorator on an individual test if you prefer that. That might can be useful for like if your test doesn’t actually need a database, then need that Mark. And obviously there’s a cost associated with that database set up and tear down. So you can avoid that overhead if you don’t need it, which is nice.

00:12:39 You get that with a Mark. That’s cool.

00:12:42 Yeah. That’s an interesting style.

00:12:44 It is, actually. That was one of the things that a lot of the people who are less familiar with pytest. When we first started putting that in some of our test classes or, sorry, test modules, they kind of look at that line and it’s like Pi test Mark equals this weird identifier and be like, well, all it’s doing is this weird assignment at the top of the module that shouldn’t have any effect.

00:13:04 And so then they comment that line out, run the test, and everything blows up.

00:13:10 So there’s a bit of a surprise there for a lot of people, but it’s a learning opportunity, right?

00:13:14 Oh, yeah. That’s cool. Has everybody jumped on the pytest bandwagon?

00:13:19 That’s a good question. I think so.

00:13:22 For the most part, if you don’t want to write pytest style functions, you still don’t have to. You can write tests the way you did before, and that’s fine.

00:13:30 There’s nothing wrong with that. And you can do things the way you have always done, and it just works.

00:13:35 But it means that now people who want to take advantage of those niceties, they can. And some people are starting to dabble with that. Like, we actually had our first pull request with some parameterized tests that went up. I think it was like a month and a half ago or maybe two months ago. And so we’re starting to see take advantage of those things, which is kind of nice.

00:13:53 All the tests, if you’re setting up a database at a module level, do all the tests within that module have to play nice and make sure they don’t mess each other up. How does that work?

00:14:02 Are you speaking specifically, sort of, about the database aspect?

00:14:05 Yeah. Is it a persistent database for all of the tests going on?

00:14:10 Sorry, I guess I could explain that a bit better. So in stock Django, when it creates the test database, when it starts the test run, it spins up a database, applies all the database migrations to that database to put it into that sort of initial known state.

00:14:25 And then in the setup of each individual test, it will start a transaction, run your test, and then the tear down will be rolling back that transaction. So it only creates the database once for the entire test run, and then each individual test is run inside a database transaction, which ensures that sort of isolation between tests.

00:14:42 Okay, cool. The Pipest plugin do that as well.

00:14:46 It does.

00:14:46 Okay.

00:14:47 It does. Once you add that market, it has the exact same behavior.

00:14:51 Okay, so is there a way? Let’s say I’ve got some. I guess all the migrations. Is there a way to set up your initial state so you can have a non empty database for a whole set of tests?

00:15:03 Yeah, you can.

00:15:04 I haven’t played with them a whole lot myself, but I can’t remember the term the junk. I think it’s database fixtures, which is confusing for pytest people. Right. Because we think of fixtures in a different way. But there’s essentially data that you can say when my test database gets spun up, insert all this data into the database at the start, and so that there’s data in the database that is in a known state.

00:15:24 Okay, cool. And you said maybe that you had some surprising wins with the switching to pytest.

00:15:31 Yeah.

00:15:33 So for myself personally, the big thing for me that I want to get in switching to Python is I want to be able to start writing parameterized tests. My background before I was writing code in Python is I did a fair bit of testing in Java and using JUnit, and I saw the value of parameterized testing there. So switching to pytest was like from a personal Ulterior motive perspective, I just want to be able to do that in Python, and certainly we get that, and that’s great. But one of the things I didn’t expect was so when you run Pi test at the end of a test run, it produces all those warnings at the end, like if it finds things that are kind of like a little bit not best practice, or maybe if you’re using something that’s deprecated or whatever, things like that, it sped out the end of the test run. And so when we first did that first migration of that project to pytest, we actually spent out this warning about a regular expression we had to find in this middleware module that’s actually used across all five microservices.

00:16:28 Had we not gotten that warning, we would never have identified that potential bug and we never would have fixed it. And we got that for free just by using Pi test as the test runner. We had a similar win with one of our services has a third party dependency, and we’re getting these warnings at the end of the test run that was pointing to the code inside that module, and it was a deprecation warning. And as it turned out at the time, we were running Python. Actually, I think we’re still running Python 37, but the deprecation warning was about something that was going to go away in Python 38. So because we saw that warning, we knew we had to upgrade that library and we got to do that work before we do the switch to Python 38, as opposed to after the fact when we have to do three, eight and suddenly things start blowing up and then we have to scramble to upgrade that library. So it was nice to get that sort of initial heads up on that problem. And we have a similar warning right now boat that’s pointing at actually the specific LTS version of Django we’re using. There’s a deprecation warning that once we go to it will be fixed when we switch to Django two, which is the newer long term support version which we’re planning on doing soon. And again, it’s like these warnings just gave us a heads up on some problems that were going to be coming down the pipe later and we got to get ahead of them, which is huge. It was super helpful for us. I never would have expected that.

00:17:47 That’s an interesting thing to notice. I remember when a lot of people, when they’re just playing around with Python, they don’t turn all the warnings up as loud as they can be. Pipedest by default, turns all those on.

00:18:01 Yeah, exactly.

00:18:02 And you can dial it down if you want to, but it’s one of those things of like, now, you know, you should go fix it instead of turning off the warnings.

00:18:11 Exactly.

00:18:12 Yeah, that’s an interesting thing. It was surprising to surprising to my team when we had tests fail, not because there were new things wrong with our system, but because we had deprecated Python code within our test modules and stuff.

00:18:29 Yeah.

00:18:31 Cool. Since everybody is how big of a team you got then.

00:18:35 So the development team as a whole in my office is about 25 ish people. My actual team team. Like, we have teams within that team, and my team is currently about five people.

00:18:46 Okay. One of the things that I’ve heard before with teams that bring on somebody that’s really excited about pipest, but other people that are going along because they don’t want to make waves or anything, sometimes a complaint will come off that code and fixtures is hard to find any problems with that.

00:19:08 I’ve heard that concern raised. I haven’t experienced that problem yet myself. As I said, I think right now, so far, we’ve sort of just dipped our feet a little bit into the taking advantage of all the pytest. Goodies.

00:19:18 Okay.

00:19:19 So we might not have run into that one yet.

00:19:21 I use Pi term a lot, and it has the ability to just jump to the definition if it runs across it.

00:19:29 They are a little different. That within most Python code, something is either defined in the code you’re looking at or it’s defined in a module that you’ve imported. But fixtures can show up. They can be defined in a comf test file somewhere. I guess that’s the part where some people get confused, but I never have. But I’ve been using it for quite a while, though.

00:19:52 Yeah. It’s a familiarity thing, right. Once you’ve seen that, you kind of know it, but until you’ve seen it when you first see it, it’s kind of surprising.

00:19:59 Yeah. So development wise, do you think developing the parameterization has helped you then? Are you using that in Django tests then?

00:20:07 A little bit. We don’t have a ton of parameterized tests yet, and I think just culturally, I think there was initially a bit of resistance using parameterized tests because I think like any technique, they can be overused or used in a way that’s perhaps less than ideal.

00:20:25 We’ve only sort of dabbled into them so far, but they have been helpful so far.

00:20:29 Okay. As far as developing tests. Once you get over the hurdle of just whatever boilerplate you have to add or something, which you were nice enough to show me an example of a Django project that has switched from using unit test to using pipest. And it’s really just not that much different.

00:20:47 No, it’s not actually.

00:20:48 That’s pretty cool. Yeah, it’s just really not that much different.

00:20:51 Now I see that Mark you did. The little pytest Mark equals pytest markchangodb and that just sets it up. That’s just sweet. Anyway, and then your test is using a client fixture.

00:21:05 Is that equivalent to some unit testing?

00:21:08 So in the base test case class that Django provides, there’s a client attribute defined on that class. That is the same thing in a classic Django unit test. A Django test case test, you can go like self dot client post and then the URL you’re hitting and parameters to that end point and so and so forth. And that simulates an actual Http call to your Django application at that end point.

00:21:35 That client argument you see in that pytest test that comes from pytestjango, it provides that fixture for you for free. That’s just sort of universally available once you have pytestjango as a dependency and then you can do the exact same thing, you can go like client post or client get client.

00:21:52 So within the test itself, test case itself, you’re not really doing that much different. It’s just the outer structure. That’s pretty clever. That’s nice.

00:22:03 So developing tests within pytestjangos, even if you were not going to take advantage of parameterization or anything, it shouldn’t be too much of a hurdle to just switch over. Development wise, you shouldn’t be spending more time with pipe test. I guess you could argue you could spend less because you don’t have to keep typing self. But that’s not that huge of a deal. But as far as running goes, does it run about as fast as unit test or have you looked into that?

00:22:31 Actually, no, that’s a good question. So that is one of the cons we’ve sort of experienced in the switch to Python. The tests do run a little bit slower. The nice part is because these are microservices, each individual project, the number of tests in a single microservice is relatively small. It’s probably on the order of like hundreds or maybe a little over 1000, which isn’t a ton of unit tests. So the runtime it went from seven to 8 seconds to 13 14 seconds kind of thing. So in terms of CI, that’s nothing, right? It’s completely inconsequential. As a developer on your machine, if you’re doing like TDD and you’re running the test all the time, that does get a little annoying. One of the things I’ve sort of started adopting recently is taking advantage of that LF flag for Pi test. That last failed flag. That’s a super huge time saver for me because I’ll write my bit of code, run the test and then one test fails or two tests fail and then I can just make my change type Pi test LF and then I’m only rerunning those two tests that failed and then once I’m finally done everything’s green I can rerun everything and see that everything is green once again.

00:23:40 Yeah, I love that flag.

00:23:42 Yeah, me too.

00:23:44 I heard about it on this podcast so thanks for that tip.

00:23:47 I love that. I don’t know if they use the flag with internally but I love that in pyramid you can just rerun just the failed as well with just a button. That’s pretty cool.

00:23:56 Yeah, that’s handy.

00:23:57 Thanks so much for telling me about all your transition from unit test to pipe test within Django this has been really fun.

00:24:04 Yeah. Thanks for having me.

00:24:05 If people want to know more about you you have the blog at codebeniccoder. Is that or com? Okay, cool. Any final words before we sign off?

00:24:18 No, I think we kind of covered everything.

00:24:20 Awesome. Well, thanks a lot and I’ll catch up with you later.

00:24:24 Cheers.

00:24:27 Thank you, Adam. Great info there. Thank you Patreon supporters for supporting the show. Join them by going to support and thank you PyCharm for sponsoring this episode. The link for the extended profile is at PyCharm that link is also in our show notes at slash 110. That’s all for now. Now go out and test something.