 Are we live? OK, good. OK, thanks everyone for coming. So last month when we met, who was there? Who was there at the Django Meetup last month? Raise of hands? OK, so that's about like 50%. I asked who has done test room development before, and there were only two out of 30 people who confessed that they have done it, and I couldn't believe it. So that's why I kind of decided that I will punch together some slides, and hopefully I can show you why test room development is awesome and why it's worth doing. And hopefully next time when we have a Django Meetup and somebody asks for test room development, everybody will raise their hands, right? So today we're going to, I'll try to show you the basics. I was hoping that I can get into the more advanced stuff, but when I realized that I had like 40 slides already and I want to do it like live coding, so it's going to take some time. I realized it's not going to fit into one day, so maybe I will do a follow-up talk next month, and then we will do the real advanced stuff. So we will talk about how to set up your Django project, how to test models, how to test your admin classes, how to test your class-based views. I'm not using function-based views anymore, but they are even easier to test. How to test if a user is authenticated, if the view is protected against, you know, unallowed access, how to test your forms, how to test post requests, how to test for 404 errors, because I like to throw a 404, to raise a 404 exception if a user tries to access an object that he doesn't have access to or that doesn't exist in the database anymore. And finally we will talk a little bit about how to use the mock library. For example, if you want to make requests to a third-party API, like you want to get your Twitter timeline or something, you cannot really do that every time you run your tests, and probably Twitter will block you after a while because you are exceeding your rate limits for accessing the APIs, right? So there's a way to, and it will also speed up your tests tremendously if you mock out these requests to third-party APIs. So I'm a bit nervous because I have basically finished the slides tonight and last night, and I have never tried this. But I think, if I have basically only have two options, I can show you all the code snippets, and then you will probably see it, and if you haven't done any Django before, you will not really get what it's all about. Or I can really show you how to do it, and that's what I want to do, because I want to show that it's not that painful, right? Usually you will think, oh my God, I have to write so much code in order to get my tests done, and I don't even know how to start, so I want to try to show you my way of thinking, and I want to try to think out loud while I'm doing it so you can see how it grows slowly, right? So basically we would start with a completely new Django project, and the first thing that I always do is I create a virtual environment for my Python project. I've already done that, so I will not do that again. I will install Django into that virtual environment, and then once you've done that, you can start a new Django project with this command line tool here, and that's what I'm going to do. Okay, oh wait, is the font big enough? Everybody can see that, right? Okay, hmm, oh yeah, I shouldn't copy this. Okay, so I created a completely new Django project, and I called it Tested2, and it generates some default files for you, the next thing that I always do in the very beginning is I create a test settings file. Basically, you put that file right next to the original settings file, and you put some stuff into this file. So this basically means that instead of a postgres database or MySQL database, you want to use an in-memory SQLite database, which makes the tests really, really fast, right? Because usually you will have hundreds of tests, and every single test will destroy the database and create a new database, and if you would do that with a database that needs IO operations on the hard drive, it's going to be really, really slow. So in-memory is a good way around this. And then I like to set this email backend here to local memory so that I don't accidentally send real emails, it has happened to me before, to real people while I'm running my tests, and usually you run your tests like hundreds or thousands of times, so you're going to send a lot of emails accidentally. So this was a painful lesson that I had to learn one day. So then you would need to install a few Python libraries. First of all, PyTest, then it comes, and PyTest has lots of plug-ins, and you can just use them by installing them. So PyTest Django is one plug-in, PyTest IPDB is for setting breakpoints into your tests, and then you will be able to use the IPDB debugger, which is nicer than the original one because it has colors and code completion, and PyTest coverage helps you to generate a coverage report. So every time when we run our tests, it generates a bunch of HTML files, and you can see all your code files and how much percent, how many lines in that file have been hit by your tests. It's a kind of very opinionated thing. Many people say 100% test coverage doesn't really matter, it doesn't mean that you have good tests, and that's true. But for me, I have realized when I don't have 100% test coverage, I get into this situation where I'm like, I just fixed this quickly, I just deployed this, I know this is not gonna harm anyone, and I don't care about the coverage, and then these kind of small, these small situations where you wanna be sloppy, they add up over time, and then your test coverage goes down and down and down, and you end up being a 40% or something, and then your test suite is no longer really useful. So I try to keep it always at 100%. I cannot even deploy to my production system if it's not 100%. So yeah, this keeps you motivated to write more tests. Mike is shaking his head, he's shocked. Oh yeah, this is just, after you install all this, you need to deactivate your virtual environment and reactivate it, otherwise it will throw some weird errors. So the next thing is I create a pytestini file in the root folder. So basically here, I will say new file, pytestini, put this stuff here inside, and you have to tell it where is the test settings file. And then these are flags that you would usually use when you execute the pytest command on the command line. Usually on the command line, you start your test by just typing py.test, and then you can add more flags, and I don't want to type all these flags all the time, so this basically means add more options to the command line argument. So every time I run my tests, I want to run coverage report, and I want it to be an HTML report. You can also output it as a PDF, or I don't know what, there are like several options. Okay, so this is the ini file. Sorry? Add options, yep. Okay, so once we've done that, we can try it. We can try if py.test works, and indeed it tries to execute tests, and so pytest is a little bit like nose test, if anyone has used that before, it tries to be very smart and recursively go into all your folders and subfolders and search for files that somehow have tests in their name, and then it goes into those files and searches for classes that begin with test, and then it goes into those classes and searches for functions that start with the word test underscore, and it tries to execute all this code. So that's what testing is all about, right? You try to execute a bunch of functions, and in those functions, those are your tests, you try to execute your actual code, and you try to pass in certain arguments, like edge cases, you try to call your function with zero, or with very large numbers, or with negative numbers, and see if it crashes somehow. So now I told you that I also want to create the coverage report, so there's another settings file that we need to create, and it's the coverage RC file, and unfortunately it's a hidden file, so I often forget that this file even exists, and when I start a new project, I'm wondering why is my coverage report all messed up, and what did I do in my last project, and then I remember that, oh yeah, there's this hidden file here, so basically I'm just saying that I don't care, in certain files, I don't really care about the test coverage, right? For example, I don't test migrations because they are auto-generated anyways, and if the migrations, the Django migrations have bugs in them, I'm screwed anyways, there's no way I can fix that. I don't like to test my settings files, I don't want to test the tests obviously, that will be overkill. I don't test my URLs.py files, and also not the Django managepy and WSGI files, so I just omit those files, so that the HTML website which we will see later is not a super huge list where half of the files don't even interest me, and because I don't test them, it means the coverage will be 0%, which will lower my total coverage, and I want my total coverage to be 100%, so whenever you have a file where you don't care about, you have to add it to this list so that it gets removed from the coverage report, okay? Okay, so now we are ready, and as I explained, PyTest will be smart about finding files and classes and functions that it wants to execute, okay? So the first thing that we will do is, we will, so each Django application basically has a model, right? I mean, there are very few reusable apps that deal with email sending that don't have models at all, but I guess most Django applications somehow have a database model that they want to use, and so let's assume we are building some kind of Twitter clone, and we will call it birdie, so Django adminpy start at birdie, boom, it creates a bunch of files for us, and you know, Django is, I mean, even in the official documentation, they are very, they really encourage you to use testing, and they have a huge chapter about testing, and they even create a test file for you already to remind you, you know, you should be writing tests, but I don't like to put all my tests into one file, so we're gonna delete that file, instead we're gonna create a folder called tests, and this has to be a Python module, so we have to put the underscore underscore init file in here, and then we will basically create one tests file for each real code file, right? So if we have a models pile here, we will also create a test underscore models pile, so I'm basically just repeating the name of the original file that I wanna test, okay? Right, so this is this, and, oh yeah, and on the slide before I said that we're gonna install mixer, so you would do pip install mixer, mixer is a really cool tool for helping you creating test fixtures. Usually when you run your test, let's say you have a certain view that shows your user profile, and you wanna test that view, that view can only be called, like by giving in the primary key of that user, so the URL will be something like slash profile slash five, right, that will be the user profile of the fifth user in our database, so there has to be something in our database so that this view can even be called, because the first thing that this view is gonna try is to fetch that model from the database with the primary key five, and if our database is empty, then the view will probably throw a 404 error, like page not found error, because there is no such user, right? So we will always have the problem for our test that we need to put some data into our database that has a certain state so that the test can do its thing, and you could obviously just do it by saying, let's say we have a post model, but always in your tests by doing this post, you create a new post, and then you say post dot message hello, and then post dot save, you can fill your database like that, right, but it's like pretty tedious, Mixer makes it like this, Mixer dot blend, app name is birdie, model name is post, and then message is hello, so it's just one line, and if this birdie model has 20 other fields, Mixer will put random values into each of these fields, which is also really cool, because sometimes Mixer will put in negative numbers, very big numbers, unicode names, and maybe you have unicode errors in your code, and you didn't know about it, and so that means sometimes your tests run and run and they all pass, but then one day suddenly they fail because Mixer accidentally, or by chance put something into your test that you never thought about, so it even helps you over time automatically finding edge cases that you never tested. Yeah, so this makes creating fixtures super, super easy. All right, so, and we're, I mean, I wanna talk about test-driven development, that means we are gonna write the test before we even write the code, and this is the thing that makes it so hard for beginners, because if you're just learning Django, how you're supposed to write tests, because writing tests means you have to know how the real thing works and looks like otherwise it's gonna be very hard to write a test, but let's try it anyways. Basically, we know that our Twitter clone app needs a certain model, so by writing the test, I have to think about my real code, so I have to think about the name, right? And let's call the model post. So I have made, by writing the test, I have made my first conscious decision about my actual implementation. I came up with a name, okay? And this test here has to be here so that PyTest can find this class and realize that it's a test class, okay? And so you can come up with any function name here, and it has to start with test underscore, and then you describe what you are trying to test, and we just wanna generate test our model. And what we're gonna do is a very stupid test, but you will see why it's still useful later. We will try to create an instance of birdie.post, and we will make our first assertion. So testing is all about creating a certain situation and then call the assert statement to see if something is true, right? So we will assert that object primary key equals one, and then we can also give a message. So if this test fails, this message will be printed and it can help us to understand what we were thinking when we were writing the test. So we say this thing should create a post instance, for example, right? So, and obviously we need to import mixer. So from mixer back end, Django import mixer, and we need to import. Okay, so this is a pitfall. Actually, this should work. All imports are here, but PyTest, so some people are purists. They say unit tests have to be completely isolated and not talk to any outside system, and the database is an outside system. So if you want to have super extremely fast unit tests, you shouldn't even use an in-memory database. You shouldn't use any database at all. You should never save your objects into the database, but I don't really agree with that. I mean, computing power is fast enough, so speed is not a concern for me, and you will always get to the point where you have to test the save function of your object anyways, and then you would have to mock out the Django database. I don't know, that's kind of weird, but PyTest by default has this thing that it protects you from actually writing into the database, and it will crash here because mixer tries to call the save function after it has created the object, so it will trigger a write, and then PyTest will say, uh-uh, we are not allowed to write unless we put this line here, okay? But this is why, I mean, I will give you the slides after this, so this is why it's called a cookbook. You're supposed to open this up at home and copy and paste the hell out of it, right? This is how I do it every day. I don't know these things by heart. I go into my previous projects and just copy and paste everything. So theoretically, this test should be nice now. So if we run this, it will crash, and because obviously we don't have the birdie schema set up, right? So first of all, we need to tell Django that this app even exists because otherwise it will not, even if we had the model already, it will not recognize it. But the fact remains, we haven't written our birdie model yet. So it's still failing. So now is the time that we should go here and we should say class, sorry, I mean post model, not birdie model. Models, model, and let's say it has each mess, each post has a body and that's of type. Text, field, boom. Yeah. So now I hope the test should pass. Indeed, it passes, right? So we wrote some tests and then we let them fail and they kind of guide us into the direction of what we need to do next. And we will see this a lot better with later examples when we test views and forms. Okay. So yeah, here's the implementation that I, oh yeah, let's have a look at the coverage report. So every time it runs, it generates this HTML curve folder here and you can open an index HTML. And we can see that in our model spy, every line of code has been executed because our test instantiated the model and there's the unknown functions or anything that could be called. So basically everything has been executed. I mean importing this file at this point is probably already enough to execute every line of code. So we are 100%, so that's great. Okay, so let's test the model a bit more. Let's assume, so the body is kind of like the Twitter message, right? It's a lot of text. And we also want to have an excerpt. We want to have like this truncated version of the text, right? So we want to write a function that gives us a shortened version of the body. And so, let me just be here. And again, because we have to think about this function before we even use it, it makes us basically create our own API before we start doing it. So this really helps. Sometimes when I write a lot of tests, I realize the way I had my implementation in mind is actually completely wrong. It's like it will result in un-maintainable code or it will result in code duplication where I could centralize things a little bit more. So it really helps. So we want to create, so now again, I have to think about how should the function be called, right? I will call the function getexcerpt. So that dictates that my test function should also be called gettestexcerpt, okay? Once again, I will need an object. So I create one. And then I will call the getexcerpt function on my object. And maybe we want to be a bit more flexible. We want to say that we want to get the first five characters from our text, right? So maybe on some points of our website, we're gonna show the first 10 characters or when it's a mobile phone, there's very little space, we're only gonna show the first five characters. So we already decided that our function is called getexcerpt and it's gonna have one function argument which will be the number of characters that we want to get returned, right? So, and now we need to make an assertion. But I have no idea what's the body text. So I don't know really what should be the results. So why don't we say that the body text for our fixture here should be hello world. And then one, two, three, four, five, yeah. So that means if we get the first five characters from hello world, our results should be just hello, right? And then we will describe that should return first few characters or something. So you can see by writing these, even this is quite annoying. Some people don't write these messages. I make it a point that every test has to have this message because it also becomes some kind of developer documentation. Somebody who needs to debug a certain function that he has never seen before, I wrote this aura. I have to debug my own shit that I wrote two years ago. I will not remember what I was thinking. But when I see this test and then I realize, oh, it should return the first few characters and maybe someone else has changed the function so that it returns the last five characters, for example, the test will fail. The next developer will look into this and realize, oh, it should return the first few characters. So that's probably what the client wanted in the first place. So you write it down in like prose, right? Text that people can read and understand easily. So yeah, that makes it easy afterwards when the test fails one day to realize, do I need to change the test? Did the business case change? Or do I need to change my code because somebody invented the bug, right? Or introduced the bug. All right, so yeah. All right, and now we will see how this will guide us to solve the actual problem. So when I write this test, it's gonna say the post object has no attribute getexcerpt because we were trying to call getexcerpt here on a post model and obviously there is no such function, so it will crash, right? So now we will do getexcerpt. We will create this and like make a very minimalistic version and we run the test again. And now it will say, wait, excerpt should have two arguments that only got one, right? So we know that we have to allow to fill in the number of characters that we wanna get back, right? Now it's gonna tell us, oh, this looks good. So it's actually calling the function now but the result isn't correct because I was only returning none, right? So now I have to return the real thing and it's gonna be self. And this is cool now. I can never remember, does it have to be char colon or does it have to be colon char to splice the string? And if you don't do test-driven development, what would you do? You would fill in the thing that you think it is, then you will restart your web server, you will go to the URL and you will look if it looks right, right? So that takes about 10 to 15 seconds. No, we will just rerun our tests and, okay, so I didn't save the file. So okay, my hunch was right, it's this way, right? But if I would have done it this way, then again, the result would not match my assertion. And now it's actually, this is the cool thing about PyTest. This is much better than the Django built-in test client. PyTest give a test runner. I mean, PyTest gives you a really nice output when you compare lists or strings or dictionaries. It tries to tell you exactly what is wrong. What was it expecting? What did it get? And if it's a very long string where only a few characters are wrong, it even shows you like a pointer to those few characters that are wrong. So it's really cool. So yeah, obviously this is the run implementation. This is the write implementation. And now the test pass again and what about our coverage? Looks good, still 100%. So you see the test guided us by showing us several errors, our message towards the real implementation, right? Okay, we did this. Okay, so we implemented this excerpt function on our model. And some of you who might have used Django before, you probably know the Django admin, right? So you have this list. It's like a database management tool, right? It can show you all the posts that are already in your database. So in our post object right now only has this body field, which can be a lot of text. So our list would look pretty ugly if it's thousands of characters per post. So it would be cool if we will actually in our list admin only show the excerpt, right? But we can't do that because the Django admin can only show the real fields unless you provide an extra function then you can show anything. So that means we need to write this function for our Django admin as well. And that means we need to create a test admin file. And our admin is probably called post admin and it's gonna have a function that is just called excerpt. And once again, we need to use mixer. And that means we are gonna save stuff into the database. So we also need to tell PyTest that we want to do this and then we create another post, birdie post, and oh, okay. So the question is how do you test Django admin classes? You can't just instantiate them and call their functions. Unfortunately, that's not possible. So it took me a while to figure this out but when you spend that time, you learn that there's this call, this admin site thing here. Also, we need our models. So for some reason, I don't understand why you need to have an admin site instance. And only then can you instantiate your own admin class. Oh, then paste property. Okay. Okay, so this probably was a bit confusing. So, okay, wait. Okay, I'll explain this later. So our result is gonna be post admin excerpt. We are calling this function and we are passing in our birdie post and we expect that the result equals hello once again. Okay, so how do you get to this without already knowing what you want to do? So you know that you want to test your own admin class and you want to test the excerpt function. Okay, so basically we made our first decision here. We know our function is gonna be called excerpt and we know that's by definition if you read the Django documentation about writing admin functions, they always have self and an object. So we know that we have to pass on an object. That means we know we have to create an object here first. Okay, so and this stuff here, this is why you go to meetups. This is the stuff that you have to Google yourself because you ask yourself, how can I actually create an instance of this class that I'm about to write? And unfortunately, you cannot just do this, right? It's not like a model. Our post, we can just instantiate like this. A form like create form, if we will write one later. We can just instantiate it like this. It doesn't work like that with the admin classes. They need the model that they are about and this admin site here. I mean, I don't know why I just take it for granted. I found it on the internet and I copy and paste this ever since. So yeah, this should basically be, yeah, this should be it. So let's try. And once again, it's gonna tell us that we are trying to instantiate a class that doesn't exist. Right, so class post admin, admin, model admin. So we have to import our own models from import models. So this admin is about the post and so this is what I wanted to do. The Django admin allows us to define which columns should be visible in that list view, right? And I want an excerpt column to be there. But this will not work. Django will crash and tell you that the post model, so the model does not have an excerpt field. It only has a body field, okay? So that's why on our admin, we have to define this excerpt function and then when the admin renders the list for each row, it will call because we are using it here in the list display, it will try to find if there's a function with the same name and then I can return whatever I want from that object. So when I want to call get excerpt and the first five characters, all right? Okay, so let's see if the test passes and it does and let's see if the coverage is still 100%. It is. Okay, okay, this is our implementation. So next thing, views. So the first view is we want to have a homepage view. Obviously this view should be accessible by anyone. You don't need to be locked in or anything can be anonymous. Usually if you read the Django docs, the first thing that you will learn about is they have this self.client.get which allows you to call any of your views. But back in the days, years ago, I learned that my tests get really, really slow because if you use this, it's almost a little bit like using Selenium. So it's like really simulating a real request. It's going through the whole middlewares and your template processors and everything. It's going through all your urls.py to find the class that the view that it needs to execute. So it's really slow. And the good thing about this, I think it renders your templates. So if your template is missing, it will crash. The bad thing about it is slow and I care more about speed than knowing that I'm missing a template file. Because I'm obviously testing everything in the browser before I ship it. And if I'm missing a template file, I will get a 500 error in the browser and I will see that. So there's another thing, it's called the request factory. It allows you to instantiate your view classes just like we just instantiated the model or like we instantiated the admin. You can also obviously just instantiate your view classes. But the view class, the views only really work when you pass in a request. So you need to create some kind of fake request object that you can pass into your view. And this is what the request factory is good for. It's like the name says, it's a factory that will generate Django request object for you. Yeah, and then we instantiate our view in the same way like we do it in our urls.py class. We use the view name dot as view. This turns the view class into a function and then you can use this function and put your request inside, okay? So let's try that. So new file, test views. So we have to think about what should be the name of our view class and it's gonna be home view. And it should be callable by anyone anonymous. So now we need the request factory and we're gonna create a fake request and it's gonna be a get request. So you could also put post here and you can put some urls here, but it doesn't really matter because we are not using urls.py anyways. And if your view code as such does not do any computation over the urls string, you can always put in any url in here, it doesn't matter. And then we need to import our view, which obviously right now doesn't exist. And we want to get a response from the view. So we will try to instantiate our home view like that. And now it's a function, now we can pass in the request like this. So this looks a bit weird, took me a while to get this into my muscle memory. Yeah, well it's like that. And then we make assertions. So we want to assert that the response status code is 200 and that means is callable by anyone or should be. I always start these messages with should be by anyone. Okay, so this is our test, very simple test. And let's see. Oh, it will crash because there's no home view. So let's create one from Django views generic template view. So this is basically the smallest possible implementation that you can do in order to create a view in Django. You just tell it is supposed to be a template view and it has this template name. I don't even bother to create template file now because we are not going to look at it in the browser. So now the test passes. So that means this view can be called by anyone and it will return a 200 success code, right? Yeah, so as I said, the downside is if you forgot to hook it up in your URL spy, you won't know about it. If you forgot to create the template, you won't know about it. And if your template is, I don't know, using lots of template tags and whatever, they will never be called as well. But you should obviously test your template tags as unit tests individually already. So this is how we make sure that the templates tags work and about the URL spy. I mean, if you code all this and then you just chip without even trying in the browser, then you are doing it wrong anyways, right? You always have to look at it in the browser in the end and then you would realize that you totally forgot to hook it up in your URL spy. So I think these two downsides here is something that I can live with. Or I mean, if you're really hardcore and you have a very expensive project for a very prestigious client, you would probably also run Selenium tests and really test your front end and then you would realize that you forgot your template or you forgot to hook it up in the URL spy, okay? Okay, so another thing that we often like to test is or need to test is authentication. You wanna be sure that certain views like your user profile cannot be accessed by anyone else, right? You don't want some hackers go into your site and start changing parts of the URL. When they see numbers in the URL, they will try to call every single number and figure out if they can access somebody else's profile and maybe even save data or delete data or whatever. So we want to be sure that the views that should be protected are protected and we don't further down the road half a year later somebody, some intern comes in, removes the lock and require decorator, it's like, oh, what is this decorator? It looks ugly, just remove it. Oh, still everything works. Of course everything works, but now everybody can access your view and you don't want that, but your test will realize that, right? So the thing is I usually use the method decorator lock and require to protect my views and that means from that point onwards, your request, we use this request factory to create a fake request. Your request object needs to have a user attribute like the lock and require decorator is trying to access request.user. And by default, the request factory generates a request object that does not have a user attribute. So we need to manually attach a user to the request and we will see how we do that. And that means Django has a special class, a special model actually, but I don't think it has a database table, the anonymous user model. I have actually never seen that before, before I started actually using test and development. So I think most people don't know that it exists. So what we're gonna do is, we will create a new view now. Sorry. And it's supposed to be a view that can only be accessed by admin users. So this is gonna be called admin view. And so first we want to test if an anonymous user cannot access this view. Yeah, so we will create our request. And this is the thing that I was mentioning. We need to put the user attribute into the request object. And in this case, there is, so the question is, what should it be? Should it be none or something? No, it has to be some smarts. So that's why Django offers this anonymous user thing here. And this is from Django, contract of models. There it is. So now we have an instance of some kind of user that's not really in our database, that's not locked in. So then we can get our response. So that means response views, admin view as view with this request that we just created. And then we make an assertion. And I happen to know, if you try to access a view that's protected with the lock in require decorator and you are not locked in, it will redirect you to the lock in view. So we could assert if the response status code is 302, I think it's 302 or one, I can never remember. But I don't like to do this assertion because maybe there's some code in your view that does in some circumstances, does a redirect. So that means maybe a redirect is happening but not because the user is anonymous but because some other situation. So it's more strict to test if lock in is in response.url because if a redirect happens, the response object has a URL attribute and this is the new URL where you are getting redirected to. And it's usually, I mean it happens, it depends on your project but by default I think it's account lock in. So the URL will look something like that and then usually next where you came from. So this is usually if you try to access a block view this is what Django will redirect you to and I'm too lazy to write all this so that's why I just check. And usually my projects don't have other URLs that also are called locked in. I mean if you had that, then again this test would not really tell you if you are getting redirected to the lock in view or to some other view that happens to have lock in the URL as well. Then you need to be a bit more precise here about the URL. So and then we hit the second test, test super user. This time we create a user. I didn't import mixer, no. So see we can, this is the baked in user model that comes with Django and it lives in the auth app and the app name is auth and model name is user. So we can also create this model even though we have not written this model ourselves, right? This is living somewhere inside the Django framework. And then as always we create a get request and we attach our user that we, so this is the user variable here, right? We attach that to the request and then we try to get a response with this request. And this time we assert that the response status code is 200. You can see the current place. Where? Oh yes, right. This is one of my favorite pitfalls. I forget it all the time and it gives you a very weird error message as well which is not very helpful. Oh yeah, and I forgot to to tell PyTest that we're gonna do database access. Okay, so now it's, as usual, it's telling us that we have not invented this view yet. So admin view is also just gonna be a template view. Template name is gonna be birdie admin HTML. And so how do you protect class-based views? It was like this. You overwrite the dispatch function and you need to learn by heart what is the function definition and it's like this and then you just return the super call. Okay. And now we can use this login required decorator. And this is something that I always have to Google. I never remember where these things can be imported from. So just Google for Django login required and then you go to Stack Overflow and you'll steal this from someone else and you find these imports. So now we can use method decorator, whoops, method decorator with the login required function. So that means for every request that comes into this view, the first, I mean every time when we call our view like this, when we pass in the request here, right? It actually enters the code right here. You could set a break point here and this is the first position where by calling this, this line here as view and then request, we end up being here in the dispatch function. And then we just let Django do whatever the dispatch function should do based on the template view here. So it's calling the dispatch. So basically we override the parent classes dispatch function here, oh sorry. And we basically don't do anything special. We just call the parent classes dispatch function. So we pass on the call to the parent class. But we decorated the function. So that means before this code here is executed, this code will be executed first, right? And this code makes sure that the user has to be logged in. So let's see if our test passes. Yeah, it passes. By the way, my naming was a bit misleading here. It sounds like I'm testing for a super user but I'm actually not doing that right now. I'm just testing for a normal authenticated user. Nevermind, I messed up the function names. Okay, so next thing, testing forms. Let's say we want to have a site where there is a text field and people can enter a message and press submit button and then a new post is added into our database. But your message has to be longer than 10 characters. We don't want small talk, we want real talk, okay? And yeah, so we need a form for this. So that means we need to create, as usual, test form spy. Our form is gonna be called post create form, I guess, or just post form. And in order to test our form, we need to import it and usually we will try to create an instance of our form. So we will do something like post form instantiate, right? Usually we need to pass in some data into the form. So whatever the user has entered. Oh, and we want to make sure if the form is empty, it's not valid. It's not valid. So maybe the first test should be, we are passing in empty data into the form, right? And our assertion will be that form.isvalid. So this is when you know how to use Django and how to work with forms. You are very familiar that you always have to call the isvalid function and that should be false. And it's like, should be invalid if no data given. So then we will write another test. We will instantiate form again. And this time we will pass in some data but less than 10 characters, right? So once again, we have to be invalid if too short. And there's nothing that we could test. We could test if, so the form after you have called isvalid has this errors dictionary here. It has a list, a dictionary with every field name that has an error and then a list of errors for that field name. So we will expect that body, this is the field name, is in this dictionary. There's a key in this dictionary with body because that field has an error, okay? So this makes sure that our form is invalid but we also want to make sure it's invalid because of that field. Maybe it has many fields and it's invalid because of something else. Then this test will not be very meaningful, right? Okay, and then obviously we want to test if with more than 10 characters if it works, right? So we have to test a positive case as well. So now it should be true. All right. And I guess the form will try to save data to the database. So we need to copy and paste this magic here again. And now let's try to test. Pi test. Uh-huh. It says it cannot import forms, which is correct because there's no such file, forms. And probably now we'll say that there's no such class. Huh, what happened? Oh, I went up one folder. Yeah, it has no post form. So post form forms, it's going to be a model form. I think we need class meta here. So the model, import our own models. The model is going to be post. Yeah, this is also interesting. So this should be enough, theoretically? No, it's not. It's telling me that I have to provide a fields or exclude attributes so that this form can be used. And actually exclude is an anti-pattern. I think they will even remove it in later Django versions if I'm not incorrect. So we have to provide the fields. And so this form should, I mean, our model only has one field, so it's pretty boring now, but this form should have the body field, the text field so that the user can enter something. Huh, did not expect this. Ah, yeah, yeah, okay, it doesn't need class meta. So I tested this just now before I came here. And I happened to stumble into that as well. And this was a very unhelpful error message. The thing was to set. But I mean, if you use Django every day and you know how to write your forms, you would know that you don't have to use the class meta here, right? So when I'm doing so much with admins and views and forms, I often forget which ones need class meta and which ones, where can I just put it into the object, into the class immediately, right? Okay, so the model is here. Okay, maybe I'm wrong. Okay, it does need class meta, dammit. Okay, and what else is wrong? Set object, that's weird. Fields, forms, model form, forms, model form, dmwm.forms, models. Okay, I guess it's a problem with my test. Did you target the body form to be a test? What do you mean? Oh, yeah, yeah, yeah, yeah, yeah, yeah. Wow, awesome. You're right. So we pay. And I actually, when I tried this at home, I fell into this pit as well, but I couldn't remember just now. So I was passing in a badly formed dictionary, right? The data you pass into the form has to be a dictionary. And I wasn't really providing key value pairs. I was just providing the value. So that's why it crashed. So now it says, I was expecting the form to be invalid when the text is too short, but my form is always valid at the moment. So that means we need to implement something. And Django has this idea of clean body. So I can, for every field on my model, so if there's a field called body, I can write a function clean field name. And then it will be, when you call form is valid, it will try to find all these custom clean methods and try to execute them before running the big overall clean method. And then finally it will tell me if it's valid or not. So what I'm gonna do here is cleaned data. So this is something that you need to learn from the Django documentation. It's like convention. So if the length of my data is less than, less or equal than five, I will raise a forms validation error. Message is too short. All right. And now my tests pass. And what about coverage? Everything 100%. Yes. Okay. Okay, we did all this and we are almost done, two or three more things. So I also wanna show, we did get requests now and now we want to do post requests as well. So we have a form. We already know that we are able to save stuff into the database. We need to create a view that is using that form. And so that, you know, you can go to a URL, see that form and press the submit button. So there has to be a view for this. And let's say, okay, yeah, this is the first example. Okay. And this view is gonna be called update view. Let's close everything. So as usual, I have to think about what is the name of my view. It's gonna be called post update view. It should be callable. So you have to go to that URL. So first you have to make a get request so that you can see the form and then you click the button and you finally make the post request, right? So we have to test both. So we will create a request with the request factory and it's gonna be a get request. And our response should be by using our post update view which doesn't exist right now as view and we pass in that request. And it's gonna be like a super lazy implementation. Anyone can post. We don't even need to be logged in. And we expect that the status code equals 200 should be callable by anyone. So let's try this. It's obviously gonna fail because we don't have that class post update view. And this is supposed to be a form view actually. Oh no, it's gonna be an update view. Oh yeah, okay, I remember. Needs a template name. Okay, so the idea, okay, I wanted to show how to use URL quarks. So the situation is this view is supposed to edit and already existing post. So the URL will be something like post slash five, right? And the five is the primary key of the post. So how do you call a view like that? That means we need mixer. We need to create a post. And here, when we call a view, we will pass in the primary key as a URL quark and it's gonna be the primary key of that object that already exists in our database, okay? Yeah. And then let's just write the rest of the tests first as well. We want to test the post request and I will just copy and paste it to make it a bit faster. So what's happening here? Since this is an update view, we need an object. So we create an object. And the user is gonna type something into the field. So this view needs some data. And here's the trick. So the primary key is passed in here when we call the dispatch function of the view. The data is attached to the post request, which kind of makes sense, right? If you send get or post request, they might have get or post data. So that's why the data is gonna be passed in to the post function of the request factory here. Oh, oh, okay. I was, can I recall that? Yeah, almost. Okay, sorry. So we are passing in the data into the post request. We are passing in the primary key into the dispatch function of our view. And then we try to call that view. And Django update views, they are built in such a way that if the request, if the post was successful, they're gonna redirect you to a success URL. So I'm testing for 302 redirect status code here. And then there's another handy thing here that I think is possible since Django 1.8 or so, or maybe even 1.7, you can call refresh from DB because when we use mixer up here, the body will be some random text. Mixer will fill in random text because we did not provide a special message here. We did not say hello or whatever, right? So this is gonna be some random text, but the post data is some text that we know. And after the post request, we want the database object to be updated, right? So the new text should be inside the database object. So we refresh our object and then we compare the body with the text that we expect, okay? So I test, okay. We haven't implemented the update view correctly. So it needs a model name. And it also needs a view class name. So this view is supposed to be dealing with post models. And it's, sorry, I don't mean view class, I mean form class. And it's supposed to use a special form class that we defined the post form, right? So with just these few lines of code, Django will be smart enough to see a URL that has a primary key inside. It will then use this primary key and try to get a post object with that primary key. If it doesn't exist, it will show a 404 page. And if it does exist, it will render the post form for us. And if you make a post request, you press the submit button, the, your data will be passed into that post form. And if the form is valid, it will call save on the form and it will redirect you to the success URL. Oh, that means I have to provide success URL as well. Let's say it should just go back to the home view. Doesn't matter for now. So I think this implementation should be fine. Yeah, and it works. And we are still 100% and almost at the end of the talk. So let's test them. We are not actually between any... Templates? Templates. Not yet. This is what I do at last. But not today. Because I'm not testing the templates. So it's not really... Just testing a function. I assume you receive this record or assume you receive this form field. Yeah, exactly. I'm most interested in security. Like, can users access other users' objects by changing the primary key in the URL and certain form validations. Can user submits half empty forms and stuff like that. Yeah, so 404, I was speaking of which. They are a little bit trickier to test because Django raises a 404 exception. And unfortunately it bubbles up all the way into the test. So the test will actually crash and show you the exception and not move on. And I mean, we want this exception to happen. That means the test is successful. It shouldn't crash. It should be successful. So there is a way in your tests to catch exceptions. So we will extend our tests a little bit. Oh yeah, so very stupid example. We wanna make sure that if there's a logged in user trying to use this update form and the user's first name is my name, then it's not allowed. Then we will throw a 404 error. This should be the functionality of our view, okay? So I will create a user and I will set the first name field to Martin. I will also create a post because we want to update a post. I wanna try to update a post. We create our request factory and I don't even care to put in any data because I know I'll be rejected. I will not even get as far as submitting my data. I attach this user object myself to the request. And now we have this with statement that is part of the Python language. And PyTest has this raises function. And here we can import from Django HTTP, import HTTP 404. So this is the exception name, right? This is an exception name. You could, I mean, any kind of exception in Python. We could probably even do this. We expect any generic exception to happen. Or I don't know, there are so many exceptions. So HTTP 404 is one exception class that's part of the Django framework, okay? So, and then inside of this with statement, we are now executing the code of which we expect that it should throw an exception. So, yeah, let's try that. And it will say, oh, I did import it. HTTP 404. Huh, this is weird. Ah, yeah, okay. I don't know, didn't save, I guess. So it says test fails because the exception has not been thrown because we haven't modified our view code. So let's make this view a little bit more secure by over, oops, sorry, by overriding the post function. So at some point, the dispatch function will figure out that this is a post request and then it will move, it will call the post function. And here we are trying to get the user from the request and then we try to access the first name field of the user. And if this is Martin, we are gonna raise an HTTP 404 exception here. And otherwise, we just do whatever the Django framework wants to do with the parent class update view. Oh, I messed it up. Where's the syntax error? Invalid syntax from, from Django HTTP. It's down here. Looks good to me. Did you try turning it off and on again? Okay, so I guess I forgot to save again. So now, actually it should work, but we are getting an error here. That's because we added some functionality that makes our older tests incompatible because now we are trying to access the user attribute on the request, which we have not done before. But we have written tests before here where we did not add the user objects to the request. So in this case, we will just use the anonymous user again. And now our test should pass and the coverage should still be 100%. All right. And this is the last thing. Mock. Actually, probably one of the most interesting things. So let's assume that we have a payment view where we are gonna using, where we will be using Stripe. And on our front end, we will attach this Stripe.checkout.js thing that Stripe gives you so that you can embed some JavaScript code in your website. There will be a Pay Now button. People click at the Pay Now button. The Stripe model pops up. People can fill in their credit card information. They press Save. When Stripe checks the credit card and everything is all right, they send us back a token. And then we will call a post call to our own payment view with that token. And then this payment view needs to call on our server, needs to call the Stripe API with that token and charge some money. So this is usually how the flow works. And the problem is our server is now gonna call, it's gonna do an HTTP request or HTTPS request to the Stripe servers. And every time we run our tests, this will happen. And a request if you have flow internet takes one or two or three seconds that will slow down your tests like crazy. And probably Stripe will knock on your door if you like, some people they have file system watchers, they run their tests every time they save the file, like on a separate monitor, which is actually very useful. So that means you would be calling the Stripe API 100,000 times per day, and then maybe they will block you, right? So we don't want to call that API at all for speed reasons and for like rate limit reasons. Okay, and let's also assume that by reading the Stripe docs, we know that in Python, oh sorry, in Python we can call, we can do pip install stripe, and then we can call stripe.charge and we fill in some, the token and the amount and the currency and stuff like that. And then it will give us back this JSON string with ID and the charge ID. And then we can save that charge ID into our database and we know we've received the payment, right? So this is basically the idea. I will just cover the whole thing. So, oh, I shouldn't have copied the whole thing. Only this part. And we will need mock. So mock is not standard library. You need to do pip install mock in order to use this. And basically the thing that, the only thing that I always use is patch, which is part of the mock library, and patch can be used as a decorator. And I will explain this after you have seen the implementation that makes things a little bit easier. Use, okay. So stripe is actually, that's actually, you can do pip install stripe and use their official API. Oh yeah, and also finally on the last slide, I wanted to show how you can send emails and test this as well. So we can do this as well here. Oh, and we also need to import Django, shortcuts, import redirect. Okay, but this is not important. So what's happening? We know that somewhere in our code, we will call the stripe API, okay? And we know that this will send an HTTP request to the stripe servers. And we don't want this to happen. So we basically monkey patch away this module. This module is imported at the top of the file here. And we will, when the test starts running, we will replace that with a so-called mock object. That's some weird object that you can do anything with that object. You can call functions on it. You can call attributes on it. You can assign values to it. And it just allows anything to happen to it. It will never say, I don't have this function. It will record that this function has been called on itself. So, and this is what patched us. We are saying that in the module called birdie, so that is the folder name here, birdie. We have a file called view.py. This is our view.py. And we are importing stripe inside of that file. And we want this to be replaced by a magic mock. So using this patch decorator, make sure when we enter this function here, we first monkey patch the stripe import. And now we can pass in the monkey patched version of our stripe import into our test function. So this is the fake stripe version now. And then we can use this fake stripe version as a variable inside of our test. And we can say that if somebody calls dot charge, we set the return value to this. This is what we expect, what a successful stripe payment should return to us, okay? So, and you know, you can come up with anything here. You can invent any kind of functions that should be called and come up with return values, okay? And then as usual, we sent a post request. This is the token that we got from the JavaScript front and when stripe validates the credit card. And finally, our view should redirect to some success page. And it should also send us an email so that we know that we have just made some money. And from Django dot core, I think import mail. And this is the final thing that, you know, as a newcomer, you will ask yourself, how do I test if an email has been sent? You can simply call mail dot out box. So every time when in your views, when you call send mail, mail dot out, this mail will sit in your out box, right? And we are using the local memory backend. So in our tests, we can access mail dot out box and our mails will be sitting in that out box. And when you write more test functions, it's like every time you define another test function here, the database will get deleted, mail out box will be cleared, everything will be like completely clean and fresh and new. So sometimes I write very long tests where I'm telling a story, like user creates this kind of transaction, then he changes some value of the transaction, then this happens, this happens, because I want to like, I need, otherwise I would have to set up the fixtures all the time. But when it's already like a very obvious way of going through the same view, doing things in the same view and changing my objects, I like to write it just into one test and reuse the objects that I just created, right? But if I'm testing completely different functionality, where I really want to have a clean database, I write another function name and I give it like test anonymous, test logged in, test super user or whatever. Okay, so yeah, does this work? I see some error message here, white space at the end. Okay, so let's see, generic. Okay, that's obvious here. I should just import view and here as well. Ta-da, and we wrote our Twitter clone with a payment page and 100% test coverage. And, oh yeah, one last thing, sometimes when your test suite is very, very big, it takes a long time to run. I mean, for a medium-sized project, it can go up to let's say 30 seconds. For really big projects, it can be 10 minutes, then you will have continuous integration server every time you push. Your server will start pulling that branch and will start running the test so that you can go and take a coffee or just continue coding or something else. And if it fails, it will send you an email. That's the usual setup. But if you are, and obviously that means if you are running the tests for the things you are currently working on, you cannot always wait 10 minutes for the whole suite to run through. So then you have the chance to specifically call a test in a certain file by providing the folder name, birdie, tests, test views, pi. And then with this double colon syntax, you can provide the class name and the function name. So you can, if you leave out the function name, it will run all functions in that class. If you leave out the class name, it will run all tests in that file. So you can be super granular about it. Or you leave out that file, then it will run all tests inside the birdie app. You might have a lot of apps, so that might also make sense. And finally, if you wanna put in break points into your tests, you can use pytest.settrace. And then when the test hits that point, it will just stop on the console and you can inspect your variables and stuff. Okay, so that's what I wanted to show today. And actually, I went much faster than I thought. So there are more interesting things that I can show maybe next month or whenever we don't have any other talks lined up, which is testing template tags, testing Django management commands. This is interesting because they are also some kind of thing that you can't just instantiate and then call it. Because usually the managepy commands, they have command line parameters and they might have certain exit codes. So you can test these things as well. Django has some stuff for that. Testing sessions can be tricky. You might have some views that write messages into the Django messages queue. And those are usually saved in the session. If you don't have a session object on your request object, this will fail, right? Usually you can just do request.session equals empty dictionary because the session object is kind of like a dictionary. But in some cases, because the real session object is not really a dictionary, it has some more functions. And if your view for some reason tries to call one of those functions, then it will crash. Then you need to create some kind of fake session similar to how we create a fake request. And I can show how we can do that. Testing with files is super interesting. Like how do you test an image upload field, right? And I can show how to do that. And then we could go on, how do you test the Django REST framework? Because the Django REST framework cannot use the normal request factory. It has its own API request factory. And you cannot just use the .sview to instantiate your view classes, your API views. You have to pass in some other stuff. So that's something that I can show. And then PyTest has another plug-in for running tests in parallel. If your computer has multiple cores and you have hundreds of tests, you can actually make use of all your cores and run tests in parallel, which will cut the testing time in half. All right, thanks for listening. I hope this wasn't too boring. And if you have any questions, ask now. No questions. Really, no questions? Okay, I guess. We will still be here eating the rest of the pizza. And if you have questions, just walk up to me.