Closing session
Published June 13, 2025
This video features Joachim Jablon and Stéphane "Twidi" Angel at DjangoCon Europe 2019 in Copenhagen, Denmark.
https://2019.djangocon.eu/talks/maintaning-a-django-codebase-after-10k-commits/
Joachim Jablon: https://twitter.com/Ewjoachim
Stéphane "Twidi" Angel: https://twitter.com/twidi
A large Django codebase becomes harder to change as dependencies age, teams and business requirements shift, Django versions advance, and undocumented history accumulates. The speakers recommend treating third-party packages as long-term maintenance commitments: test upcoming versions, contribute fixes, fork or replace abandoned packages when necessary, isolate dependencies behind adapters, and keep ownership of Django models and migrations. They argue for separating business logic from views and the ORM through services and selectors, and describe architecture patterns such as functional cores with imperative shells, hexagonal or clean architecture, and domain-driven design with bounded contexts and a shared language. Finally, they advocate a deliberate testing pyramid using focused unit, integration, and functional tests, with readable tests, Pytest, Factory Boy, BDD, and snapshot testing where useful.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: So maintaining a Django project after 10,000 commits. Let's go. So uh who are we? Well I'll start with myself. I'm Joachim, uh currently working at PeopleDoc I have uh six years of professional experience using uh Python and Django. I've worked on two big cut bases and I had the opportunity to see a third one from UpClose.
Speaker 2: Hi, I'm Tudi and I'm a dinosaur. I have 22 years old of professional experience but only 12 with Python and Django. I worked on so many code bases of all sizes that with my old age I couldn't keep the count. I'm a freelancer and the founder of isem. io
Speaker 1: And before we start, uh we have a confession. Uh the title is a bit of a lie If you have a huge cut base and it's completely broken and you're still pressured into delivering features every day, well There's no real magic trick. There will be some work to do. But please don't leave the room right away. Uh there's hope. And uh the ideas that we will present might help if you're able to make some time to refactor your current project. or maybe for your next project. So we'll be talking about a project. This is any big project. It could be your big project and this is our fictional big project. It's called monolith.
Speaker 1: Uh for our fictional project for today, we deliver huge rocks into people garden. The Monolith project uh started eight years ago. Uh today the tests take one hour uh to run in the CI. It has more than a hundred dependencies Uh hundreds of models, more than a thousand fields. Uh some tables are bigger than one million, uh one hundred million rows in production. Uh according to the statistics uh in terms of commits and lines of code, our project uh is bigger than Django itself, so maybe we should have uh the conference for this project. Who knows? Uh so a quick show of hands to just get the the
Speaker 1: idea. Who's been working on similar projects in the room? Okay, so that's about let's say ten percent of the room. Okay, thank you. Uh so in the beginning Monolith was just uh an app. There were a few views running on some server. But with time uh the the project became more mature and there were different aspects of the app that we had to add or that involuntarily found their way into the code. So uh testing, monitoring, reporting, packaging, translating, communicating, developing and sadly a little bit of suffering too. Uh today it's just not an app anymore, it has become huge. Another thing is that
Speaker 1: uh with time uh that passes uh the environment changes and that's what happened with monolith. First uh turnover with uh 10 uh 15% annual turnover in the industry. It's no surprise that uh no one working on it today knows the full history of the project Also uh we pivoted. Uh in the beginning we delivered pebbles, now bigger rocks. So uh the underlying code of course is the same, but it has to go through many uh adaptations But we were successful in that so much that uh there are now three distinct teams working on this and we had to split the cut base in three parts Of course, the complexity of the project only grew with time, so uh
Speaker 1: it makes adding new features harder and harder, uh and bugs are more insidious. And finally, we had to go through multiple Django versions, uh, because we started with Django 1. 4 LTS. And uh a lot of third parties that we used were never upgraded, so we had to remove them. So monolith has become old. These are all things that we can't really control, but are there are ways to reduce their effect on our project. So we're going to leave monolith on the side and talk more into uh our subject for today uh we'll see four aspects in which we we will be able to give you a little bit of advice, hopefully. The first is the borrowings
Speaker 1: of your codes. That's the third parties, the things done by other people that you put in your code. Then the heart of your code of our code that's business logic where you create value. Then the boxes of our code that will be uh architecture so how we function efficiently we divide our system into subsystems And then the trials of our code, that's the tests, how your code may prove brave and trustworthy before we task it with production.
Speaker 2: So let's start by talking about third-party dependency. When you need to cut something in your project, you basically have three solutions. Code it yourself, of course Use someone else code you paid for, a contractor, for example, or use someone else code use someone else code you don't pay for, like open source. Just kidding. Even it's if even if it's open source you should pay for it one way or another. So this is how we can basically define a third-party component. Some external code used by your project. Whether it's something huge that will save you a lot of time or just a small library with with a single function, say left pad.
Speaker 2: Yes, some jokes need never get old. In our context, it generates a Python package you install via pip install related to Jungle or not. We could also consider external APIs to be some sort of third parties, but let's keep the APIs aside and focus on third-party packages. So why do we want to use third party dependency? Using a third party can reduce the effort so the time and so the cost to implement some to reduce to reduce the risk. Smarter people already did the brain works of the corner cases and the bugs. But everything has a downside. In the real world, a third party could increase the effort
Speaker 2: for the time and so the cost in the long term and increase the risk. Let's see why. First, security updates. Smaller libraries won't have many A's trying to find security issues issues. Usually this means bugs may stay longer. It's important to remember then by adding dependencies the exposed surface exposed surface of your project is not just your code anymore, but the code from many other people you don't know. Not saying that they have lower standards with respect to secure coding, but it's just a fact. You're not in control and Also you do not control the direction the application will take.
Speaker 2: Maybe the author the author will soon change the wall behavior or will simply break the API or just introduce a subtle change that could break that could break your use case You think you can rely on a versioning for that? But not everybody follows semantic versioning, even when they say they do. You may also encounter bugs, need to add a feature, or simply upgrade the version of Django you use. And Django changes fast. And yes, on the scale of big projects, Django changes fast. So when you upgrade you want to be sure that your dependencies are ready. The problem is that the evolution of an external external app can be very slow.
Speaker 2: New issues or pull requests can take a very long time to be taken into consideration by the owner. Owner who can just ignore your request and they are totally free to do so. You may not agree But you should. And even worse, this owner measure I've just abandoned the application for any reason. You know, as I said, shit happens. Just before going to the solutions, here is another thing thing to think about. Do you need the full applications as it comes? Maybe developed for a lot of use cases for a lot of people. Or maybe you need just a small part or even the whole thing but with some axe to make it fit your needs
Speaker 2: So here is our first advice. Never forget the hidden cost of adding third parties. In the long run, it's almost never just pip install and add to install adapts. Short integration time now could lead to longer maintenance later. So now we'll see how we can do what we can do to get over all this issue. First, there are some external dependencies you'll use just as they are and it's okay. It's okay for an app developed internally in your company, your e-control. It's okay when the app solve well-known problems with a lot of complexity. Databas drivers, cryptography, etc. It's okay for
Speaker 2: official packages of external APIs. It's okay when Django provides a backend system like authentication, storage, and the app is a well-known backend. And it's okay when the legitimacy of the apps makes it reliable and durable, like for example Django Rest framework. So for all the this kind of apps, let's admit it's okay But for the others, what can we do? First you need to know when the next update will break your code. It's a good idea to test the latest versions of all your dependencies in continuous integration. This way you will be warned when something breaks. No need to fix it right away, but at least you know this will be an issue later.
Speaker 2: And tooling exists to help you on this When a problem is found, you can ask the owner for help by creating an issue or even provide the fix yourself. This may be a good way of contributing back. And please, when you report an issue, be nice. But as we've seen in some cases the communication is broken and you need to take back control The first way to take ownership is to create your own public fork, of course if the licensing allows it Then be ready to become the owner of an external dependency used by others. As we seen with big power comes big responsibility
Speaker 2: If you don't want to go public on this, you can maintain your own private fork or even or even integrate it in your own project as a whole or only the parts you need. But don't forget that you are not sole responsible of security updates, etc. Of course, the ultimate way to get rid of all the third-party dependency problems is to implement yourselves the parts you need. No dependencies, no problem. If you counter or don't want to take ownership, there's still one thing you can do. You could build an adapter, your own abstraction layer, with the rest of your project only calling this layer instead of the external library.
Speaker 2: Don't hesitate to add tooling to forbid the import of your external dependency anywhere else than in your adapter. Your abstraction layer will need to explicitly define everything you use Isolating the package helps a lot for updates or if you need to switch to another package later for example. So here's our second advice. Isolate your short patches. To finish on third-party dependencies, I'll do a brief apartheid about Django models. One thing you really want is to have the entire ownership of your database. And by this I mean your jungle migrations and so
Speaker 2: your jungle models. So if an external app you don't own provides concrete models, my advice is to not use it. And think of a way to add the behavior in your own project. If this app provides abstract models, it's different. But you relation things twice. You're still not in control and don't forget that the direction this app will take may not be the one you expect. So do not let anyone define your model. And my last advice for this part, whatever you do, try to contribute back to open source
Speaker 1: That But uh could you imagine uh yeah uh it this bears the question like Django itself is a third party to your application. So does everything that uh Tweedy uh said just now mean something about the way we should Use Django within our code. So this leads us to uh business uh logic. So this will be the heart of your code. And before really starting, I'm going to just define what I understand as uh business logic Uh it's all the different parts of your codes and the ideas and constraints behind it that will still hold true
Speaker 1: if you were to replace Django with Flask, for example. Who'd do that? So an example for this logic and the structure behind uh would be For example, when a user is created, they get a welcome email or only premium users can access this page. On a course, the start date the start date must be before the end date. So uh whenever you see uh something uh that you would want to yeah uh whenever you express something you would want to And to to do independently from the technology and the actions, who can do them and the consequences, you're really defining business logic. And the problem is the
Speaker 1: that uh today in a standard Django application, the business logic and the Django code are kind of like this, completely intertwinned, uh in a spaghettible, impossible to disentangle. And um yeah the business logic is sometime in the templates, that's very very wrong. And in the forms and the serializers and the views and the models and the managers And the problem is that each part of this in a standard Django application are already here for another reason, not to hold your business logic. So if you were to change the the business logic you never actually know where you want to go to to find it. And uh if you change something in a view or a form or something
Speaker 1: It's really easy to break the bu the business logic without realizing it. We tend to think that any code that we write in a Django project should fit into one of Django's base file. But it's not true. We can write code that's uh in module that Django doesn't know about. Uh So uh a nice way of doing this is uh through service layers. That's an idea that's been around for several years now. Uh I fell first encountered this idea uh when attending uh Hanakolo's talk avoiding monoliths in DjangoCon Europe 2015. My first DjangoCon. It was expanded recently in Radoslav Georgeov's Django structure for scale and longevity
Speaker 1: at EuroPython 2018. And I'll be exploring this idea a bit, uh starting on Radov S Redoslav 's version, and I'll leave you folks to see the talks if you want more info. So in this particular way of writing Django software, uh you add two files in uh your Django application. One is a service module that will be providing simple functions, task quiz, object creation, modification and deletion, and establishing and maintaining the constraints of your business logic. And the second one is a selectors module that will also provide simple functions, but uh this time to retrieve objects according to your business rules and the permissions.
Speaker 1: And the rule is that any object manipulation should be done through your service and selector layer, banning the idea of forms and views that would be accessing directly the URM So that would make our diagram look something like this. Uh Django code is now uh mostly isolated from your business logic and nothing from the web Can find its way uh to your models without passing through your service layer. Uh the business logic with the LP of the ORM is now uh properly isolated and in charge. Uh This might be enough in many cases and you don't really have to go beyond that.
Speaker 1: But as a third experiment, I'd like to continue this idea a bit further If we try to integrate also the the ORM, uh the way we would integrate a third party like uh uh presented by 3D. So this would mean your business logic would now be c implemented completely independently from Django. It's let's say in pure Python code as opposed to Django Python code. Uh so your ORM code would be the the adapter between your code, your business logic, and uh the ORM. And with a little bit of uh class interfacing, you could even transform it to adapt it to any ORM or even any persistent service.
Speaker 1: So note that for this you would need uh two distinct classes for your models, one classic Django model class and a pure Python class. So let's call it uh an entity. Uh and we can continue this madness beyond uh with the the thought experiment. Uh your business logic could now be uh completely free of Django. Uh yeah, but for now it still lives in the service and selector files. uh in Django. Well there there's no real reason in the end. It could live independently from Django in your own code base, but on a whole different module outside of the Django architecture. So uh if we take a look at this graph and uh try to look it from a little further, it looks like it's kind of symmetrical with things in the outside and things in the inside.
Speaker 1: So I'd like to just reshape it uh this way uh to have an idea. So yeah, uh it looks a little Strange. And I I I really sure um I I'm not sure anyone uh really took this idea so far with Django, but I would sure like to know if they did and how it worked for them. Uh so my yeah if we take the most important thing from this idea, it would be to to split your business logic, your jungle views and the URM. And when I I think about it, it's probably some advice that we've heard before with different words. Uh so I guess I can hear the question that some of you are wondering as of now
Speaker 1: Uh if we put layers and layers and layers of code in the kind of yeah uh in the kind of lasagna that many pi people identify with uh huge enterprise languages like Java and. NET. So is this Java? Are we Javaing our code? And Well, if Java is the term we want to put in front of keeping ourselves from putting everything everywhere, yeah, maybe yes, this And we could call this Java. But I think the real point is that uh there's this common idea that with Java you have too many layers Uh and with Django maybe we are missing a some layer. Maybe we would be lacking a bit of software architecture.
Speaker 1: uh using uh Django's own software architecture as if it was enough for the the whole project we have So let's talk about software architecture. Uh in this part I'll be advertising a lot of ideas. and work from other people. There are concepts that I wished I had known before and that would have eased a few uh decision design decisions I had to take. Uh so we're talking about architecture. So what exactly are we talking about? Uh my take would be that uh it's about boxes. There's small boxes, big boxes, and how you decide what goes in which box, how you label your boxes, and how you help things go from one box to another box, etc.
Speaker 1: So we'll be introducing three boxing patterns today, from a very practical one to a much uh theoretical and high-level view. And as much as I'd like to make you experts in those concepts, each one would be worth a full-length talk, so we're not going to do uh four talks today. So our first concept uh is from Gary Bernthardt. Uh it's called functional core on imperative shell. And the idea is that in your code you should always separate the logic where the decisions are made from the glue that feeds the logic input and do things with the output So the logic uh should be expressed using functional code, which means codes without side effects, which means that uh they don't modify an object attribute and they don't call
Speaker 1: uh an external system like read a file or write in your database, etc. And there's a very, very nice thing uh with functional code. Which is that uh it's easier to unit test. It only depends on its input, and the only thing to check is the output. So you can extensively check, uh you can extensively unit test your logic this way uh and really go and find all the the possible corner cases of your logic But then if you don't have any side effects, uh you're producing nothing. Uh a system needs side effects like reading and writing HTTP packets and reading and writing a database, otherwise it's just producing heat.
Speaker 1: So uh that's the role of the imperative shell that surrounds uh your functional calls It has as little logic as possible, but it conveys the information from your program input to the logic to the output. And because the imperative shell has no logic or little logic, you don't have to write many tests uh to make sure that it works. But it's deeply linked to many parts of the system. So you want those tests to be integration tests and you want to avoid mocking. You can think of it as a kind of uh a factory where your functional calls would be the machines, they take something in and spit something out.
Speaker 1: And the imperative shell would be the conveyor belt that is tasked with uh making each machine's output linked to another machine's input. I'll take an example. Uh both of these snippets uh right here are parsing etc hosts and printing each line ignoring comments. On the left side Uh we have a nice function. Uh it's uh yeah, so uh the function uh uh passhost would be doing all the work and then it's probably uh encapsulated. uh exposed you don't need to know a lot of different parameters and uh it it really eases the way to read the main function because you don't have to know all the the details.
Speaker 1: And then on the right one Uh it 's completely split into uh different parts where the our pass host uh function is just uh reading uh lines in the in this argument and then uh applying the uh the rule for excluding comments and then yielding different lines And then the main part is uh tasked with opening the uh etc host file and printing the line. So the the pass host is not even reading etc host anymore. So uh as uh let's do a show of hands. Who thinks uh the left one is cleaner? Who thinks the right one is cleaner? Okay, all the hands that were lifted were on the right one. And you're completely right.
Speaker 1: Uh the one from my point of view Uh the one on the left is uh mixing I. O. and logic. Uh the one on the right is separating them into a functional core that's pass hosts. And an imperative shell which is main. And a very nice consequence of doing things this way, uh let's say that now everything has changed and uh Impressed by the ability, uh our ability to parse the host file, our customer now wants to read the host file from an API, parse it and write it to a local file. So uh on the left and the right uh the code uh before and after we changed for the the requirement, we can see that uh we were able to uh change completely the piping without touching anything about our logic.
Speaker 1: The the pass host function has stayed completely the same and all the unit tests that we wrote for it are still applying. Uh so if we generalize this idea, it means that the I/O, so the file or network interactions, should always live up in your stack, hence the hoist. Uh but yeah if you bury the IOs uh at the the bottom of your stack, it's going to be harder to modify your code and to test it and to debug it. So uh this had been presented by Brandon Rhodes uh in a talk named uh hoisting your IO uh your IOs. I really recommend it. But let's go back to Gary Bernhardt for a second.
Speaker 1: One of his inspirations uh for the functional core and imperative shell. was uh something called the hexagonal architecture by Alistair Cockburn, which describes a way to organize software with layers and how these layers uh should communicate. So in this uh way uh of uh organizing things, the innermost layer is the the domain. Uh that's the business, uh let's say the business logic of your application. Then the application itself, uh so in green for people who can see colors. Uh And then uh yeah, the application itself, uh its job is to adapt uh the object of the domain.
Speaker 1: to the real world and then in the uh outmost layer it's the real world, uh the databases, HTTP, etc. And one of the important rule is that objects in the inside should never know about objects in the outside So for the um outside to call the inside, so that that would be a view that's calling business logic, which is a thing that you can expect will happen often. uh you just have to make a direct call because the exterior can know about the interior. But uh for the inside to call the outside that would be for example business logic that needs to make a database call. This has an I. O. Uh actually one of the ways to do it uh is uh
Speaker 1: through something called uh dependence inversion. So for example you could implement it as an an abstract interface that will be defined by the domain, which knows that it has to store uh the object. And then it will be implemented by the application. Which knows that storing means putting uh in the uh the database using the Django RAM. Uh so this way the domain can communicate with the application without actually knowing anything about it Uh so if I recontextualize all these concepts in jungle terms, we would end up with something like this, which as far as I can tell from our research looks like a classical software architecture pattern And I've seen I I think I've already seen this diagram before,
Speaker 1: but uh I can't say when. Well So it's interesting to note that uh Brandon Rhodes offers a very similar view in his talk, uh the cleaner architecture in Python. Which looks at another pattern called the clean architecture, uh which is very similar and inspired from the hexagonal architecture I just presented. Now to our last uh architecture topic for today. Uh what if I told you that uh there is a systematic approach that can help you split your system into subsystems uh in ways that minimizes friction and coupling. Uh it's uh domain-driven design, which is an idea from Eric Evans.
Speaker 1: And it embodies a lot of different advices from higher to lower level. And some of it is about code, uh and much of this, uh more importantly, is not Uh the primary idea of domain-driven design is, uh as the title says, uh, that um software design should all uh as much as possible be driven by the actual knowledge from the domain or business or trade if you prefer. So there are three important concepts that uh embody uh this idea. Um F modeling, for example, a person in your application, even if it's the same person, uh would be uh done in completely different ways. Whether you're modeling a person as a friend, if you work at Facebook
Speaker 1: or a recruit if you work at uh LinkedIn or a contributor if you work at GitHub or a teammate at Slack etc. So a good model can be created by being curious about your domain rules and links and constraints. Secondly, uh when deciding uh how to split your domain uh into subdomains that will communicate together, there is a rule of thumb that you could use which be to follow the way that the business urine has divided uh the domain into subdomains, uh or it's usually called bounded contexts So try to identify the different bounded contexts and work within them in your software. But for in order to understand the domain, the first step is to speak its language.
Speaker 1: You must learn and use the same words for the same business concepts, whether you're a technical person or not. It's called the ubiquitous language. So for example, if you notice that uh the product people talk about a shopping cart and then the support uh people talk about a shopping basket. And the developers what they have implemented is something called an order And you realize that they're all talking about the same thing, then there's probably a problem at this point. You you really need to try to unify everyone around the same language to for everyone to understand uh each other So I'll take uh an example of domain-driven design that I uh shamelessly stole stole from Cyril Martraire with his blessing.
Speaker 1: Uh let's uh say this is our customer model or entity if you prefer. Uh but there's a problem. Uh here we have uh a delivery address, but uh people might have two different addresses, a shipping address and a billing address. So first th first reaction, let's add everything to the customer model. But then the problem of uh doing this is that uh soon enough our model will be it will be exploding with uh hundreds of fields So we can do it another way. An address in the end is an address is an address. So uh now we have an address model and a customer model. That's nice. But From the the business point of view, is this address concept really a thing of its own?
Speaker 1: Well, we went and uh asked the the billing department and the shipping department.
Speaker 2: You have a billing department and a shipping department? Oh exactly.
Speaker 1: Yeah, uh you're right. Maybe we could have split it this way with uh shipping model, uh uh shipping address model and a billing address model
Speaker 2: By the way, what these uh those people from the shipping uh department said?
Speaker 1: Oh, they told us that uh the the person we send stuff to is not called a shipping address but a recipient. And the billing department, they told us that the entity that we build is not called a billing address, but an account.
Speaker 2: Oh, did they say anything else?
Speaker 1: Well actually yes. Uh the shipping department uh they said that uh while we were there, uh it would be very nice if we could store the building code and the delivery hours so that the delivery could be more successful And the billing folks they told us that uh they need to know the account tax code and also the currency, maybe using Django money who so. So uh there's that.
Speaker 2: Oh excuse me It looks cleaner now, thank you.
Speaker 1: Yeah. As you see, uh answering those demands was not too hard with our software architecture this way. And how do you think we would have done if we have had kept our original split? So the conclusion for this will be that uh we're not just building a web app and as much as we're uh software software professionals Uh we also have to become invested in the field of our company because it will definitely help us drive better design decisions. A quick note, uh I've cited a bunch of folks on their talks or works. Uh it's interesting to know that they all inspired themselves from
Speaker 1: one another. Uh in this graph you can see direct reference in people's talk or blog posts to other people's work. So it's really a matter of uh standing on the sh the shoulders of giants or in this case maybe just taller folks, but it's already good. Uh uh so yeah while we we've seen that it's very important to be careful uh when you take other people's code. Uh don't hesitate to listen to other people's ideas. But
Speaker 2: a project is not only about code, it's also about tests. Before we start, here is a reminder of the testing param It shows three levels of tests. At the bottom, the unit test. The test, for example, uh single function without IOs, database access, networking. etc. Generally you want to have many of these tests covering all the connect cases and so you want them to be fast, really fast. Then the integration tests. They don't test the logic, but they test that different parts work well together. Like a view that will use permissions, business logic, and so on. They may have side they may have side effects and will be longer to run, but
Speaker 2: so you should not have too many of them. And finally, functional tests. The test uh business requirement of the project is generally spreads uh over several parts. If for example the user wanting to do a specification your website. This test can easily be way longer, but you will generally have a lot less of them. One kind that is not on this slide are end-to-end tests, because they can be seen as an extension of functional tests at a higher level that may involve a browser. Here is an example of how the tests could be separated for a jungle view. You can see that
Speaker 2: higher level tests include things that have already been tested at a lower level, of course. But they don't test all the corner cases, only the main ones. If all the parts are well covered by unit test And if integration tests ensure they're all all work well together, then the functional tests, uh all the functional tests you need are just end-to-end tests This is an important advice. Right test, yes, but the right test and only the right ones. When writing your test, there's a thing you must keep in mind.
Speaker 2: You don't write your test like you want your like you write your code. A test should be easy to read and to understand at first sight. It should test one thing and one thing only. Using dry, don't Repet yourself, seems like a good idea at the beginning. But I can assure you that sooner or later you'll get this. Generally, forbid defining thousands of things in the setup of a bus test class. Never try to define smart functions with a lot of magic. In your test, aim for the simplest to read and work Right. If a test fails, you don't want to pass the majority of your time trying to understand what it does. You want to look at it and understand it right away.
Speaker 2: And you want to be sure that if you change something in a test, it won't break any other tests. Here are some things that can get complicated in the long run. First, mixing. There are black boxes that can do some magic, the opposite of what we want. So In test avoid them as much as possible. And you if you really need one, make sure it doesn't depend on another. Then the test client. In unit test you should not need to call the view so no test clients. In integration test, you should not need to call the test client more than once for a given view. You're just checking that the that the URL is okay.
Speaker 2: If you need more integration tests for a view, call it directly because in the end it's just a callable. for functional for functional tests if it's of course okay to test to use a test client and finally fix your fix your files Because nobody wants to edit a big JSON file every time a model changes. And also because it adds stuff, stuff you cannot see when reading your test, exactly like a long setup in a buzz test class. A good way to avoid all the speed faults is to stop using unit test and its Django layer and to use PyTest instead, with PyTest style functions. It's dead simple functions that take fixtures that are functions too
Speaker 2: as arguments and where you do your assertions by using the simple Python assert statement. No need to wonder what assert method to use. And regarding the problem of uh creating test objects, whether they are model instances or cell entities A very nice tool is Factory Boy. It allows you to create objects by specifying the attributes And only the one unit for a specific test. All the other attributes will be generated randomly, sequentially , etc. This removes the need for fixture files in a very clean way and makes your st your test more stable. And better you can even use it in your unit test
Speaker 2: creating model instance without hitting the database. I now briefly introduce you to some topics about tests that are particular particularly useful in large projects. First, behavior-driven development. It was inspired by test driven development, but instead of writing your test as code , you write them as scenarios in plain English. So a little show of hands. Who has ever worked with behavior-driven development? Oh. And uh amongst you, who has liked it? Oh just a few. So it's been
Speaker 2: English, but uh of course uh it must follow a few rules. Here you see a feature, blog, with one of its many scenarios. Here it's uh publishing the article. A scenario is composed of steps. First the context, given I'm the user and I have an article. Then the action when I go to the article page and I press the publish button and then the assertion. Then the article should be published You can then transpose everything into Python using one of the few existing libraries. In this example we use PyTest BDD that use a PyTest ecosystem, including the fixtures. But they all work the same way.
Speaker 2: A step is represented by a function. Here you can see the scenario, which is uh the test that will be executed by PyTest and all the steps as functions using fixtures. There's a big advantage to this. Assuming there are tooth the rules, anyone on the project can write the scenarios. For example, product owners and not only the developers who still have to convert the steps into Python code of course. When this is done everywhere in your project, it can be a very nice way to document all your features in one place, which can be a lifesaver for your new teammate entering your big project. And you have a solid link between your product and your code. But sometimes you you can't describe the behavior you want.
Speaker 2: For example, you want to check that something is right, but you you don't know how to define this right. In this case you can try snapshot testing. It involves comparing the output of something with a previously saved output. So show of hands, who has done snapshot testing before Oh, more than BDD, but uh just a few. The most common is testing the rendering of a web page via screenshot. But at a lower level it can be used to compare the SQL queries executed during a test or to compare the HTML returned by a view or even the output of an API. Tooling generally includes ways to generate fresh snapshots for a new test or when something changed.
Speaker 2: Having the snapshots and their updates in your version control system allows the reviewer and the rest of your team to see what changed and how it evolved in time. So to sum up this part, I urge you to make tests the prime part of your process. If it's not already the case, do it and you'll thank me later. Because this will allow you to trust your code, to need less human QA time, and to achieve continuous delivery
Speaker 1: So yeah, uh it's time to wrap things up as our wonderful master of ceremony is making big signs to make me understand. So just a few closing words. Uh we know that uh quite a few things uh we've said are things that uh we haven't seen a lot in the Python community, which means that either we're visionaries or we're completely out of touch. Uh remember that just because two guys on a stage told you something with a self-assured tone doesn't mean that it's the absolute truth. There are other good and valid ways of achieving equivalent results. Uh but if you apply just 10% of what we've said here, we're convinced that uh you're in a better state than most of the code bases we've seen. So uh if you even if you don't don't feel guilty about it, now you know that uh these patterns exist.
Speaker 1: Obviously uh it's a really broad subject, so this will uh this talk was just an appetizer, but we've left plenty of breadcrumbs. towards other talks uh that are more in-depth for you to follow. Uh so stay curious, uh try things and think before you code and final finally
Speaker 2: Don't overj don't over enjoy it.
Speaker 1: Thank you.
Put external packages behind an adapter or abstraction layer and prevent the rest of the project from importing them directly. This makes upgrades, replacement, and ownership easier; when necessary, maintain a fork or implement only the required functionality yourself.
Discussed at 12:11Use service functions for creating, modifying, and deleting objects, and selector functions for retrieving them according to business rules. Views and forms should go through these layers rather than accessing the ORM directly, keeping business logic isolated from Django’s web layer.
Discussed at 16:46Keep decision-making logic in pure functions that depend only on their inputs, and put I/O and side effects in a thin imperative shell around them. This makes the core logic easier to unit-test while the shell can be covered with a smaller number of integration tests.
Discussed at 22:15Hexagonal architecture puts business logic in the center and keeps databases, HTTP, and other external systems at the edges; inner code should not depend directly on outer code. Dependency inversion lets the domain define an interface while an outer application layer implements it, such as using Django’s ORM.
Discussed at 27:53Model the software around the actual business domain, identify bounded contexts that reflect how the business is divided, and use a shared ubiquitous language. Understanding the domain’s concepts and terminology leads to boundaries and models that fit the business rather than forcing everything into one generic model.
Discussed at 30:57Use many fast unit tests for isolated logic, fewer integration tests for interactions between components, and a small number of longer functional or end-to-end tests for business requirements. Higher-level tests should cover the main flows rather than repeat every lower-level corner case.
Discussed at 36:27Make each test easy to read, focused on one behavior, and free of excessive setup, magic helpers, mocks, and shared hidden state. Use the test client only where appropriate, call views directly for integration checks when possible, and avoid large fixture files that obscure what the test is doing.
Discussed at 38:48Pytest provides simple test functions and fixtures without relying on Django’s unittest layer, while Factory Boy creates only the attributes a test needs and generates the rest. Factories reduce brittle fixture files and can create model instances without hitting the database in unit tests.
Discussed at 40:20BDD expresses features as plain-English scenarios with given, when, and then steps, which product owners and other non-developers can help write. When implemented in Python, those scenarios document the product behavior and connect it directly to the code for new teammates.
Discussed at 41:54Snapshot tests compare current output with a saved, version-controlled result. They can check rendered pages, SQL queries, view HTML, or API responses, while making changes visible to reviewers when snapshots are updated.
Discussed at 44:10Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025