1 00:00:00,000 --> 00:00:15,677 2 00:00:15,677 --> 00:00:19,010 BRIAN YU: OK, welcome back everyone, Web Programming with Python and JavaScript. 3 00:00:19,010 --> 00:00:20,700 And we'll pick up where we left off last week, 4 00:00:20,700 --> 00:00:24,080 which was exploring Django, which was a new Python web framework that we were 5 00:00:24,080 --> 00:00:27,980 able to use to continue to design more sophisticated, more interesting, more 6 00:00:27,980 --> 00:00:31,010 advanced web applications and do so more easily. 7 00:00:31,010 --> 00:00:34,460 It gave us access to things like a model already 8 00:00:34,460 --> 00:00:36,350 in place for how to authenticate users, how 9 00:00:36,350 --> 00:00:40,620 to log them in, how to log them out, how to remember that a user is logged in. 10 00:00:40,620 --> 00:00:43,220 It also gave us access to a really nice and easy to use 11 00:00:43,220 --> 00:00:45,320 administrative interface which made it such 12 00:00:45,320 --> 00:00:48,810 that if we wanted to very easily add new things to our models 13 00:00:48,810 --> 00:00:50,030 it was very easy to do that. 14 00:00:50,030 --> 00:00:53,270 And it also gave us the ability to enforce relationships 15 00:00:53,270 --> 00:00:55,310 between different models in ways that was easier 16 00:00:55,310 --> 00:00:58,280 than we would have had to have done previously, whereby if we wanted 17 00:00:58,280 --> 00:01:02,150 a relationship, for example, between flights and individual passengers, 18 00:01:02,150 --> 00:01:05,930 rather than have to create for ourselves another table that would relate 19 00:01:05,930 --> 00:01:08,960 an individual flight ID to an individual passenger ID 20 00:01:08,960 --> 00:01:11,540 we were able to leverage Django in order to simply say 21 00:01:11,540 --> 00:01:14,180 that we want there to be some sort of many to many relationship 22 00:01:14,180 --> 00:01:16,070 between flights and passengers. 23 00:01:16,070 --> 00:01:17,960 And taking advantage of that relationship, 24 00:01:17,960 --> 00:01:21,400 we could then use that in order to have Django do the hard work for us. 25 00:01:21,400 --> 00:01:24,166 So figuring out that we need some sort of intermediary table, 26 00:01:24,166 --> 00:01:25,790 and Django goes ahead and creates that. 27 00:01:25,790 --> 00:01:28,670 Well, we don't need to worry about how exactly Django's doing it. 28 00:01:28,670 --> 00:01:31,520 We just need to worry about what relationship we want. 29 00:01:31,520 --> 00:01:34,140 And so before we dive into the main focus of today, 30 00:01:34,140 --> 00:01:36,590 which is going to be about testing and also 31 00:01:36,590 --> 00:01:39,180 continuous integration and continuous delivery, 32 00:01:39,180 --> 00:01:41,210 we're going to talk a little bit more about Django and the features 33 00:01:41,210 --> 00:01:43,168 that it gives us to touch on a couple of things 34 00:01:43,168 --> 00:01:46,820 that we didn't quite get to last time in order to answer a couple of questions 35 00:01:46,820 --> 00:01:47,759 that people have had. 36 00:01:47,759 --> 00:01:49,550 And so the first thing we'll take a look at 37 00:01:49,550 --> 00:01:53,330 is inside of the same airline example that we've been using, 38 00:01:53,330 --> 00:01:57,740 we'll take a look at how we might customize the administrative interface. 39 00:01:57,740 --> 00:02:01,575 And so a couple of questions were raised during the last lecture about 40 00:02:01,575 --> 00:02:04,700 whether or not we could change anything about the administrative interface, 41 00:02:04,700 --> 00:02:07,610 that thing that's automatically created as one of the built in Django 42 00:02:07,610 --> 00:02:08,503 applications. 43 00:02:08,503 --> 00:02:10,669 And in fact there are things that we can do in order 44 00:02:10,669 --> 00:02:12,132 to customize it to our liking. 45 00:02:12,132 --> 00:02:15,090 There are in fact, many different options that we have available to us. 46 00:02:15,090 --> 00:02:17,423 But I'm just going to show you a couple just to give you 47 00:02:17,423 --> 00:02:19,320 a sense for what's possible. 48 00:02:19,320 --> 00:02:24,410 So if I look go into Admin.py, which is the place where, in last lecture, 49 00:02:24,410 --> 00:02:26,870 we really just had these three lines at the bottom where 50 00:02:26,870 --> 00:02:29,420 we registered our airport model as something that we should 51 00:02:29,420 --> 00:02:31,790 be able to edit in the admin interface in addition 52 00:02:31,790 --> 00:02:34,670 to the flight and passenger model as other things 53 00:02:34,670 --> 00:02:37,760 that we wanted to be able to edit and modify in the admin interface. 54 00:02:37,760 --> 00:02:43,370 What we can do is extend the existing model admin class 55 00:02:43,370 --> 00:02:46,180 in order to add additional features that we might want. 56 00:02:46,180 --> 00:02:47,930 And the full list of all of these features 57 00:02:47,930 --> 00:02:49,820 is available in Django's documentation. 58 00:02:49,820 --> 00:02:53,630 But I'm just going to show you a couple that might be interesting or of use. 59 00:02:53,630 --> 00:02:57,230 So one, for example, is that you can determine 60 00:02:57,230 --> 00:03:01,100 how users are able to interact with particular data points. 61 00:03:01,100 --> 00:03:04,250 So for instance, I'm extending the model admin class 62 00:03:04,250 --> 00:03:06,690 to create a new class called passenger admin. 63 00:03:06,690 --> 00:03:10,370 And this is going to be a special set of configuration settings 64 00:03:10,370 --> 00:03:13,580 that I'm only going to use when I'm editing passengers. 65 00:03:13,580 --> 00:03:17,480 And I use that down here when I register the passenger class for the admin site. 66 00:03:17,480 --> 00:03:20,465 I'm saying, use this special passenger admin class 67 00:03:20,465 --> 00:03:22,340 which is going to contain additional settings 68 00:03:22,340 --> 00:03:26,510 or additional information about how I want the user of the admin interface 69 00:03:26,510 --> 00:03:29,010 to be able to interact with passengers. 70 00:03:29,010 --> 00:03:32,600 And in this case, I'm using this special variable called filter horizontal 71 00:03:32,600 --> 00:03:36,890 which is going to give me a really nice way of taking a passenger 72 00:03:36,890 --> 00:03:39,260 and manipulating what flights they're a part of. 73 00:03:39,260 --> 00:03:40,843 So I'll show you what that looks like. 74 00:03:40,843 --> 00:03:43,190 Filter horizontal is one of those built in settings 75 00:03:43,190 --> 00:03:45,110 that I can manipulate using Django. 76 00:03:45,110 --> 00:03:51,575 And if I run the server now, and go to this URL, 77 00:03:51,575 --> 00:03:54,710 and I go to /admin to get to the admin interface. 78 00:03:54,710 --> 00:03:57,730 What I can do is, now when I go into passengers 79 00:03:57,730 --> 00:04:00,440 and I click on an individual passenger like Alice, what 80 00:04:00,440 --> 00:04:03,870 I get is, this is what the filter horizontal administrative window looks 81 00:04:03,870 --> 00:04:04,370 like. 82 00:04:04,370 --> 00:04:06,560 And I'll get a really easy way of controlling what flights 83 00:04:06,560 --> 00:04:07,310 that they're on. 84 00:04:07,310 --> 00:04:09,827 Before, if you recall from last week, I was clicking 85 00:04:09,827 --> 00:04:12,410 and there was Control or Command clicking on different flights 86 00:04:12,410 --> 00:04:14,030 to either select or de-select them. 87 00:04:14,030 --> 00:04:17,386 Now I can determine what flight Alice is on just by clicking on a flight, 88 00:04:17,386 --> 00:04:18,510 moving them back and forth. 89 00:04:18,510 --> 00:04:20,420 Now Alice is not on any of the flights. 90 00:04:20,420 --> 00:04:22,350 If I click on another flight and move it over, 91 00:04:22,350 --> 00:04:25,160 now Alice is on both of these flights. 92 00:04:25,160 --> 00:04:27,680 And so these are just small changes I can 93 00:04:27,680 --> 00:04:31,500 make to change the appearance of individual flights. 94 00:04:31,500 --> 00:04:35,460 And one other thing you might have noticed from last time. 95 00:04:35,460 --> 00:04:39,960 And before I do that, let me go back to the way it was before. 96 00:04:39,960 --> 00:04:45,440 If I were to go into Admin and go and edit an individual flight, 97 00:04:45,440 --> 00:04:46,040 for instance-- 98 00:04:46,040 --> 00:04:48,170 I want to edit flight number one-- 99 00:04:48,170 --> 00:04:50,727 you'll notice that normally what I get are just the options 100 00:04:50,727 --> 00:04:52,310 to edit the properties of that flight. 101 00:04:52,310 --> 00:04:55,226 I can edit that flight's origin, I can ediit the flight's destination, 102 00:04:55,226 --> 00:04:56,900 I can edit the flight's duration. 103 00:04:56,900 --> 00:05:01,730 But I can't, built in here, edit the flight's individual passengers. 104 00:05:01,730 --> 00:05:02,360 And why not? 105 00:05:02,360 --> 00:05:04,820 In the passenger side I was able to edit the flights, 106 00:05:04,820 --> 00:05:06,860 but I can't seem to do the reverse. 107 00:05:06,860 --> 00:05:10,220 Well the reason, if we look into models.py here, 108 00:05:10,220 --> 00:05:13,190 is that if we look at what properties the flight has we only 109 00:05:13,190 --> 00:05:16,470 defined origin and destination and duration. 110 00:05:16,470 --> 00:05:19,709 Whereas for passengers we defined their first name and their last name 111 00:05:19,709 --> 00:05:21,500 and this additional property called flights 112 00:05:21,500 --> 00:05:23,240 that was associated with passengers. 113 00:05:23,240 --> 00:05:25,790 So therefore when we go into the passenger admin interface, 114 00:05:25,790 --> 00:05:28,260 I can edit what flights that passenger is on. 115 00:05:28,260 --> 00:05:31,160 But when I go into the admin interface for editing flights, 116 00:05:31,160 --> 00:05:33,042 I can't control what passengers they're on. 117 00:05:33,042 --> 00:05:35,000 And so this is something we can also solve just 118 00:05:35,000 --> 00:05:38,640 by doing some customization of the administrative interface. 119 00:05:38,640 --> 00:05:41,360 So what I'm going to do is first define a new class 120 00:05:41,360 --> 00:05:45,320 which extends StackedInline, where StackedInline is just one 121 00:05:45,320 --> 00:05:49,100 of Django's built in classes that lets us, in a stacked format, 122 00:05:49,100 --> 00:05:51,890 add new relationships between objects. 123 00:05:51,890 --> 00:05:54,590 And so I'm going to call this one PassengerInline, 124 00:05:54,590 --> 00:05:57,470 and you'll see why in a moment, where this is going to be the class 125 00:05:57,470 --> 00:06:00,020 that represents the place on the admin interface 126 00:06:00,020 --> 00:06:04,520 where I would like to be able to add and modify passengers. 127 00:06:04,520 --> 00:06:07,330 I need to specify what model I want to use, 128 00:06:07,330 --> 00:06:11,030 what data I want to be associated with this class that I've just created. 129 00:06:11,030 --> 00:06:14,540 And in this case I'm saying passenger.flights.through. 130 00:06:14,540 --> 00:06:18,320 This additional .through property refers to that intermediary table. 131 00:06:18,320 --> 00:06:21,830 So recall that we have a passengers table and a flights table, 132 00:06:21,830 --> 00:06:24,510 but there is some sort of relationship table between them-- 133 00:06:24,510 --> 00:06:28,080 some table that connects individual passengers to individual flights. 134 00:06:28,080 --> 00:06:31,200 And this .through is how I sort of access that intermediary. 135 00:06:31,200 --> 00:06:33,380 And extra equals one just means that I want 136 00:06:33,380 --> 00:06:35,970 to be able to add one additional passenger at a time. 137 00:06:35,970 --> 00:06:38,330 And you'll see what that looks like in a moment. 138 00:06:38,330 --> 00:06:40,730 And then in the special flight admin class 139 00:06:40,730 --> 00:06:42,950 that I'm extending from the existing admin class, 140 00:06:42,950 --> 00:06:47,750 I'm saying I want to add this additional inline section of the Admin page called 141 00:06:47,750 --> 00:06:50,810 PassengerInline, which is the one that I created up there. 142 00:06:50,810 --> 00:06:57,076 And so if I now register the flight model to my admin site, 143 00:06:57,076 --> 00:07:00,200 specifying that I want to add these additional settings in the flight admin 144 00:07:00,200 --> 00:07:04,496 settings, now when I go to edit an individual flight 145 00:07:04,496 --> 00:07:07,370 in addition to getting to edit the origin, destination, and duration, 146 00:07:07,370 --> 00:07:10,460 I also see this view, which is the stacked inline 147 00:07:10,460 --> 00:07:14,360 view, where it's just a stacked list of all of the passengers on this flight. 148 00:07:14,360 --> 00:07:17,240 Whereby if I want to add an additional passenger to the flight 149 00:07:17,240 --> 00:07:21,010 I simply need to go down here and select which passenger that I want to add. 150 00:07:21,010 --> 00:07:23,760 Of course, in this case, both passengers are already on the flight 151 00:07:23,760 --> 00:07:27,809 so it wouldn't make much sense for me to add one of those passengers again. 152 00:07:27,809 --> 00:07:31,100 But that would be the sort of thing that right now our application can't handle 153 00:07:31,100 --> 00:07:34,430 but you might want to add additional constraints that help enforce. 154 00:07:34,430 --> 00:07:39,500 And so the goal of this is just to show just a very brief teaser as to how 155 00:07:39,500 --> 00:07:41,334 you might be able to manipulate or customize 156 00:07:41,334 --> 00:07:43,874 the administrative interface to look and feel exactly the way 157 00:07:43,874 --> 00:07:44,930 that you want it to do. 158 00:07:44,930 --> 00:07:48,350 And if you take a look at the Django documentation for the admin interface, 159 00:07:48,350 --> 00:07:50,750 there are a whole lot more settings than just the couple 160 00:07:50,750 --> 00:07:52,700 that I've shown you here that you can get a taste for, 161 00:07:52,700 --> 00:07:53,908 that you can experiment with. 162 00:07:53,908 --> 00:07:57,310 And I would encourage you to experiment with it as you work on project three 163 00:07:57,310 --> 00:07:59,060 just to get a sense for the different ways 164 00:07:59,060 --> 00:08:02,090 that people can begin to interact with this administrative interface 165 00:08:02,090 --> 00:08:04,255 that Django has really built out for you. 166 00:08:04,255 --> 00:08:06,380 The other thing that I wanted to touch on last week 167 00:08:06,380 --> 00:08:09,410 that we didn't quite get to was how to deal with static files. 168 00:08:09,410 --> 00:08:13,400 So a couple of people have asked about how you use static files in Django, 169 00:08:13,400 --> 00:08:16,610 like CSS files or JavaScript files that are external. 170 00:08:16,610 --> 00:08:24,090 So the way that you would do that in Django is, inside of our HTML file, 171 00:08:24,090 --> 00:08:28,070 so one of our Django templates, what we would first need to do at the top 172 00:08:28,070 --> 00:08:31,130 is use this additional Django command called load static which 173 00:08:31,130 --> 00:08:33,350 loads static files into this page. 174 00:08:33,350 --> 00:08:38,000 And then if we wanted to reference a CSS style sheet, for example, 175 00:08:38,000 --> 00:08:40,169 this is the specific Django syntax for doing that. 176 00:08:40,169 --> 00:08:42,127 So it looks a little bit different than the way 177 00:08:42,127 --> 00:08:43,700 it looked in Flask, for instance. 178 00:08:43,700 --> 00:08:47,450 But inside of the curly braces and percent symbols, 179 00:08:47,450 --> 00:08:51,230 we'd have the static keyword just to say we want a static file to be used here. 180 00:08:51,230 --> 00:08:54,950 And then which static file, well, it'll be the flight's directory 181 00:08:54,950 --> 00:08:57,060 and then styles.css. 182 00:08:57,060 --> 00:08:59,690 And if you look at the structure of our application 183 00:08:59,690 --> 00:09:04,520 here, inside of the flight's app, I have inside of it 184 00:09:04,520 --> 00:09:08,180 not just a templates directory that has flights and then based 185 00:09:08,180 --> 00:09:10,810 on HTML along with all these other HTML files, 186 00:09:10,810 --> 00:09:13,910 but I also have a static directory, inside of which 187 00:09:13,910 --> 00:09:16,160 is the flight's directory, inside of which 188 00:09:16,160 --> 00:09:19,280 is this styles.css file, which is the static file 189 00:09:19,280 --> 00:09:22,640 that I want to load in all of my HTML files. 190 00:09:22,640 --> 00:09:25,310 The question also came up last week as to why 191 00:09:25,310 --> 00:09:28,372 it is that we need this additional flights directory. 192 00:09:28,372 --> 00:09:30,830 In the same way that we had an additional flights directory 193 00:09:30,830 --> 00:09:34,130 inside of templates, we also have this additional flights directory inside 194 00:09:34,130 --> 00:09:37,520 of the static directory which feels a little bit redundant because we already 195 00:09:37,520 --> 00:09:40,790 have a flight's directory, which is the name of the entire app. 196 00:09:40,790 --> 00:09:43,550 And the reason, again, is the same exact thing as before. 197 00:09:43,550 --> 00:09:47,030 It has to do with the fact that if I had multiple different apps, each of which 198 00:09:47,030 --> 00:09:51,874 had a styles.css file for instance, a pending flight slash in front of it 199 00:09:51,874 --> 00:09:54,290 helps to namespace that file, to make sure that I know how 200 00:09:54,290 --> 00:09:56,820 to reference it in order to access it. 201 00:09:56,820 --> 00:09:59,150 So just wanted to show you those things just 202 00:09:59,150 --> 00:10:01,610 to give you a sense for a couple of final things in Django 203 00:10:01,610 --> 00:10:04,037 that are possible that you may be interested in doing 204 00:10:04,037 --> 00:10:07,370 as you go about working on your project, though it may not actually, in the end, 205 00:10:07,370 --> 00:10:08,580 come up. 206 00:10:08,580 --> 00:10:11,270 But the main focus of what I wanted to talk about today 207 00:10:11,270 --> 00:10:13,220 is ultimately going to be about testing. 208 00:10:13,220 --> 00:10:16,830 And so as we begin to develop more and more sophisticated, more complex 209 00:10:16,830 --> 00:10:18,810 web applications, it's going to be increasingly 210 00:10:18,810 --> 00:10:22,650 important to test that code in order to verify that it's accurate. 211 00:10:22,650 --> 00:10:24,450 And so reasons why it might be good to test 212 00:10:24,450 --> 00:10:29,130 that code are to make sure that if I'm making changes to one part of the code 213 00:10:29,130 --> 00:10:32,130 base, making changes to one function that might be used 214 00:10:32,130 --> 00:10:35,040 in a dozen other places across the application, 215 00:10:35,040 --> 00:10:38,280 I want to make sure that the changes I'm making to this one function 216 00:10:38,280 --> 00:10:41,370 aren't going to cause some other part of the web application to break, 217 00:10:41,370 --> 00:10:42,270 for instance. 218 00:10:42,270 --> 00:10:44,310 And it might also be important because I'd 219 00:10:44,310 --> 00:10:47,580 want to anticipate the fact that functions may behave differently 220 00:10:47,580 --> 00:10:49,050 based on different types of inputs. 221 00:10:49,050 --> 00:10:52,480 And so I want to account for the fact that no matter what sort of input 222 00:10:52,480 --> 00:10:54,540 I provide, I want to know that my application is 223 00:10:54,540 --> 00:10:56,284 going to be able to handle them well. 224 00:10:56,284 --> 00:10:58,950 And so what we're going to spend a lot of time today focusing on 225 00:10:58,950 --> 00:11:00,720 is how to build out a system for testing. 226 00:11:00,720 --> 00:11:03,420 First of all, how you would even test things on your own. 227 00:11:03,420 --> 00:11:06,720 But then looking at what tools and technologies exist in order to help 228 00:11:06,720 --> 00:11:10,590 facilitate the testing process which ultimately becomes very, very important 229 00:11:10,590 --> 00:11:14,550 as we begin to develop more complex web applications. 230 00:11:14,550 --> 00:11:20,850 So to take a just very simple example, I wrote inside of-- 231 00:11:20,850 --> 00:11:28,130 let's go to the source and go to prime.py, 232 00:11:28,130 --> 00:11:30,170 which is a function that I wrote. 233 00:11:30,170 --> 00:11:33,260 Inside of a file called prime.py I wrote a function called is_prime. 234 00:11:33,260 --> 00:11:36,320 And what this function does is, it determines whether or not 235 00:11:36,320 --> 00:11:37,294 a number is prime. 236 00:11:37,294 --> 00:11:39,710 And this has nothing to do with web applications just yet, 237 00:11:39,710 --> 00:11:41,927 but I'm going to show you this example as a function 238 00:11:41,927 --> 00:11:44,510 that I might want to test to see whether or not it's accurate. 239 00:11:44,510 --> 00:11:46,520 And then we'll go from there in order to later 240 00:11:46,520 --> 00:11:50,450 explore, how do we apply these same ideas of testing a function in command 241 00:11:50,450 --> 00:11:54,110 line Python to going on to how do you test functions in a Django web 242 00:11:54,110 --> 00:11:56,370 application, for instance. 243 00:11:56,370 --> 00:11:59,510 So this function, is_prime, is going to take a number 244 00:11:59,510 --> 00:12:03,060 n, a non-negative integer, and tell me whether or not it is in fact prime, 245 00:12:03,060 --> 00:12:05,790 returning true if it's prime, returning false otherwise. 246 00:12:05,790 --> 00:12:08,270 And the basic logical flow for this function 247 00:12:08,270 --> 00:12:11,060 is that if the number is less than two, well, 248 00:12:11,060 --> 00:12:13,500 it's not prime because two is the smallest prime number. 249 00:12:13,500 --> 00:12:15,140 So we'll go ahead and return false. 250 00:12:15,140 --> 00:12:18,650 And then I'll run a loop going from two up 251 00:12:18,650 --> 00:12:21,980 until the square root of the number-- math.squareroot 252 00:12:21,980 --> 00:12:23,780 is a function that gets me the square root. 253 00:12:23,780 --> 00:12:28,100 And if it's the case that n mod i, where mod 254 00:12:28,100 --> 00:12:31,190 is an operator that gives me the remainder when I divide n by i-- 255 00:12:31,190 --> 00:12:36,100 if I divide the number I'm looking at by this i, this variable in the loop, 256 00:12:36,100 --> 00:12:38,660 and that number comes out to be zero-- in other words, 257 00:12:38,660 --> 00:12:41,000 n is evenly divisible by i-- 258 00:12:41,000 --> 00:12:43,910 well that means that the num n is definitely not prime 259 00:12:43,910 --> 00:12:45,920 because i is one of its factors. 260 00:12:45,920 --> 00:12:47,730 And so I'm saying, return false. 261 00:12:47,730 --> 00:12:49,670 The number is not prime. 262 00:12:49,670 --> 00:12:52,580 And then finally, if I made it through checking all of these factors 263 00:12:52,580 --> 00:12:55,940 up to the square root and none of them divided 264 00:12:55,940 --> 00:12:59,390 n evenly, then I can say that this number is in fact prime. 265 00:12:59,390 --> 00:13:01,420 And so at the end I'm going to return true. 266 00:13:01,420 --> 00:13:04,730 And so this is the basic logic for the is_prime function 267 00:13:04,730 --> 00:13:06,140 that I wanted to create. 268 00:13:06,140 --> 00:13:09,080 And now my goal is going to be, how do I test this function? 269 00:13:09,080 --> 00:13:13,440 How do I evaluate whether or not it's going to work or no? 270 00:13:13,440 --> 00:13:18,570 So one thing I can do is to test it is just to test it on my own. 271 00:13:18,570 --> 00:13:24,650 So I can open up the Python interpreter and say, from prime import 272 00:13:24,650 --> 00:13:26,210 my is_prime function. 273 00:13:26,210 --> 00:13:28,520 And now I have my is_prime function and I can test it. 274 00:13:28,520 --> 00:13:32,810 I can say, OK, is_prime of 23, well 23 is a prime number 275 00:13:32,810 --> 00:13:34,460 so this should return true. 276 00:13:34,460 --> 00:13:35,637 It should be prime. 277 00:13:35,637 --> 00:13:36,470 And it does in fact. 278 00:13:36,470 --> 00:13:39,010 And likewise I can say, is_prime 28. 279 00:13:39,010 --> 00:13:42,080 Well, 28 is not a prime number so this should be false. 280 00:13:42,080 --> 00:13:45,870 And so I press Return and indeed it does turn out to be false. 281 00:13:45,870 --> 00:13:49,550 And then I could try, is_prime 25, for instance. 282 00:13:49,550 --> 00:13:53,920 Let's see, well, 25 is not prime either because 5 is one of its factors. 283 00:13:53,920 --> 00:13:57,160 And OK, something seems to be wrong with my is_prime function. 284 00:13:57,160 --> 00:14:01,190 My is_prime function thinks that 25 is in fact prime 285 00:14:01,190 --> 00:14:02,660 when in reality it's not prime. 286 00:14:02,660 --> 00:14:08,840 So I found some sort of bug or error inside of my is_prime function. 287 00:14:08,840 --> 00:14:11,720 But all of this is ultimately going to be pretty tedious 288 00:14:11,720 --> 00:14:15,380 because as I think about using this function in the broader 289 00:14:15,380 --> 00:14:18,620 context of a larger web application where I'm constantly making changes 290 00:14:18,620 --> 00:14:21,140 to different parts of the codebase where I might be changing 291 00:14:21,140 --> 00:14:24,560 different aspects of the function, I don't want to have to, 292 00:14:24,560 --> 00:14:27,527 every single time I make a change to the program 293 00:14:27,527 --> 00:14:30,110 and I want to make sure that the is_prime function is working, 294 00:14:30,110 --> 00:14:33,672 to go in here and type is_prime 23 and 28 and 25 295 00:14:33,672 --> 00:14:36,630 and try a whole bunch of examples to make sure that it's going to work. 296 00:14:36,630 --> 00:14:39,150 So what might I do instead? 297 00:14:39,150 --> 00:14:42,230 What are some other things that I could do just to simplify this process 298 00:14:42,230 --> 00:14:45,105 or make it so I don't need to manually go into the Python interpreter 299 00:14:45,105 --> 00:14:47,239 every time and try out the is_prime function 300 00:14:47,239 --> 00:14:48,530 on a bunch of different inputs? 301 00:14:48,530 --> 00:14:54,386 302 00:14:54,386 --> 00:14:55,850 AUDIENCE: [INAUDIBLE]. 303 00:14:55,850 --> 00:15:00,049 304 00:15:00,049 --> 00:15:01,090 BRIAN YU: Great, exactly. 305 00:15:01,090 --> 00:15:03,150 I can write some sort of program that's just 306 00:15:03,150 --> 00:15:04,900 going to run a whole bunch of these for me 307 00:15:04,900 --> 00:15:07,399 in order to make sure that I have the right idea. 308 00:15:07,399 --> 00:15:09,190 And so let's try doing something like that. 309 00:15:09,190 --> 00:15:15,220 If we open up testzero.py, what I have in testzero.py is a very simple Python 310 00:15:15,220 --> 00:15:18,280 application, or really just a Python module that 311 00:15:18,280 --> 00:15:20,830 contains a single function called test prime, 312 00:15:20,830 --> 00:15:25,210 that is going to test whether or not I get the expected value that I want. 313 00:15:25,210 --> 00:15:28,900 So if I feed into this test prime function a number n 314 00:15:28,900 --> 00:15:31,660 and a Boolean value of what I expect the result to be, 315 00:15:31,660 --> 00:15:34,990 this expected value, then I can check if-- 316 00:15:34,990 --> 00:15:37,650 so run the is_prime function on n, because I imported it 317 00:15:37,650 --> 00:15:39,050 up here at the top. 318 00:15:39,050 --> 00:15:43,820 And if I check the is_prime function and it's not equal to the expected value-- 319 00:15:43,820 --> 00:15:48,250 in other words, when I perform the computation it wasn't what I expected 320 00:15:48,250 --> 00:15:50,904 it to be based on what I thought the test should result-- 321 00:15:50,904 --> 00:15:52,320 then there was some sort of error. 322 00:15:52,320 --> 00:15:54,850 There's some sort of error in my is_prime function. 323 00:15:54,850 --> 00:15:57,040 And so I'll print something like, there is 324 00:15:57,040 --> 00:16:01,030 an error on is_prime n, where n is whatever the number. 325 00:16:01,030 --> 00:16:04,452 We expected it to be whatever we expected it to be. 326 00:16:04,452 --> 00:16:06,160 And so this function is going to simplify 327 00:16:06,160 --> 00:16:10,990 the process of allowing me to test whether or not a number is prime. 328 00:16:10,990 --> 00:16:15,110 And so in order to use this, now I just need to call the test prime function. 329 00:16:15,110 --> 00:16:17,530 So I could write a Python file, for instance, 330 00:16:17,530 --> 00:16:20,440 to just use the test prime function over and over again 331 00:16:20,440 --> 00:16:22,900 in order to test whether a number is prime. 332 00:16:22,900 --> 00:16:27,250 Another thing you'll sometimes see is people writing just a bash script, 333 00:16:27,250 --> 00:16:34,180 just a script that's going to run inside of the terminal shell without anything 334 00:16:34,180 --> 00:16:34,810 more. 335 00:16:34,810 --> 00:16:37,600 And so the way bash scripts work is that they'll generally just 336 00:16:37,600 --> 00:16:40,930 be a bunch of lines of commands that I might run on the command line. 337 00:16:40,930 --> 00:16:46,790 And in this case, Python-C is just a way of saying, run this particular Python 338 00:16:46,790 --> 00:16:47,500 command. 339 00:16:47,500 --> 00:16:50,860 And so on line one I'm saying, run this Python command. 340 00:16:50,860 --> 00:16:53,590 First import the test prime function. 341 00:16:53,590 --> 00:16:55,992 And then test to make sure that one is not prime. 342 00:16:55,992 --> 00:16:58,450 And likewise, I'm doing the same thing on this line saying, 343 00:16:58,450 --> 00:17:01,840 test to make sure that two is prime, and make sure that eight is not prime 344 00:17:01,840 --> 00:17:03,380 make sure 11 is prime. 345 00:17:03,380 --> 00:17:07,260 So on and so forth, testing all of these possible input values. 346 00:17:07,260 --> 00:17:12,310 And now if I wanted to run this I could run that script. 347 00:17:12,310 --> 00:17:16,450 And what I get is for the two for which the number did not 348 00:17:16,450 --> 00:17:20,300 match the expected result, where I ran is_prime eight, 349 00:17:20,300 --> 00:17:23,680 I expected eight to not be prime but there seems to be some sort of error 350 00:17:23,680 --> 00:17:24,410 here. 351 00:17:24,410 --> 00:17:27,700 I get that there is some error that's listed here. 352 00:17:27,700 --> 00:17:28,760 And so that's helpful. 353 00:17:28,760 --> 00:17:32,380 Now, any time I make some sort of change to my web application or my application 354 00:17:32,380 --> 00:17:35,350 in general, I can run a sample test file that's 355 00:17:35,350 --> 00:17:38,530 going to run a whole bunch of these tests and then give back to me 356 00:17:38,530 --> 00:17:41,050 some information about any line of output 357 00:17:41,050 --> 00:17:44,260 that I get here is a sign that something wasn't quite right, 358 00:17:44,260 --> 00:17:47,650 that something went wrong over the course of me trying to test 359 00:17:47,650 --> 00:17:50,080 this function or this application. 360 00:17:50,080 --> 00:17:54,790 So if we go back to prime.py, I can begin to start debugging this, 361 00:17:54,790 --> 00:17:57,157 try and figure out what exactly went wrong. 362 00:17:57,157 --> 00:17:59,948 Does anyone have a sense for what might be wrong, out of curiosity? 363 00:17:59,948 --> 00:18:02,820 364 00:18:02,820 --> 00:18:07,700 Why it seems to be thinking that more numbers are 365 00:18:07,700 --> 00:18:10,576 in fact prime when they're not? 366 00:18:10,576 --> 00:18:12,450 If you played around with it for long enough, 367 00:18:12,450 --> 00:18:14,866 you might find that it has to do something with this loop. 368 00:18:14,866 --> 00:18:18,290 That for some reason I'm not testing enough possible values in the loop. 369 00:18:18,290 --> 00:18:20,780 And in particular, it has to do with the fact 370 00:18:20,780 --> 00:18:25,850 that when Python does loops in a range, it 371 00:18:25,850 --> 00:18:28,010 includes the first thing in the range but it 372 00:18:28,010 --> 00:18:30,080 excludes the last thing in the range. 373 00:18:30,080 --> 00:18:37,040 So for instance, if I do for i in range zero to five, print i, what I get is, 374 00:18:37,040 --> 00:18:38,990 I start with zero and it goes all the way 375 00:18:38,990 --> 00:18:41,120 up to for but it doesn't actually include five. 376 00:18:41,120 --> 00:18:43,160 So it includes the first thing in the range 377 00:18:43,160 --> 00:18:45,300 but it doesn't include the last thing in the range. 378 00:18:45,300 --> 00:18:47,091 This is a common source of what we call off 379 00:18:47,091 --> 00:18:51,080 by one errors in Python and other languages, where we had the right idea 380 00:18:51,080 --> 00:18:53,690 but we were just off by one number in something 381 00:18:53,690 --> 00:18:55,370 that we were trying to calculate. 382 00:18:55,370 --> 00:18:57,650 And so the problem in this is prime function 383 00:18:57,650 --> 00:19:00,290 just happens to be another one of those off by one errors 384 00:19:00,290 --> 00:19:03,650 that really, in order to include that final number, that square root that I 385 00:19:03,650 --> 00:19:06,800 want to test, I additionally need to test that last number, 386 00:19:06,800 --> 00:19:09,920 the actual square root itself, or the number closest to it 387 00:19:09,920 --> 00:19:12,365 if it doesn't have an integer square root. 388 00:19:12,365 --> 00:19:14,240 And so now that I've fixed the function and I 389 00:19:14,240 --> 00:19:17,570 want to test it, rather than go back into Python 390 00:19:17,570 --> 00:19:20,120 and then saying from prime import is_prime 391 00:19:20,120 --> 00:19:25,070 and then testing all of these individual values again, all I have to do 392 00:19:25,070 --> 00:19:27,230 is run that test file again. 393 00:19:27,230 --> 00:19:29,945 And this time the test file produced no output. 394 00:19:29,945 --> 00:19:31,820 Remember output was only going to be produced 395 00:19:31,820 --> 00:19:34,850 if there was some sort of error, if there was a line that was incorrect. 396 00:19:34,850 --> 00:19:38,360 And now when I run the test file I get no errors which tells me that, at least 397 00:19:38,360 --> 00:19:41,840 for the inputs that I provided, the test seemed to be working. 398 00:19:41,840 --> 00:19:45,260 Now this doesn't mean that my function is necessarily totally correct 399 00:19:45,260 --> 00:19:49,760 because it just means that whatever test I decided to give to this program, 400 00:19:49,760 --> 00:19:51,519 it passed those individual tests. 401 00:19:51,519 --> 00:19:53,810 But that's where it starts to become important to write 402 00:19:53,810 --> 00:19:55,220 good comprehensive tests. 403 00:19:55,220 --> 00:19:57,290 To make sure that if you're going to test 404 00:19:57,290 --> 00:19:59,510 a function or a particular part of your application, 405 00:19:59,510 --> 00:20:02,090 that you're really testing it under all possible conditions. 406 00:20:02,090 --> 00:20:05,390 That we have tests for even numbers and odd numbers 407 00:20:05,390 --> 00:20:08,300 and different types of numbers to really try and get a sense for, 408 00:20:08,300 --> 00:20:11,360 is this function really correct by testing it on as many different things 409 00:20:11,360 --> 00:20:11,860 as we can. 410 00:20:11,860 --> 00:20:13,693 And that's going to be important as we start 411 00:20:13,693 --> 00:20:16,610 to build larger web applications that are getting more complicated. 412 00:20:16,610 --> 00:20:18,650 We want to make sure that our tests are testing 413 00:20:18,650 --> 00:20:23,152 as many different possible situations that might come up as possible. 414 00:20:23,152 --> 00:20:24,360 Questions on anything so far? 415 00:20:24,360 --> 00:20:27,420 416 00:20:27,420 --> 00:20:28,400 OK. 417 00:20:28,400 --> 00:20:31,670 So we'll take a look at a couple other features of Python that 418 00:20:31,670 --> 00:20:33,860 might be useful as we go about testing. 419 00:20:33,860 --> 00:20:38,690 And one thing is Python's built in assert command. 420 00:20:38,690 --> 00:20:42,590 And so the Python assert command really does one thing and one thing only. 421 00:20:42,590 --> 00:20:46,700 It takes the expression that comes after the word assert and it asserts, or just 422 00:20:46,700 --> 00:20:50,391 states, that this expression is going to be true. 423 00:20:50,391 --> 00:20:52,640 And if it's not true, then Python will throw an error, 424 00:20:52,640 --> 00:20:56,360 the same way Python throws an error if you try to divide by zero for instance, 425 00:20:56,360 --> 00:20:59,070 or access something in a dictionary that doesn't exist. 426 00:20:59,070 --> 00:21:02,830 It'll throw a particular exception known as-- 427 00:21:02,830 --> 00:21:04,520 it'll throw an assertion error. 428 00:21:04,520 --> 00:21:08,750 And so if I assert like, the statement true, for instance, nothing happens. 429 00:21:08,750 --> 00:21:10,260 It just states that it was true. 430 00:21:10,260 --> 00:21:12,750 And it's not actually changing or modifying anything. 431 00:21:12,750 --> 00:21:15,590 But if I assert something that's false, then I 432 00:21:15,590 --> 00:21:17,130 get this assertion error exception. 433 00:21:17,130 --> 00:21:19,820 The whole program quits and I get this error 434 00:21:19,820 --> 00:21:23,240 that says the expression that came after the assert was not true 435 00:21:23,240 --> 00:21:25,230 so there was some sort of problem. 436 00:21:25,230 --> 00:21:29,480 And so this can be useful if I'm trying to see whether or not 437 00:21:29,480 --> 00:21:33,380 a program is working correctly or not because if I want to state definitively 438 00:21:33,380 --> 00:21:36,740 that something is supposed to be true, I can assert that it is a true. 439 00:21:36,740 --> 00:21:40,970 And if it's not true, then my program is going to give me some sort of error. 440 00:21:40,970 --> 00:21:44,810 And so if I look at assert0.py, for instance, here's 441 00:21:44,810 --> 00:21:49,130 a very simple file that is just going to define a function called 442 00:21:49,130 --> 00:21:54,440 square that takes a number x and returns x times x, returns the square of x. 443 00:21:54,440 --> 00:21:59,060 And down here on line four, I'm just going to assert that the square of 10 444 00:21:59,060 --> 00:22:01,040 is equal to 100, because that should be true. 445 00:22:01,040 --> 00:22:02,600 The square root of 10 should be 100. 446 00:22:02,600 --> 00:22:06,270 And I want to assert that that is in fact the case. 447 00:22:06,270 --> 00:22:10,455 So now if I try to run assert0.py, nothing happens, right? 448 00:22:10,455 --> 00:22:13,580 Assert doesn't print anything to the screen or have any other side effects. 449 00:22:13,580 --> 00:22:17,700 It just asserts that the thing that comes after it is in fact true. 450 00:22:17,700 --> 00:22:22,802 If it's not true, in an assertone.py for instance, if I try to assert a fact 451 00:22:22,802 --> 00:22:23,510 that is not true. 452 00:22:23,510 --> 00:22:28,490 I try to assert that the square of 10 is a 101, for instance, instead of 100. 453 00:22:28,490 --> 00:22:33,980 Now when I try to run assert1.py, now I get this assertion error 454 00:22:33,980 --> 00:22:37,005 because the thing that I was asserting was in fact false 455 00:22:37,005 --> 00:22:39,380 and so the Python program is going to give me some error. 456 00:22:39,380 --> 00:22:43,580 It's going to quit and say that something went wrong. 457 00:22:43,580 --> 00:22:48,290 And in particular, all programs when I run them give me some sort of exit code 458 00:22:48,290 --> 00:22:52,860 that indicate whether or not the program ended in a particular state or not. 459 00:22:52,860 --> 00:22:55,880 And generally speaking an exit code of zero means the program ended 460 00:22:55,880 --> 00:22:57,190 and everything went well. 461 00:22:57,190 --> 00:23:00,890 And an exit code of something other than zero, like one or something else, 462 00:23:00,890 --> 00:23:05,000 means that something went wrong as I was running this particular program. 463 00:23:05,000 --> 00:23:07,970 And in bash, in the terminal environment that I'm using, 464 00:23:07,970 --> 00:23:12,800 if I want to look at the exit code I can type echo $?. 465 00:23:12,800 --> 00:23:15,830 And here I see that the exit code of this program was one. 466 00:23:15,830 --> 00:23:18,740 Something went wrong and therefore this program 467 00:23:18,740 --> 00:23:21,380 exited with a status code other than zero. 468 00:23:21,380 --> 00:23:24,030 Whereas meanwhile if I had just run assert0.py, 469 00:23:24,030 --> 00:23:26,060 and I looked at what the exit code of that was, 470 00:23:26,060 --> 00:23:28,268 that exit code was zero because everything went fine. 471 00:23:28,268 --> 00:23:29,370 There was no problem. 472 00:23:29,370 --> 00:23:32,060 And so we'll return to this idea of exit codes 473 00:23:32,060 --> 00:23:35,210 and how we can use the fact that exit code zero means everything's 474 00:23:35,210 --> 00:23:37,880 OK and exit code one or something else means something 475 00:23:37,880 --> 00:23:39,712 went wrong a little bit later. 476 00:23:39,712 --> 00:23:41,420 But this just gives you the sense that we 477 00:23:41,420 --> 00:23:44,330 can use these assertions to either have some sort of error 478 00:23:44,330 --> 00:23:48,710 if something goes wrong, or do nothing if everything was OK. 479 00:23:48,710 --> 00:23:51,320 But of course it's going to again be tedious 480 00:23:51,320 --> 00:23:53,690 if we continually have to write these assert statements 481 00:23:53,690 --> 00:23:55,694 after assert statements all the time in order 482 00:23:55,694 --> 00:23:57,110 to verify that things are correct. 483 00:23:57,110 --> 00:23:59,960 It would be nice if there were some easier ways to do this. 484 00:23:59,960 --> 00:24:02,690 And Python does, in fact, allow us some tools 485 00:24:02,690 --> 00:24:04,460 that make it easier to do testing. 486 00:24:04,460 --> 00:24:06,710 And in particular, they have a framework called 487 00:24:06,710 --> 00:24:10,370 unittest which is a Python library that's 488 00:24:10,370 --> 00:24:13,040 designed to help make it easier to test your programs. 489 00:24:13,040 --> 00:24:14,600 And so we'll take a look at unittest. 490 00:24:14,600 --> 00:24:19,830 And then we'll look at how we can apply unittest to actual web applications. 491 00:24:19,830 --> 00:24:23,700 And so what we're building up to now is in tests1.py. 492 00:24:23,700 --> 00:24:26,030 And so this will look a little more sophisticated. 493 00:24:26,030 --> 00:24:29,630 And what we've done here is, first, import unittest. 494 00:24:29,630 --> 00:24:32,550 And now I'm defining an entire class. 495 00:24:32,550 --> 00:24:36,030 This class is going to extend unittest.testcase. 496 00:24:36,030 --> 00:24:39,860 So it's a class that's going to contain a whole bunch of individual tests 497 00:24:39,860 --> 00:24:42,230 that I want to run on my code. 498 00:24:42,230 --> 00:24:47,267 And each test is just going to be a function, or a method inside 499 00:24:47,267 --> 00:24:47,850 of this class. 500 00:24:47,850 --> 00:24:53,300 So I define a test called test1 and this docstring here, this comment, 501 00:24:53,300 --> 00:24:55,804 is just a label for the test such that if the test fails, 502 00:24:55,804 --> 00:24:57,220 then I'll know something about it. 503 00:24:57,220 --> 00:24:59,640 I'll be presented with this label for it. 504 00:24:59,640 --> 00:25:01,820 And so the name for this test, my description of it, 505 00:25:01,820 --> 00:25:04,146 is that it's going to check that one is not prime. 506 00:25:04,146 --> 00:25:06,020 And unittest has a whole bunch of these built 507 00:25:06,020 --> 00:25:07,970 in functions for asserting things. 508 00:25:07,970 --> 00:25:11,850 Rather than just a plain old assert that says assert some expression, 509 00:25:11,850 --> 00:25:14,210 there is a special assert false, for instance, 510 00:25:14,210 --> 00:25:16,970 that I want to assert false is_prime one. 511 00:25:16,970 --> 00:25:21,090 In other words, assert the fact that if I evaluate is_prime of one, 512 00:25:21,090 --> 00:25:22,910 that that should be false. 513 00:25:22,910 --> 00:25:27,920 And likewise, in test2, where I want to check that two is prime, 514 00:25:27,920 --> 00:25:30,920 I can likewise call is_prime applied to two 515 00:25:30,920 --> 00:25:34,650 and assert that the result of that expression should be true. 516 00:25:34,650 --> 00:25:38,600 And so I can define a whole bunch of these functions, each of which 517 00:25:38,600 --> 00:25:41,880 is just going to be an individual test that I want to run. 518 00:25:41,880 --> 00:25:45,020 So here is checking that eight is not prime, checking that 11 is prime, 519 00:25:45,020 --> 00:25:49,070 checking that 25 is not prime, checking that 28 is not prime, for instance. 520 00:25:49,070 --> 00:25:51,560 And then at the end down at the bottom I'm 521 00:25:51,560 --> 00:25:56,780 just going to run unittest.main, which is going to run all of my unit tests. 522 00:25:56,780 --> 00:26:00,590 In other words, run all of these individual functions. 523 00:26:00,590 --> 00:26:05,510 And so if I now run tests1.py then what I get 524 00:26:05,510 --> 00:26:10,620 is that it says it ran six tests in 0.001 seconds and, OK. 525 00:26:10,620 --> 00:26:11,960 Everything was fine. 526 00:26:11,960 --> 00:26:15,260 So nothing went wrong in this case. 527 00:26:15,260 --> 00:26:17,420 I didn't get any errors. 528 00:26:17,420 --> 00:26:23,060 If I change my prime function and put that error back in, 529 00:26:23,060 --> 00:26:29,420 such that now the is_prime function is buggy, and now I try to run tests1.py, 530 00:26:29,420 --> 00:26:31,610 now I get a whole bunch of these interesting errors. 531 00:26:31,610 --> 00:26:34,130 So up here at the top it's telling me what happened. 532 00:26:34,130 --> 00:26:37,190 This dot just means this first test passed, it passed, it passed. 533 00:26:37,190 --> 00:26:39,917 This one failed, then it passed, and then this one failed. 534 00:26:39,917 --> 00:26:42,500 And now it's going to tell me which tests specifically failed. 535 00:26:42,500 --> 00:26:46,160 So I failed the test 25 check, which is the check that's 536 00:26:46,160 --> 00:26:47,780 going to check that 25 is not prime. 537 00:26:47,780 --> 00:26:51,170 Again, this was that description line that I included in that triple quoted 538 00:26:51,170 --> 00:26:51,841 docstring. 539 00:26:51,841 --> 00:26:53,590 And then it's going to tell me, all right, 540 00:26:53,590 --> 00:26:55,940 what was the line that caused the assertion error? 541 00:26:55,940 --> 00:27:00,800 Well the error with here where I was asserting the fact that 25 should not 542 00:27:00,800 --> 00:27:03,620 be prime, that is_prime25 should evaluate to false 543 00:27:03,620 --> 00:27:06,210 and it was an error because true is not false. 544 00:27:06,210 --> 00:27:08,600 And likewise I got the same error on this test eight 545 00:27:08,600 --> 00:27:11,570 function, where I wanted to make sure that eight is not prime. 546 00:27:11,570 --> 00:27:14,720 And likewise I got another assertion error there as well. 547 00:27:14,720 --> 00:27:18,784 And so that seems like a little bit of overkill for a function 548 00:27:18,784 --> 00:27:21,200 this simple that's just testing whether a number is prime. 549 00:27:21,200 --> 00:27:24,500 And it seems like we probably could have done this without using unittest, 550 00:27:24,500 --> 00:27:25,610 and in fact, we did. 551 00:27:25,610 --> 00:27:30,350 But as we begin to want to test more sophisticated, more complex 552 00:27:30,350 --> 00:27:33,350 programs we'll see how the value of unittest really comes in 553 00:27:33,350 --> 00:27:35,480 and how we can begin to build on this in order 554 00:27:35,480 --> 00:27:39,080 to make our tests more comprehensive, make the process 555 00:27:39,080 --> 00:27:40,820 of running tests even easier. 556 00:27:40,820 --> 00:27:43,280 But questions about anything we've seen so far with just 557 00:27:43,280 --> 00:27:47,008 unittest before we move on? 558 00:27:47,008 --> 00:27:48,002 Yeah. 559 00:27:48,002 --> 00:27:51,470 AUDIENCE: [INAUDIBLE]. 560 00:27:51,470 --> 00:27:52,470 BRIAN YU: Good question. 561 00:27:52,470 --> 00:27:55,430 The echo command is just a Unix command that basically just 562 00:27:55,430 --> 00:27:56,390 prints something out. 563 00:27:56,390 --> 00:27:58,730 And in this case I wanted to print out to the screen 564 00:27:58,730 --> 00:28:03,517 the return value, or the exit status, of the previous command that I ran. 565 00:28:03,517 --> 00:28:04,100 Good question. 566 00:28:04,100 --> 00:28:06,680 567 00:28:06,680 --> 00:28:13,080 OK, so that was unittest in the context of just a basic Python file, 568 00:28:13,080 --> 00:28:15,370 a Python module that contained a function. 569 00:28:15,370 --> 00:28:20,550 Now let's try and apply this to unittest in the context, or unit testing 570 00:28:20,550 --> 00:28:23,140 in the context of an actual web application. 571 00:28:23,140 --> 00:28:28,890 And so let's go into airline1 for now. 572 00:28:28,890 --> 00:28:34,407 And we'll take a look at inside of our flights application. 573 00:28:34,407 --> 00:28:37,240 We've been looking at all these various files that we've had before. 574 00:28:37,240 --> 00:28:39,514 So we had this admin.py file, which we saw 575 00:28:39,514 --> 00:28:42,180 before helped us to create the admin interface however we liked. 576 00:28:42,180 --> 00:28:46,930 The apps.py file was a file that just defined basic configuration settings. 577 00:28:46,930 --> 00:28:49,780 Models.py was on we were using to define our different models, 578 00:28:49,780 --> 00:28:52,470 whether they were airports or flights or passengers. 579 00:28:52,470 --> 00:28:55,050 And we dealt with URLs in view last time too 580 00:28:55,050 --> 00:28:57,540 in order to manipulate what people see and what URLs 581 00:28:57,540 --> 00:28:59,340 they have to go in order to see it. 582 00:28:59,340 --> 00:29:04,140 But the one file we really didn't touch last time was this tests.py file. 583 00:29:04,140 --> 00:29:07,290 So Django comes built in with its own testing framework in order 584 00:29:07,290 --> 00:29:10,290 to make it easy for us to run tests on our web applications 585 00:29:10,290 --> 00:29:13,050 because it's designed with this idea that we should make sure 586 00:29:13,050 --> 00:29:17,800 that our applications passing our tests before we deploy it for users to use. 587 00:29:17,800 --> 00:29:20,850 And so if we open up test.py, here's some tests 588 00:29:20,850 --> 00:29:24,750 that I've written in order to allow and facilitate 589 00:29:24,750 --> 00:29:27,592 for the process of testing this web application. 590 00:29:27,592 --> 00:29:29,550 And so the first thing you'll see up at the top 591 00:29:29,550 --> 00:29:33,150 is that I'm importing from Django.test, I'm importing TestCase. 592 00:29:33,150 --> 00:29:37,230 TestCase is an extension to the unittest framework. 593 00:29:37,230 --> 00:29:40,170 Django has taken unittest and built onto it 594 00:29:40,170 --> 00:29:44,490 in order to make it easier to test some Django web application specific things. 595 00:29:44,490 --> 00:29:46,730 And you'll see examples of that later. 596 00:29:46,730 --> 00:29:50,370 And so here's my class that's going to contain functions for each of the tests 597 00:29:50,370 --> 00:29:51,690 that I want to run. 598 00:29:51,690 --> 00:29:56,040 And inside of this class, the first thing that I do is set up the tests. 599 00:29:56,040 --> 00:30:01,980 So this is a function set up, built into the TestCase framework, that is 600 00:30:01,980 --> 00:30:04,500 going to run before any test ever runs. 601 00:30:04,500 --> 00:30:06,570 So before any of my individual test functions 602 00:30:06,570 --> 00:30:09,180 that we're about to see ever actually happen, 603 00:30:09,180 --> 00:30:10,980 the first thing that's going to happen is 604 00:30:10,980 --> 00:30:13,500 that this set up method is going to run which is going 605 00:30:13,500 --> 00:30:15,120 to do a couple of interesting things. 606 00:30:15,120 --> 00:30:17,220 And it's just going to set up the tests such 607 00:30:17,220 --> 00:30:19,810 that I can test things that I care about later. 608 00:30:19,810 --> 00:30:22,860 So maybe I care about testing things about airports and flights. 609 00:30:22,860 --> 00:30:26,400 So in order to really test this, the first thing that I want to do 610 00:30:26,400 --> 00:30:28,800 is set up those tests by creating some airports 611 00:30:28,800 --> 00:30:31,867 and creating some flights inside of my database such that I can then 612 00:30:31,867 --> 00:30:34,950 run some tests to make sure that they're behaving the way that they should 613 00:30:34,950 --> 00:30:36,310 behave. 614 00:30:36,310 --> 00:30:40,710 And so on lines 11 and 12 here, you see that I'm creating new airports. 615 00:30:40,710 --> 00:30:43,470 So this is a slightly different syntax for creating objects 616 00:30:43,470 --> 00:30:47,050 than we saw last time where before we just use airport and then all 617 00:30:47,050 --> 00:30:48,690 of the properties and then we saved it. 618 00:30:48,690 --> 00:30:50,689 This is another way of achieving the same thing. 619 00:30:50,689 --> 00:30:52,020 Just wanted to show you both. 620 00:30:52,020 --> 00:30:55,010 And so we can say airport.objects.create, 621 00:30:55,010 --> 00:30:58,440 create a new airport with code AAA and city City_A. 622 00:30:58,440 --> 00:31:01,590 Create another one with code BBB and city City_B. 623 00:31:01,590 --> 00:31:05,190 And then create a whole bunch of these individual flights 624 00:31:05,190 --> 00:31:08,430 where this has origin A1, A2, duration 100. 625 00:31:08,430 --> 00:31:11,310 And a couple others have different origins and destinations 626 00:31:11,310 --> 00:31:12,810 and different durations. 627 00:31:12,810 --> 00:31:15,120 Now what am I actually going to test? 628 00:31:15,120 --> 00:31:17,610 Well, let's take a look at an example of a function 629 00:31:17,610 --> 00:31:19,650 that I might be interested in testing. 630 00:31:19,650 --> 00:31:23,240 So I've added to models.py. 631 00:31:23,240 --> 00:31:28,250 I've added to the flight model this additional function 632 00:31:28,250 --> 00:31:29,840 called is_valid_flight. 633 00:31:29,840 --> 00:31:33,620 So the issue came up during last week's lecture of, well, 634 00:31:33,620 --> 00:31:36,800 right now based on the way that the flights are set up it's 635 00:31:36,800 --> 00:31:40,090 not at all clear that a flight is necessarily a valid flight. 636 00:31:40,090 --> 00:31:42,080 It might be the case that someone could create 637 00:31:42,080 --> 00:31:45,970 a flight that goes from London Heathrow Airport to the same airport 638 00:31:45,970 --> 00:31:47,720 and that wouldn't really be a valid flight 639 00:31:47,720 --> 00:31:50,803 but we'd be allowed to do it based on the way that our database is set up. 640 00:31:50,803 --> 00:31:54,470 And so maybe we want to add a function that is going to take a flight 641 00:31:54,470 --> 00:31:57,140 and check to make sure that it is, in fact, valid. 642 00:31:57,140 --> 00:31:59,840 And so that way when we display the page that 643 00:31:59,840 --> 00:32:01,670 displays information about that flight, it 644 00:32:01,670 --> 00:32:04,920 would also include information about whether that flight is valid or not. 645 00:32:04,920 --> 00:32:07,380 And if it's not valid, then we could do something about it. 646 00:32:07,380 --> 00:32:09,230 We might also, likewise you can imagine we 647 00:32:09,230 --> 00:32:11,690 could extend this to have a page that would just show me 648 00:32:11,690 --> 00:32:16,010 all of the currently invalid flights, all the flights that 649 00:32:16,010 --> 00:32:19,880 don't satisfy some constraint that we want to have on flights. 650 00:32:19,880 --> 00:32:23,780 And so what does my is_valid_flight function do right now? 651 00:32:23,780 --> 00:32:27,150 Well right now it returns a Boolean value, true or false, 652 00:32:27,150 --> 00:32:29,450 indicating whether or not a flight is valid. 653 00:32:29,450 --> 00:32:31,500 And what does it mean for a flight to be valid? 654 00:32:31,500 --> 00:32:35,930 Well in this case I'm saying a flight is valid if the origin of the flight 655 00:32:35,930 --> 00:32:37,571 is not equal to the destination. 656 00:32:37,571 --> 00:32:40,070 In other words, it starts somewhere and ends somewhere else. 657 00:32:40,070 --> 00:32:43,820 If it's starting and ending in the same place that's not a valid flight. 658 00:32:43,820 --> 00:32:46,820 And likewise, the duration needs to be greater than or equal to zero. 659 00:32:46,820 --> 00:32:49,340 I don't want a negative length flight because that 660 00:32:49,340 --> 00:32:51,780 wouldn't make a whole lot of sense. 661 00:32:51,780 --> 00:32:55,950 Questions about is_valid_flight? 662 00:32:55,950 --> 00:32:58,410 OK, so now what I might want to do is actually test this, 663 00:32:58,410 --> 00:33:01,350 make sure the is_valid_flight method is actually 664 00:33:01,350 --> 00:33:05,040 going to work because if I'm going to use it inside of my view, for instance, 665 00:33:05,040 --> 00:33:05,940 then it better work. 666 00:33:05,940 --> 00:33:08,070 And in fact I do use it in my view because if I 667 00:33:08,070 --> 00:33:11,580 take a look at the flight.html page, this 668 00:33:11,580 --> 00:33:14,640 was the page that before showed me just information about the flight 669 00:33:14,640 --> 00:33:16,560 number, origin, and destination. 670 00:33:16,560 --> 00:33:20,400 I've added an additional list item here that's just indicating whether or not 671 00:33:20,400 --> 00:33:21,260 the flight is valid. 672 00:33:21,260 --> 00:33:24,390 So valid is just going to be true or false by accessing 673 00:33:24,390 --> 00:33:27,600 the is_valid_flight property of the flight, which will now 674 00:33:27,600 --> 00:33:29,720 tell me whether the flight is valid. 675 00:33:29,720 --> 00:33:37,860 And so you'll notice that if I run the Django server now and go here, 676 00:33:37,860 --> 00:33:40,440 and I click on an individual flight, then what 677 00:33:40,440 --> 00:33:43,350 I get is this additional property on the flight page 678 00:33:43,350 --> 00:33:46,150 that's says valid true, meaning this slide is valid. 679 00:33:46,150 --> 00:33:48,130 It would be valid false if it were not valid. 680 00:33:48,130 --> 00:33:50,130 And so now that this valid function is something 681 00:33:50,130 --> 00:33:52,650 that's integrated into the logic of my web application 682 00:33:52,650 --> 00:33:57,120 I want to test to make sure that it is, in fact, going to work. 683 00:33:57,120 --> 00:34:00,840 And so I've created two airports, airport1 and airport2. 684 00:34:00,840 --> 00:34:03,780 This flight goes from airport1 to airport2. 685 00:34:03,780 --> 00:34:05,771 This flight goes from airport1 to airpor1, 686 00:34:05,771 --> 00:34:08,520 which would be a sign that that should be an invalid flight that I 687 00:34:08,520 --> 00:34:10,230 might be interested in testing. 688 00:34:10,230 --> 00:34:12,520 And here's one that goes from airport1 to airport2, 689 00:34:12,520 --> 00:34:17,230 but it has a negative duration, a duration of negative 100 for instance. 690 00:34:17,230 --> 00:34:21,360 And so now here's an example of a sample test. 691 00:34:21,360 --> 00:34:26,340 I'm testing the departure count of an airport. 692 00:34:26,340 --> 00:34:30,179 And so remember that last week when we set up our models for airports, 693 00:34:30,179 --> 00:34:36,389 we also allowed for an individual flight to be related to its two departures 694 00:34:36,389 --> 00:34:39,659 such that I can take an airport and access all of the departures 695 00:34:39,659 --> 00:34:41,540 from that particular airport. 696 00:34:41,540 --> 00:34:44,310 And so here what I'm testing is I'm making sure 697 00:34:44,310 --> 00:34:46,949 that that departure's relationship really works. 698 00:34:46,949 --> 00:34:51,480 So I'm getting an airport by saying, get me the airport that has code AAA. 699 00:34:51,480 --> 00:34:54,610 And now I want to assert that these things are equal to each other. 700 00:34:54,610 --> 00:34:59,900 I want to assert that the count of the number of departures should be three. 701 00:34:59,900 --> 00:35:03,000 And so if the count of the number of departures from A1 is three, 702 00:35:03,000 --> 00:35:04,950 then all is well and my test will pass. 703 00:35:04,950 --> 00:35:07,980 And if something's wrong there, if the departure count is not three, 704 00:35:07,980 --> 00:35:10,650 then this test will fail. 705 00:35:10,650 --> 00:35:12,851 Let's see the other tests that I've written. 706 00:35:12,851 --> 00:35:15,100 Here is one to test the arrival count in the same way. 707 00:35:15,100 --> 00:35:18,602 There's only one flight that lands at City_A, 708 00:35:18,602 --> 00:35:20,310 and so I'm testing to make sure that if I 709 00:35:20,310 --> 00:35:25,290 count the number of arrivals at City_A, that should only be one as well. 710 00:35:25,290 --> 00:35:28,200 And now here I'm starting to test whether or not 711 00:35:28,200 --> 00:35:29,860 these flights are actually valid. 712 00:35:29,860 --> 00:35:34,824 So let me try and get the airport1, airport2 in the flight. 713 00:35:34,824 --> 00:35:36,990 And I want to make sure that this is the flight that 714 00:35:36,990 --> 00:35:39,995 goes from airport1 to airport2 and has a duration of 100 715 00:35:39,995 --> 00:35:42,120 because I might be trying to get some other flight. 716 00:35:42,120 --> 00:35:44,640 And then I'm going to assert the fact that F, 717 00:35:44,640 --> 00:35:47,070 this flight here, this should be a valid flight 718 00:35:47,070 --> 00:35:50,910 because it's going from airport1 to airport2, it has a duration of 100. 719 00:35:50,910 --> 00:35:54,120 That's a valid flight and I want to assert that that is in fact true. 720 00:35:54,120 --> 00:35:56,640 And so I'm testing that. 721 00:35:56,640 --> 00:35:58,630 Here, now let me test other things as well. 722 00:35:58,630 --> 00:36:00,780 So in addition to testing whether a flight is valid 723 00:36:00,780 --> 00:36:03,310 I want to test whether flights are correctly invalid. 724 00:36:03,310 --> 00:36:05,550 And so if there's an invalid destination where 725 00:36:05,550 --> 00:36:07,770 the destination is the same as the origin, 726 00:36:07,770 --> 00:36:09,480 that should not be a valid flight. 727 00:36:09,480 --> 00:36:14,490 So if I grab the flight whose origin is city one and whose destination is city 728 00:36:14,490 --> 00:36:21,060 one, if I run F.is_valid_flight well, that should be false because that 729 00:36:21,060 --> 00:36:23,850 flight should not be a valid flight. 730 00:36:23,850 --> 00:36:28,230 And likewise I can do a test for an invalid flight duration, 731 00:36:28,230 --> 00:36:32,250 that if I query for that flight that I created that has a duration of negative 732 00:36:32,250 --> 00:36:37,080 100 and then assert that F.is_valid_flight is false, well, 733 00:36:37,080 --> 00:36:40,650 that should work because this flight that has a duration of negative 100 734 00:36:40,650 --> 00:36:43,330 minutes is very clearly not a valid flight. 735 00:36:43,330 --> 00:36:46,140 And so when I run the is_valid_flight function then 736 00:36:46,140 --> 00:36:48,960 that should result in false. 737 00:36:48,960 --> 00:36:51,600 Questions about any of these tests or how they work? 738 00:36:51,600 --> 00:36:54,220 739 00:36:54,220 --> 00:36:54,720 Yep. 740 00:36:54,720 --> 00:36:56,940 AUDIENCE: [INAUDIBLE]. 741 00:36:56,940 --> 00:36:57,736 BRIAN YU: Yep. 742 00:36:57,736 --> 00:36:59,224 AUDIENCE: [INAUDIBLE]? 743 00:36:59,224 --> 00:37:08,750 744 00:37:08,750 --> 00:37:09,750 BRIAN YU: Good question. 745 00:37:09,750 --> 00:37:11,340 What is the .get doing? 746 00:37:11,340 --> 00:37:14,670 .get is just a simple way to query for a single thing. 747 00:37:14,670 --> 00:37:17,400 So normally I might try and query something 748 00:37:17,400 --> 00:37:22,590 by filtering to try and get only the things that are from CityA, 749 00:37:22,590 --> 00:37:23,400 for instance. 750 00:37:23,400 --> 00:37:26,859 And that might give me a query set of multiple different possible responses. 751 00:37:26,859 --> 00:37:28,650 But if I only care about getting one thing, 752 00:37:28,650 --> 00:37:32,850 if there's only one city that has city code AAA, 753 00:37:32,850 --> 00:37:36,540 then I can use .get to just say, get me the thing that has city code A. 754 00:37:36,540 --> 00:37:40,440 That only works, though, if there is a city with code AAA in the database. 755 00:37:40,440 --> 00:37:43,500 And the reason that's there is because of this setup function 756 00:37:43,500 --> 00:37:46,590 where I initially add a city here by creating 757 00:37:46,590 --> 00:37:48,960 a new airport that has code AAA. 758 00:37:48,960 --> 00:37:53,430 So if I were to do like .get equals CCC, for example, 759 00:37:53,430 --> 00:37:57,960 that wouldn't work because that does not already exist in the database. 760 00:37:57,960 --> 00:37:59,850 And so now that I've created these tests I 761 00:37:59,850 --> 00:38:02,100 can begin to use Django's built in testing 762 00:38:02,100 --> 00:38:04,500 infrastructure to actually run these tests. 763 00:38:04,500 --> 00:38:06,820 And this is quite powerful for a number of reasons. 764 00:38:06,820 --> 00:38:09,810 One, because it makes it very easy just to quickly run all of the tests 765 00:38:09,810 --> 00:38:12,390 that I've created across all of my different applications. 766 00:38:12,390 --> 00:38:16,020 But secondly because Django knows that my tests are likely 767 00:38:16,020 --> 00:38:18,630 going to involve manipulations of the database. 768 00:38:18,630 --> 00:38:21,990 They're going to involve me creating objects and testing them. 769 00:38:21,990 --> 00:38:27,670 And it would be probably not a good idea if any time I ran these tests, 770 00:38:27,670 --> 00:38:31,000 it would just actually run these commands on the database. 771 00:38:31,000 --> 00:38:31,500 Why? 772 00:38:31,500 --> 00:38:34,776 773 00:38:34,776 --> 00:38:35,735 AUDIENCE: [INAUDIBLE]. 774 00:38:35,735 --> 00:38:36,360 BRIAN YU: Yeah. 775 00:38:36,360 --> 00:38:38,230 It's going to mess up the data in a number of different ways. 776 00:38:38,230 --> 00:38:40,688 And one, it's going to create these fake cities that aren't 777 00:38:40,688 --> 00:38:42,240 actual airports inside my database. 778 00:38:42,240 --> 00:38:45,600 But secondly if I ran the test once and then ran the test again, for instance, 779 00:38:45,600 --> 00:38:47,850 it would run through this whole set up function again, 780 00:38:47,850 --> 00:38:50,550 it would create a second city with code AAA and BBB. 781 00:38:50,550 --> 00:38:54,180 And that's going to really mess up the data that I have in my database. 782 00:38:54,180 --> 00:38:57,480 And so Django is smart enough to know that when we're running tests, go ahead 783 00:38:57,480 --> 00:38:59,220 and don't use the original database. 784 00:38:59,220 --> 00:39:01,080 Create a separate test database that we're 785 00:39:01,080 --> 00:39:03,990 going to use purely for the function of running these tests. 786 00:39:03,990 --> 00:39:06,540 And it's going to be blank and it will start from scratch. 787 00:39:06,540 --> 00:39:08,970 And that will allow us to just create these objects 788 00:39:08,970 --> 00:39:11,649 without needing to worry about what other data might already 789 00:39:11,649 --> 00:39:14,190 be in the database, without needing to worry about messing up 790 00:39:14,190 --> 00:39:15,480 the data that's already there. 791 00:39:15,480 --> 00:39:17,730 And just allow us to really test the things 792 00:39:17,730 --> 00:39:22,660 that we're interested in testing in a clean and isolated environment. 793 00:39:22,660 --> 00:39:25,260 So how do we actually run those tests? 794 00:39:25,260 --> 00:39:32,250 I'll go back here and I'll run Python manage.py and test. 795 00:39:32,250 --> 00:39:34,170 And what you'll see here is that it's first 796 00:39:34,170 --> 00:39:35,712 going to create that test database. 797 00:39:35,712 --> 00:39:37,170 It's creating a brand new database. 798 00:39:37,170 --> 00:39:40,020 It's going to be an empty database where I'll run all of these tests. 799 00:39:40,020 --> 00:39:43,019 And then you'll see a very similar interface to what we saw on unittest. 800 00:39:43,019 --> 00:39:46,950 It's really just running unittest on these tests because it gives me 801 00:39:46,950 --> 00:39:49,980 these five dots indicating five tests all successful. 802 00:39:49,980 --> 00:39:52,650 It ran those tests in 0.013 seconds. 803 00:39:52,650 --> 00:39:53,670 Now it's OK. 804 00:39:53,670 --> 00:39:56,940 Then it gets rid of the test database because presumably I no longer need it. 805 00:39:56,940 --> 00:39:58,480 And now we're all set. 806 00:39:58,480 --> 00:40:01,080 And so using that, I was able to very quickly, 807 00:40:01,080 --> 00:40:04,500 by running Python manage.py test, run all of the tests 808 00:40:04,500 --> 00:40:09,000 that I had previously created inside of this web application. 809 00:40:09,000 --> 00:40:11,000 Questions about anything so far? 810 00:40:11,000 --> 00:40:11,623 Yeah. 811 00:40:11,623 --> 00:40:13,072 AUDIENCE: [INAUDIBLE]. 812 00:40:13,072 --> 00:40:23,751 813 00:40:23,751 --> 00:40:25,000 BRIAN YU: Good question, yeah. 814 00:40:25,000 --> 00:40:29,620 So we want this setup to only happen once before each test. 815 00:40:29,620 --> 00:40:34,000 Each test, though, is operating in isolation to each other test such 816 00:40:34,000 --> 00:40:38,230 that we never want it to be the case generally that what 817 00:40:38,230 --> 00:40:41,860 happens while running one test might influence the running of a second test. 818 00:40:41,860 --> 00:40:45,790 Because generally speaking, good tests are written independently, 819 00:40:45,790 --> 00:40:48,240 where one test is not going to influence something else. 820 00:40:48,240 --> 00:40:50,073 And if something goes wrong in one test it's 821 00:40:50,073 --> 00:40:51,610 not going to affect the second test. 822 00:40:51,610 --> 00:40:54,400 And so these are all operating independently. 823 00:40:54,400 --> 00:40:56,830 And they all first set themselves up and then 824 00:40:56,830 --> 00:41:01,070 run the test without ever interacting with each other. 825 00:41:01,070 --> 00:41:04,480 And so this set up code will happen one time 826 00:41:04,480 --> 00:41:06,190 for each one of these individual tests. 827 00:41:06,190 --> 00:41:09,370 But the setup code for one test is not going to influence the setting up 828 00:41:09,370 --> 00:41:12,800 of a second test, for instance. 829 00:41:12,800 --> 00:41:19,760 So what we've done now so far is create tests that are testing the database, 830 00:41:19,760 --> 00:41:20,260 really. 831 00:41:20,260 --> 00:41:25,540 We're testing the back end side of our web application involving the models 832 00:41:25,540 --> 00:41:28,630 that we have, the functions associated with those models, 833 00:41:28,630 --> 00:41:32,740 and making sure that all of that is working correctly. 834 00:41:32,740 --> 00:41:34,450 But that's not the only thing that we can 835 00:41:34,450 --> 00:41:38,200 test because if we think about the relationships between different things 836 00:41:38,200 --> 00:41:41,620 on our web application, if we think about it in Django's model view 837 00:41:41,620 --> 00:41:44,980 template format where we have a model that has all the data, 838 00:41:44,980 --> 00:41:48,220 our views.py file takes that information from the model and figures 839 00:41:48,220 --> 00:41:51,100 out what to put into the template, and then the template displays 840 00:41:51,100 --> 00:41:53,170 all the information that the user cares about. 841 00:41:53,170 --> 00:41:55,420 Well, we've tested the model side of things. 842 00:41:55,420 --> 00:41:58,520 But we really haven't yet tested the template side of things. 843 00:41:58,520 --> 00:42:01,990 And so what we'd like to do now is test the template side of things. 844 00:42:01,990 --> 00:42:04,840 Test not just the back end, but make sure that whatever information 845 00:42:04,840 --> 00:42:08,660 we're passing into our template, that we're able to test that, too. 846 00:42:08,660 --> 00:42:14,370 So let's take a look at that by looking at airline2. 847 00:42:14,370 --> 00:42:18,880 848 00:42:18,880 --> 00:42:21,080 So we'll take a look at test.py. 849 00:42:21,080 --> 00:42:24,380 And in this case, notice we're doing a couple of things. 850 00:42:24,380 --> 00:42:27,980 In addition to importing TestCase, we're also importing Client. 851 00:42:27,980 --> 00:42:31,460 And what Client is going to do is simulate a web client, 852 00:42:31,460 --> 00:42:35,600 a client that is able to make requests and get responses back from the server 853 00:42:35,600 --> 00:42:38,450 such that we can simulate web requests to different pages 854 00:42:38,450 --> 00:42:42,990 and make sure that the information we get back is what we expect it to be. 855 00:42:42,990 --> 00:42:44,990 And so these first couple of tests are the same. 856 00:42:44,990 --> 00:42:48,860 But let's now take a look at test_index. 857 00:42:48,860 --> 00:42:51,600 And so what did the index function do, first of all? 858 00:42:51,600 --> 00:42:53,000 Let's make sure we remember. 859 00:42:53,000 --> 00:42:56,870 What our index function did, when we first go to the default flights page, 860 00:42:56,870 --> 00:43:00,290 is that it sets up some context information 861 00:43:00,290 --> 00:43:05,270 where we set flights equal to query for all of the flights. 862 00:43:05,270 --> 00:43:07,040 And then we take that flights information 863 00:43:07,040 --> 00:43:10,640 and we pass it into the index.html page. 864 00:43:10,640 --> 00:43:12,710 That way the index page, very simply, would just 865 00:43:12,710 --> 00:43:16,340 show a listing of all of the flights because of the context that we pass in, 866 00:43:16,340 --> 00:43:18,900 this context variable here, includes within it 867 00:43:18,900 --> 00:43:21,440 this flights key that's associated with a value that 868 00:43:21,440 --> 00:43:26,282 represents all of the flights that we have inside of that context. 869 00:43:26,282 --> 00:43:28,740 So how do I test to make sure the index function's working? 870 00:43:28,740 --> 00:43:31,280 That if I had two flights in my database, 871 00:43:31,280 --> 00:43:35,600 that when I pass it into the template I'm passing in both of those flights? 872 00:43:35,600 --> 00:43:39,060 Well, here's the test_index function, which is just one of these tests 873 00:43:39,060 --> 00:43:41,810 where the first thing I'm going to do is create a new web client-- 874 00:43:41,810 --> 00:43:43,070 I'm just going to call it C-- 875 00:43:43,070 --> 00:43:45,380 which is going to be able to make requests. 876 00:43:45,380 --> 00:43:49,580 And now I'm going to do C.get, send a get request to the slash route, 877 00:43:49,580 --> 00:43:52,790 that default route that should just load the default index page, 878 00:43:52,790 --> 00:43:55,170 and get back the response. 879 00:43:55,170 --> 00:43:58,370 And so a couple of things I want to do about that response. 880 00:43:58,370 --> 00:44:01,670 First thing I want to do is make sure that response was successful. 881 00:44:01,670 --> 00:44:05,030 Recall that when I make an HTTP request I get some sort of exit code 882 00:44:05,030 --> 00:44:07,760 where generally 200 means everything was OK. 883 00:44:07,760 --> 00:44:10,820 I want to first of all assert these two things are equal. 884 00:44:10,820 --> 00:44:13,560 I want to make sure that response.status_code, 885 00:44:13,560 --> 00:44:17,720 in other words the status code the came back from that web HTTP request, 886 00:44:17,720 --> 00:44:19,100 is equal to 200. 887 00:44:19,100 --> 00:44:20,670 That those two should be the same. 888 00:44:20,670 --> 00:44:23,930 And if, for some reason I try to access the index page and there was an error 889 00:44:23,930 --> 00:44:28,240 and I didn't get a 200 response, well then this test should fail. 890 00:44:28,240 --> 00:44:30,020 And the second thing that I want to check 891 00:44:30,020 --> 00:44:33,050 is I want to check the context of the response. 892 00:44:33,050 --> 00:44:36,920 Remember the context was that object that I passed into the template. 893 00:44:36,920 --> 00:44:40,790 And what Django allows us to do in order to make it easier to test our templates 894 00:44:40,790 --> 00:44:45,170 is it gives us direct access to the context when 895 00:44:45,170 --> 00:44:46,950 we're testing the response. 896 00:44:46,950 --> 00:44:50,900 So if we want to make sure that inside the context that came back 897 00:44:50,900 --> 00:44:55,010 there were two flights, because up above I created two flights, 898 00:44:55,010 --> 00:44:59,450 then what I'll do is say, check the response context, 899 00:44:59,450 --> 00:45:03,230 get at the flight's key, count them up and make sure 900 00:45:03,230 --> 00:45:05,580 that there are two of them. 901 00:45:05,580 --> 00:45:08,060 And so the reason this works is that if we compare it 902 00:45:08,060 --> 00:45:15,200 to the views.py over here, in views.py I define this context Python dictionary. 903 00:45:15,200 --> 00:45:20,180 And I pass that context dictionary into index.html. 904 00:45:20,180 --> 00:45:23,720 Then when I run the test, I'm able to actually access that context 905 00:45:23,720 --> 00:45:26,780 dictionary, get at the flights information in there, which is just 906 00:45:26,780 --> 00:45:29,150 going to be one of those Django query sets, 907 00:45:29,150 --> 00:45:31,970 count up the number of responses that came back from that, 908 00:45:31,970 --> 00:45:34,340 and make sure that that number is equal to two 909 00:45:34,340 --> 00:45:37,430 to verify that the index function is in fact working the way 910 00:45:37,430 --> 00:45:38,300 that I expect it to. 911 00:45:38,300 --> 00:45:41,490 912 00:45:41,490 --> 00:45:43,320 So other things I can do here. 913 00:45:43,320 --> 00:45:46,760 I can test to make sure that if I go to a valid flight, 914 00:45:46,760 --> 00:45:49,880 then the flight page is going to work for me. 915 00:45:49,880 --> 00:45:51,920 That if I try and access a flight it will work. 916 00:45:51,920 --> 00:45:55,220 And if you recall, what does my flight function do in this views file? 917 00:45:55,220 --> 00:45:58,250 I try to get the flight in question. 918 00:45:58,250 --> 00:46:02,120 If it doesn't exist I raise a 404 error saying there was some sort of problem. 919 00:46:02,120 --> 00:46:03,660 This flight number doesn't exist. 920 00:46:03,660 --> 00:46:07,057 If I went to my URL/28 but there is no flight 28, 921 00:46:07,057 --> 00:46:09,890 I'm going to get a 404 error saying, sorry the flight doesn't exist, 922 00:46:09,890 --> 00:46:11,270 or at least I should. 923 00:46:11,270 --> 00:46:14,870 And then I'll render this context, rendering information 924 00:46:14,870 --> 00:46:18,029 about the flight, passengers on the flight, passengers that are not 925 00:46:18,029 --> 00:46:20,570 on the flight in case I want to register them for the flight, 926 00:46:20,570 --> 00:46:23,130 so on and so forth. 927 00:46:23,130 --> 00:46:27,290 So if I'm now testing to make sure that I can get a valid flight, 928 00:46:27,290 --> 00:46:31,640 well let me first grab a flight that goes from A1 to A1. 929 00:46:31,640 --> 00:46:33,650 So this is actually not a valid flight but I 930 00:46:33,650 --> 00:46:36,950 want to test to make sure that this flight page is going to work. 931 00:46:36,950 --> 00:46:40,520 Then what I'll do is get a web client, try 932 00:46:40,520 --> 00:46:45,890 to go to slash and then whatever this flight ID is, going to F.id, 933 00:46:45,890 --> 00:46:47,720 saving that response. 934 00:46:47,720 --> 00:46:51,890 And then, down here, asserting that the status code is equal to 200. 935 00:46:51,890 --> 00:46:55,040 In other words, if I go to slash some ID, where 936 00:46:55,040 --> 00:46:59,340 that ID is the ID of an actual flight, then this page should work. 937 00:46:59,340 --> 00:47:02,660 I should get a 200 status code as a response from it. 938 00:47:02,660 --> 00:47:05,750 On the other hand, what happens if I try and access 939 00:47:05,750 --> 00:47:07,850 a flight ID that doesn't exist? 940 00:47:07,850 --> 00:47:13,270 So here on line 60, I have an interesting query 941 00:47:13,270 --> 00:47:15,386 which is a slightly more complex Django query 942 00:47:15,386 --> 00:47:17,260 but I'll try to explain what's going on here. 943 00:47:17,260 --> 00:47:20,600 I want to get the maximum value of any ID of any flight. 944 00:47:20,600 --> 00:47:22,600 Because what I want to test in this function is, 945 00:47:22,600 --> 00:47:26,320 I want to test what happens if I go to an ID number that doesn't exist. 946 00:47:26,320 --> 00:47:31,510 If the IDs only go up to ID three or four, what happens if I go to /5? 947 00:47:31,510 --> 00:47:33,430 So the first thing I need to know is, what 948 00:47:33,430 --> 00:47:38,090 is the maximum ID of any of the flights in my database right now? 949 00:47:38,090 --> 00:47:41,410 And so to do that I'm going to go to flight.objects.all, query 950 00:47:41,410 --> 00:47:43,150 for all the possible flights. 951 00:47:43,150 --> 00:47:46,600 And Aggregate is a special Django function 952 00:47:46,600 --> 00:47:48,490 that helps to, as the name might suggest, 953 00:47:48,490 --> 00:47:51,010 aggregate data by some particular attribute. 954 00:47:51,010 --> 00:47:54,800 And in this case I'm aggregating it based on the maximum ID value. 955 00:47:54,800 --> 00:47:59,260 So I'm going to get the maximum ID value of any of the individual flights. 956 00:47:59,260 --> 00:48:01,930 And then when I actually do this test I'm 957 00:48:01,930 --> 00:48:06,010 going to get slash whatever the maximum ID is plus one. 958 00:48:06,010 --> 00:48:08,470 So I know that whatever route I'm trying to access now, 959 00:48:08,470 --> 00:48:11,920 it's not going to be a route that exists because of the maximum ID was flight 960 00:48:11,920 --> 00:48:16,020 number five, then when I access /6, as I'm doing here, 961 00:48:16,020 --> 00:48:20,350 then there is no way that this should be a valid flight which means that 962 00:48:20,350 --> 00:48:24,250 the status code that I get back as a response should be status code 404. 963 00:48:24,250 --> 00:48:27,920 I should get that 404 error if I go to a flight that doesn't exist. 964 00:48:27,920 --> 00:48:31,311 And so this function tests to make sure that is going to work. 965 00:48:31,311 --> 00:48:34,310 And then you can see I also I have a couple of other tests here as well. 966 00:48:34,310 --> 00:48:36,580 So here I'm testing to make sure that the passenger 967 00:48:36,580 --> 00:48:38,330 page is going to work correctly. 968 00:48:38,330 --> 00:48:40,540 So I first start by adding a passenger. 969 00:48:40,540 --> 00:48:45,360 And then I check to make sure that when I go to slash this particular flight, 970 00:48:45,360 --> 00:48:47,470 that if I count up the number of passengers 971 00:48:47,470 --> 00:48:50,620 that I have that one passenger, for instance, on that flight. 972 00:48:50,620 --> 00:48:55,330 And I also do the same sort of test to make sure that non-passengers work. 973 00:48:55,330 --> 00:49:00,100 So if I have a new passenger but I don't register that passenger for a given 974 00:49:00,100 --> 00:49:06,070 flight, then when I try to check the number of non-passengers 975 00:49:06,070 --> 00:49:08,470 on this flight, that should be one. 976 00:49:08,470 --> 00:49:12,550 And so what I'm doing here is by accessing the response context 977 00:49:12,550 --> 00:49:15,430 and getting at particular aspects of the response context, 978 00:49:15,430 --> 00:49:17,800 I can effectively test what information is 979 00:49:17,800 --> 00:49:20,470 getting passed to my template, test what information actually 980 00:49:20,470 --> 00:49:23,890 gets displayed to the user when they go to a particular page. 981 00:49:23,890 --> 00:49:27,610 Which means I can now automatically run these sorts of tests 982 00:49:27,610 --> 00:49:31,240 to make sure that my page is going to behave the way that it should behave 983 00:49:31,240 --> 00:49:34,810 such that later on down the line if I'm making changes to the flight's page 984 00:49:34,810 --> 00:49:37,960 and I change different things about what information is in the context. 985 00:49:37,960 --> 00:49:40,990 And I want to make sure that this original functionality of displaying 986 00:49:40,990 --> 00:49:43,660 the passengers, displaying the people that are not passengers, 987 00:49:43,660 --> 00:49:44,800 is still working. 988 00:49:44,800 --> 00:49:48,490 I can just use tests like this in order to make that happen. 989 00:49:48,490 --> 00:49:55,210 Such that now if I go back and run Python manage.py test, 990 00:49:55,210 --> 00:49:57,270 now I just ran all 10 of those tests. 991 00:49:57,270 --> 00:49:58,460 Everything was OK. 992 00:49:58,460 --> 00:50:02,680 And so now I can feel pretty confident that that is, in fact, working. 993 00:50:02,680 --> 00:50:05,240 If I mess something up, though, for instance, 994 00:50:05,240 --> 00:50:10,780 if in here rather than passengers being all the passengers and non-passengers 995 00:50:10,780 --> 00:50:15,070 being only the passengers that are not on this flight excluding the people 996 00:50:15,070 --> 00:50:16,210 that are on this flight. 997 00:50:16,210 --> 00:50:18,580 If I accidentally switch them up, for instance, 998 00:50:18,580 --> 00:50:23,290 and this was my non-passengers and this was my passengers, for instance. 999 00:50:23,290 --> 00:50:26,190 And now I try to run that. 1000 00:50:26,190 --> 00:50:28,840 Well now I'm going to get the fact that I got two failed tests. 1001 00:50:28,840 --> 00:50:31,600 I missed a couple of assertions. 1002 00:50:31,600 --> 00:50:33,580 And here were the tests that I failed. 1003 00:50:33,580 --> 00:50:37,020 I failed the test flight page in non-passengers. 1004 00:50:37,020 --> 00:50:39,872 And I also failed the test flight page passenger's test. 1005 00:50:39,872 --> 00:50:41,830 And then I can go back and figure out why is it 1006 00:50:41,830 --> 00:50:43,490 that exactly I failed those tests? 1007 00:50:43,490 --> 00:50:45,970 I can fix those individual errors. 1008 00:50:45,970 --> 00:50:47,170 I can go back. 1009 00:50:47,170 --> 00:50:48,550 I can run the test again. 1010 00:50:48,550 --> 00:50:51,550 And now I can see that I passed those individual tests. 1011 00:50:51,550 --> 00:50:54,130 And so writing tests in advance and then running those 1012 00:50:54,130 --> 00:50:56,110 tests when you make changes is often a good way 1013 00:50:56,110 --> 00:50:58,470 to verify that your code is, in fact, working. 1014 00:50:58,470 --> 00:51:00,730 That you haven't made a change to one part of the code 1015 00:51:00,730 --> 00:51:03,372 and therefore messed up some other part of your application. 1016 00:51:03,372 --> 00:51:06,580 And that can be good for making sure the code is well integrated, that you're 1017 00:51:06,580 --> 00:51:09,580 avoiding bugs wherever possible. 1018 00:51:09,580 --> 00:51:11,020 Questions about things so far? 1019 00:51:11,020 --> 00:51:14,010 1020 00:51:14,010 --> 00:51:18,980 OK, so thus far we've seen a bunch of different ways to run tests. 1021 00:51:18,980 --> 00:51:21,440 We saw just running sample assertions. 1022 00:51:21,440 --> 00:51:23,000 We took a look at unittest. 1023 00:51:23,000 --> 00:51:24,949 And unittest has a bunch of other functions 1024 00:51:24,949 --> 00:51:27,990 that we can use in order to assert the things that are true are not true. 1025 00:51:27,990 --> 00:51:31,670 We saw assert true, assert false, we saw assert equal. 1026 00:51:31,670 --> 00:51:34,670 There's also assert not equal if two things are not the same, 1027 00:51:34,670 --> 00:51:37,250 assert that some item is in a list, for instance, or not 1028 00:51:37,250 --> 00:51:40,310 in a particular list, and a whole bunch of others that you can explore. 1029 00:51:40,310 --> 00:51:42,591 And so unittest is useful for quickly running 1030 00:51:42,591 --> 00:51:44,840 a whole bunch of functions that are each going to test 1031 00:51:44,840 --> 00:51:47,469 a particular aspect of our program. 1032 00:51:47,469 --> 00:51:49,760 And then we saw how Django was able to expand and build 1033 00:51:49,760 --> 00:51:55,100 upon those basic functions in order to allow us to test individual routes, 1034 00:51:55,100 --> 00:51:58,400 or test things about our database, or test the templates that 1035 00:51:58,400 --> 00:52:01,550 come back and looking at the context of the information in those templates 1036 00:52:01,550 --> 00:52:03,650 to make sure that those work too. 1037 00:52:03,650 --> 00:52:06,920 And I'll just show you one other example of testing in order 1038 00:52:06,920 --> 00:52:10,490 to give you a sense for the different types of testing that's available. 1039 00:52:10,490 --> 00:52:14,270 What about something like a JavaScript application? 1040 00:52:14,270 --> 00:52:19,324 So if I had a JavaScript application like counter.html, 1041 00:52:19,324 --> 00:52:21,740 which is a simple JavaScript application similar to things 1042 00:52:21,740 --> 00:52:24,780 we've seen before that just has a number and I 1043 00:52:24,780 --> 00:52:28,400 have an Add button that increases the number and a Minus button that 1044 00:52:28,400 --> 00:52:31,067 decreases the number where I can just press the plus button over 1045 00:52:31,067 --> 00:52:33,649 and over and over and the number goes up and up and up and up, 1046 00:52:33,649 --> 00:52:35,690 and then the minus button decreases that number. 1047 00:52:35,690 --> 00:52:37,940 How do I test to make sure that's accurate? 1048 00:52:37,940 --> 00:52:40,730 I can't just use like the Django framework 1049 00:52:40,730 --> 00:52:45,192 that I was using before, because it's not just a matter of request this page 1050 00:52:45,192 --> 00:52:46,400 and see what the response is. 1051 00:52:46,400 --> 00:52:48,483 Because the response will just be the number zero. 1052 00:52:48,483 --> 00:52:50,942 And I can't use that to say, test the plus and minus button 1053 00:52:50,942 --> 00:52:53,066 because that's not something about Django's server. 1054 00:52:53,066 --> 00:52:54,810 It's about something in the web browser. 1055 00:52:54,810 --> 00:52:57,810 It's about the JavaScript code that's really running in the web browser. 1056 00:52:57,810 --> 00:52:59,990 So how do I test that? 1057 00:52:59,990 --> 00:53:05,090 So there's an entire set of testing tools known as browser testing 1058 00:53:05,090 --> 00:53:09,140 tools that are designed for really testing things happening inside 1059 00:53:09,140 --> 00:53:10,370 of the web browser. 1060 00:53:10,370 --> 00:53:13,640 And one example of that is Selenium, which is the one that I'm 1061 00:53:13,640 --> 00:53:14,870 going to show you know. 1062 00:53:14,870 --> 00:53:18,830 And what Selenium does is it uses a web driver to allow 1063 00:53:18,830 --> 00:53:22,850 us to use Python code to effectively control 1064 00:53:22,850 --> 00:53:24,830 what's going on in a web browser, to pretend 1065 00:53:24,830 --> 00:53:27,800 to be the user programmatically, in order to manipulate 1066 00:53:27,800 --> 00:53:29,450 different things about the page. 1067 00:53:29,450 --> 00:53:31,520 And so how might that work? 1068 00:53:31,520 --> 00:53:39,400 Well, I'm going to go ahead and from Selenium import web driver. 1069 00:53:39,400 --> 00:53:45,220 And I'm also going to import from this test file a function called file URI. 1070 00:53:45,220 --> 00:53:50,290 What file URI is going to do is it just takes an HTML file 1071 00:53:50,290 --> 00:53:53,260 and turns it into the URL that I would actually 1072 00:53:53,260 --> 00:53:56,470 want to use if I were to open something like this up. 1073 00:53:56,470 --> 00:54:00,470 So for instance, if I take this URL and open it in Chrome, I get to the page. 1074 00:54:00,470 --> 00:54:02,980 This is just a file URL that now takes me 1075 00:54:02,980 --> 00:54:05,449 directly to the page that is located on my system. 1076 00:54:05,449 --> 00:54:07,490 And so that's all the file URI function is doing. 1077 00:54:07,490 --> 00:54:14,190 But I'll go ahead and save that inside of a variable called URI. 1078 00:54:14,190 --> 00:54:20,030 And now what I'm going to do is create a web driver by going webdriver.chrome. 1079 00:54:20,030 --> 00:54:24,320 So Selenium has a whole bunch of built in web drivers for different browsers. 1080 00:54:24,320 --> 00:54:26,820 Chrome just happens to be the one that I'm going to use now. 1081 00:54:26,820 --> 00:54:29,190 And what effectively this is going to allow me to do 1082 00:54:29,190 --> 00:54:36,097 is control Google Chrome through the usage of Selenium's Python API. 1083 00:54:36,097 --> 00:54:39,180 And so what I see is this Google Chrome window that now pops up that says, 1084 00:54:39,180 --> 00:54:42,060 Chrome is being controlled by automated test software. 1085 00:54:42,060 --> 00:54:45,390 I've installed something called Chrome driver inside of Chrome that 1086 00:54:45,390 --> 00:54:47,970 lets me now run these automated tests. 1087 00:54:47,970 --> 00:54:51,660 And so now if I say, well, URI is the file that I want to open. 1088 00:54:51,660 --> 00:54:57,720 So if I say driver.get URI, well that's going to, in here, 1089 00:54:57,720 --> 00:55:02,070 open up the page that I wanted it to open up. 1090 00:55:02,070 --> 00:55:04,590 And so now if I want a test, like I want to test 1091 00:55:04,590 --> 00:55:08,230 these individual buttons, the plus and minus buttons, how do I do that? 1092 00:55:08,230 --> 00:55:13,470 Well inside of counter.html, if I took a closer look at it, and I-- 1093 00:55:13,470 --> 00:55:14,670 let's go ahead and open up. 1094 00:55:14,670 --> 00:55:18,900 1095 00:55:18,900 --> 00:55:21,850 Let's look at counter.HTML. 1096 00:55:21,850 --> 00:55:24,580 Inside the HTML I have these two buttons, Plus and Minus. 1097 00:55:24,580 --> 00:55:27,370 The Plus button has an ID of increase. 1098 00:55:27,370 --> 00:55:29,410 And so if I want to get at the Plus button, 1099 00:55:29,410 --> 00:55:33,172 well that's as simple as me now just saying, driver-- 1100 00:55:33,172 --> 00:55:36,730 or lets me call the variable plus. 1101 00:55:36,730 --> 00:55:44,800 I'll say plus equals driver.find_element_by_ID increase. 1102 00:55:44,800 --> 00:55:48,980 So I want to get the thing that has ID called increase and save that as Plus. 1103 00:55:48,980 --> 00:55:52,360 And so now I can say, plus.click, for example. 1104 00:55:52,360 --> 00:55:55,490 I've extracted the element that has ID increase. 1105 00:55:55,490 --> 00:55:58,790 And now I can programmatically, in Python, using Selenium, 1106 00:55:58,790 --> 00:56:01,780 say plus.click to click on the plus button. 1107 00:56:01,780 --> 00:56:04,684 And ideally that should have had the result 1108 00:56:04,684 --> 00:56:06,100 of causing the number to increase. 1109 00:56:06,100 --> 00:56:08,470 It looks like that didn't quite work in this case. 1110 00:56:08,470 --> 00:56:13,640 What is-- plus is this element. 1111 00:56:13,640 --> 00:56:15,160 And I wanted to say plus.click. 1112 00:56:15,160 --> 00:56:19,200 1113 00:56:19,200 --> 00:56:20,010 No elements CLS. 1114 00:56:20,010 --> 00:56:23,004 1115 00:56:23,004 --> 00:56:25,670 So when I-- quick question, how does it know that it's a button? 1116 00:56:25,670 --> 00:56:30,549 When I'm extracting a web element, it knows information about the web element 1117 00:56:30,549 --> 00:56:31,840 that I'm attempting to extract. 1118 00:56:31,840 --> 00:56:34,940 So in this case it does know the fact that it is a button. 1119 00:56:34,940 --> 00:56:36,620 Let me give that another try. 1120 00:56:36,620 --> 00:56:40,880 So we'll go from Selenium, import webdriver, 1121 00:56:40,880 --> 00:56:47,580 and we'll say from tests import file_uri. 1122 00:56:47,580 --> 00:56:51,810 So URI equals file_uri of counter.html. 1123 00:56:51,810 --> 00:56:56,050 And now driver equals webdriver.chrome. 1124 00:56:56,050 --> 00:56:59,100 And now I have this Chrome web driver. 1125 00:56:59,100 --> 00:57:02,630 And I wanted to say driver.get this particular URI. 1126 00:57:02,630 --> 00:57:10,140 And now plus equals driver.find_element_by_id increase. 1127 00:57:10,140 --> 00:57:12,062 And now plus.click. 1128 00:57:12,062 --> 00:57:15,270 All right, not sure what happened last time but this seems to be working now. 1129 00:57:15,270 --> 00:57:19,890 So now I found the element that has ID increase, saved it 1130 00:57:19,890 --> 00:57:21,325 inside of a variable called plus. 1131 00:57:21,325 --> 00:57:23,200 And now I can actually manipulate that button 1132 00:57:23,200 --> 00:57:25,290 by doing something like plus.click, for instance. 1133 00:57:25,290 --> 00:57:26,940 And I do it again. 1134 00:57:26,940 --> 00:57:30,352 And that-- didn't seem to have the same effect. 1135 00:57:30,352 --> 00:57:33,560 So maybe there's some sort of bug in the web application that I can look for. 1136 00:57:33,560 --> 00:57:36,930 But the idea here is that we can begin to take advantage of the interface 1137 00:57:36,930 --> 00:57:40,690 that Selenium gives us in order to manipulate the page 1138 00:57:40,690 --> 00:57:43,600 and make sure that it's working the way that we want it to. 1139 00:57:43,600 --> 00:57:50,600 And so let me show you an example of those tests in action. 1140 00:57:50,600 --> 00:57:55,900 And so one thing I might want to do is test different aspects about the page. 1141 00:57:55,900 --> 00:57:57,970 So I might write a whole bunch of unit tests 1142 00:57:57,970 --> 00:58:01,120 where I first try to get the counter.html file 1143 00:58:01,120 --> 00:58:04,870 and then assert that the title of the page that I got is equal to counter, 1144 00:58:04,870 --> 00:58:06,460 make sure that the title is working. 1145 00:58:06,460 --> 00:58:09,130 And likewise I might test the increase button 1146 00:58:09,130 --> 00:58:14,080 by first getting the counter.html file, finding the increase button, 1147 00:58:14,080 --> 00:58:16,510 clicking on the increase button, and then making sure 1148 00:58:16,510 --> 00:58:20,626 that when I get the H1 tag, that heading at the top and check the text, 1149 00:58:20,626 --> 00:58:22,750 that the text should be one-- that that should have 1150 00:58:22,750 --> 00:58:24,730 the result of updating the counter. 1151 00:58:24,730 --> 00:58:28,251 And likewise, I would want to do the same thing for the decrease button-- 1152 00:58:28,251 --> 00:58:30,250 make sure that when I click the decrease button, 1153 00:58:30,250 --> 00:58:32,380 it has the effect of subtracting one. 1154 00:58:32,380 --> 00:58:35,622 And then down below, I might test that the increase button works 1155 00:58:35,622 --> 00:58:37,330 multiple times, which seems like maybe it 1156 00:58:37,330 --> 00:58:39,560 didn't based on the opening of the file just 1157 00:58:39,560 --> 00:58:44,620 then, such that if I get the increase button and click on it three times, 1158 00:58:44,620 --> 00:58:48,160 then the result of that should be that if I check the contents of that 1159 00:58:48,160 --> 00:58:51,460 heading then that should be three. 1160 00:58:51,460 --> 00:58:54,710 And so what Selenium allows us to do is it 1161 00:58:54,710 --> 00:58:57,010 allows us to do some of this browser type testing 1162 00:58:57,010 --> 00:59:00,160 where I can really begin to test whether or not 1163 00:59:00,160 --> 00:59:02,930 things are working in the web browser in response to things 1164 00:59:02,930 --> 00:59:04,180 that users are actually doing. 1165 00:59:04,180 --> 00:59:06,700 That if they click on something and that has the effect of something 1166 00:59:06,700 --> 00:59:08,680 changing on the page, that's not something 1167 00:59:08,680 --> 00:59:12,550 that's very easily testable by Django's testing software, for instance. 1168 00:59:12,550 --> 00:59:16,930 But it is something that I can do by taking advantage of browser testing. 1169 00:59:16,930 --> 00:59:21,220 The goal of all that was to show you a bunch of different types of tests 1170 00:59:21,220 --> 00:59:23,830 that we can perform, whether it's testing functions 1171 00:59:23,830 --> 00:59:27,130 just by using asserts, using unittest, using Django's framework 1172 00:59:27,130 --> 00:59:29,440 to test databases and templates and contexts, 1173 00:59:29,440 --> 00:59:31,810 or using Selenium to test user interfaces 1174 00:59:31,810 --> 00:59:35,230 and testing the way that users actually interact with the web browser. 1175 00:59:35,230 --> 00:59:38,290 All of that is going to be very helpful as we 1176 00:59:38,290 --> 00:59:41,950 begin to build larger and more sophisticated web applications. 1177 00:59:41,950 --> 00:59:45,430 And what we'll see after the break is how we can actually 1178 00:59:45,430 --> 00:59:48,430 implement some of these tests and use some of these tests 1179 00:59:48,430 --> 00:59:52,000 in our own building and deployment process for web applications 1180 00:59:52,000 --> 00:59:54,190 as we take a look at CI and CD-- 1181 00:59:54,190 --> 00:59:56,770 Continuous Integration and Continuous Delivery. 1182 00:59:56,770 --> 00:59:58,550 We'll take a look at that after the break. 1183 00:59:58,550 --> 00:59:59,970 We'll take a short break now. 1184 00:59:59,970 --> 01:00:01,300 OK, welcome back. 1185 01:00:01,300 --> 01:00:03,670 So where we left off was talking about testing 1186 01:00:03,670 --> 01:00:06,130 and how we can write tests in order to make sure 1187 01:00:06,130 --> 01:00:08,410 that different components of our application work 1188 01:00:08,410 --> 01:00:11,316 and then run those tests in order to make sure that anytime 1189 01:00:11,316 --> 01:00:13,690 we are making a change to one part of our web application 1190 01:00:13,690 --> 01:00:17,170 we're not inadvertently causing some other part of the web application 1191 01:00:17,170 --> 01:00:20,521 to stop working, such that if we're writing tests comprehensively 1192 01:00:20,521 --> 01:00:23,020 and thoroughly across all different parts of our application 1193 01:00:23,020 --> 01:00:26,440 it becomes pretty easy to make sure and verify the different components 1194 01:00:26,440 --> 01:00:28,640 and aspects of our application are working. 1195 01:00:28,640 --> 01:00:32,310 And so this is ultimately just part of good software development workflow. 1196 01:00:32,310 --> 01:00:34,060 And that's what we're going to be spending 1197 01:00:34,060 --> 01:00:37,270 some time with the rest of today talking about, which are just good software 1198 01:00:37,270 --> 01:00:40,150 development design practices and goals that we 1199 01:00:40,150 --> 01:00:44,186 should bear in mind as we go about designing software in the modern era. 1200 01:00:44,186 --> 01:00:46,810 And one thing you'll hear commonly when we deal with this topic 1201 01:00:46,810 --> 01:00:51,620 is CI/CD, which stands for Continuous Integration and Continuous Delivery. 1202 01:00:51,620 --> 01:00:53,300 So what does that actually mean? 1203 01:00:53,300 --> 01:01:00,160 Well, continuous integration has to do with consistently and frequently 1204 01:01:00,160 --> 01:01:02,650 integrating code together between different people 1205 01:01:02,650 --> 01:01:04,720 working on the same project, for instance. 1206 01:01:04,720 --> 01:01:08,260 Where in an old model you might imagine that once upon a time 1207 01:01:08,260 --> 01:01:10,900 there would be one central place where all the main code was. 1208 01:01:10,900 --> 01:01:13,180 And different developers working on the same project 1209 01:01:13,180 --> 01:01:16,630 might be working independently, separately on different features. 1210 01:01:16,630 --> 01:01:18,490 And then only at the end would they then try 1211 01:01:18,490 --> 01:01:22,071 to integrate their code all back together with the main original branch 1212 01:01:22,071 --> 01:01:24,070 in order to make sure that everything's working. 1213 01:01:24,070 --> 01:01:26,920 But that leads to a lot of different integration problems 1214 01:01:26,920 --> 01:01:29,707 where it's not necessarily immediately clear that when 1215 01:01:29,707 --> 01:01:31,540 you try to integrate all the things together 1216 01:01:31,540 --> 01:01:33,380 that it's just suddenly going to work. 1217 01:01:33,380 --> 01:01:35,440 And so what continuous integration is all about 1218 01:01:35,440 --> 01:01:38,830 is frequently merging different changes back 1219 01:01:38,830 --> 01:01:41,924 to some main branch that either you are working on your own project 1220 01:01:41,924 --> 01:01:44,590 or that multiple different people, if you're working in a group, 1221 01:01:44,590 --> 01:01:47,890 are working on together in order to make sure that changes that you make 1222 01:01:47,890 --> 01:01:49,090 are continuously integrated. 1223 01:01:49,090 --> 01:01:53,770 And using software version control tools like Git, this is actually quite easy. 1224 01:01:53,770 --> 01:01:56,380 And a second aspect to continuous integration, 1225 01:01:56,380 --> 01:02:00,220 which is sort of added onto this, is that any time a change is made 1226 01:02:00,220 --> 01:02:03,070 and we want to integrate it in with the rest of the codebase, 1227 01:02:03,070 --> 01:02:06,139 we'd like to verify that that change is actually going to work. 1228 01:02:06,139 --> 01:02:08,180 In other words, it's not going to break anything. 1229 01:02:08,180 --> 01:02:11,320 It's not going to cause any of the existing parts of the codebase to fail. 1230 01:02:11,320 --> 01:02:13,510 Because if you have one software developer who's 1231 01:02:13,510 --> 01:02:16,500 working on feature A in your web application, 1232 01:02:16,500 --> 01:02:19,210 they might not be thinking about features B, C, and D. 1233 01:02:19,210 --> 01:02:22,210 And when they integrate feature A into the branch, for all 1234 01:02:22,210 --> 01:02:25,120 they know it might be causing some problems to happen 1235 01:02:25,120 --> 01:02:27,260 in other parts of the web application. 1236 01:02:27,260 --> 01:02:29,480 So one important part of continuous integration 1237 01:02:29,480 --> 01:02:31,460 is going to be automated unit testing. 1238 01:02:31,460 --> 01:02:34,760 This idea that any time we make some change to the web application 1239 01:02:34,760 --> 01:02:38,300 and want to integrate it with the branch on which we're working on developing 1240 01:02:38,300 --> 01:02:41,270 this application, we automatically run some set of tests 1241 01:02:41,270 --> 01:02:44,420 and then we get some result. And any time I push code to GitHub, 1242 01:02:44,420 --> 01:02:47,450 for instance, I'd like to know whether that code I just pushed 1243 01:02:47,450 --> 01:02:50,014 actually passed the test or not by automatically running 1244 01:02:50,014 --> 01:02:53,180 those tests every time I push code so that I don't have to worry about doing 1245 01:02:53,180 --> 01:02:56,346 it myself and I might forget to do it, for instance, in order to really just 1246 01:02:56,346 --> 01:02:59,050 verify that this is going to work. 1247 01:02:59,050 --> 01:03:02,260 Continuous delivery, on the other hand, take this one step further. 1248 01:03:02,260 --> 01:03:05,530 In addition to just continually integrating code into the code base 1249 01:03:05,530 --> 01:03:08,890 consistently we also want to continuously deliver code 1250 01:03:08,890 --> 01:03:10,540 to our ultimate web application. 1251 01:03:10,540 --> 01:03:12,970 So as opposed to making a whole bunch of changes 1252 01:03:12,970 --> 01:03:15,340 and only after a couple of months of building up changes 1253 01:03:15,340 --> 01:03:18,880 to release of those changes in one go, continuous delivery 1254 01:03:18,880 --> 01:03:22,190 is all about making incremental deliveries to our web application. 1255 01:03:22,190 --> 01:03:25,540 That if we make one small modification and push that modification 1256 01:03:25,540 --> 01:03:28,600 and it passes all of those tests, then let's go ahead 1257 01:03:28,600 --> 01:03:32,470 and make it easy to then deploy that small change to our web applications 1258 01:03:32,470 --> 01:03:34,850 so people can see those changes right away. 1259 01:03:34,850 --> 01:03:38,320 And so these are growing in popularity as practices 1260 01:03:38,320 --> 01:03:40,154 when it comes to software development. 1261 01:03:40,154 --> 01:03:42,070 And we'll be looking at some of the tools that 1262 01:03:42,070 --> 01:03:45,194 help to make this a little bit easier today, which are tools that you might 1263 01:03:45,194 --> 01:03:49,060 see an industry quite frequently working on larger software development 1264 01:03:49,060 --> 01:03:50,350 projects like this. 1265 01:03:50,350 --> 01:03:54,640 And so there's many different CI tools for continuous integration 1266 01:03:54,640 --> 01:03:58,060 who are meant to serve the purpose of helping to quickly integrate code 1267 01:03:58,060 --> 01:03:59,770 and to run tests on code. 1268 01:03:59,770 --> 01:04:01,952 And you can explore a bunch of them. 1269 01:04:01,952 --> 01:04:04,660 They all do very similar things but have their slight differences 1270 01:04:04,660 --> 01:04:05,466 and advantages. 1271 01:04:05,466 --> 01:04:07,590 The one we're going to be focusing on in this class 1272 01:04:07,590 --> 01:04:11,500 is Travis, which just happens to be one of the more popular CI tools. 1273 01:04:11,500 --> 01:04:13,500 And the basic way that Travis is going to work-- 1274 01:04:13,500 --> 01:04:17,050 Travis is the little guy in the lower right of the screen there-- 1275 01:04:17,050 --> 01:04:21,910 is that when I push code to GitHub, when I've made changes to a web application 1276 01:04:21,910 --> 01:04:23,710 and I push that code to GitHub by pushing 1277 01:04:23,710 --> 01:04:28,150 some new change to my repository, what GitHub is going to do 1278 01:04:28,150 --> 01:04:32,237 is GitHub is going to notify Travis, our CI software, that we've 1279 01:04:32,237 --> 01:04:34,570 made some changes to our repository, that there has been 1280 01:04:34,570 --> 01:04:36,972 a new commit, a new push to the repository, 1281 01:04:36,972 --> 01:04:39,430 and to let Travis know that there has been this new change. 1282 01:04:39,430 --> 01:04:41,500 And what Travis is then going to do is it's 1283 01:04:41,500 --> 01:04:44,719 going to pull that code from GitHub and then run some tests on it. 1284 01:04:44,719 --> 01:04:47,510 Travis can do other things as well and we'll touch on that as well. 1285 01:04:47,510 --> 01:04:50,130 But one thing that it's quite good for is for running tests. 1286 01:04:50,130 --> 01:04:53,350 If Travis knows that there's been a change that's been pushed to GitHub, 1287 01:04:53,350 --> 01:04:55,360 and Travis can then say all right, let's pull 1288 01:04:55,360 --> 01:04:58,630 that new version of the code, the latest version of the repository, 1289 01:04:58,630 --> 01:05:01,852 run the tests, make sure that the tests are all passing. 1290 01:05:01,852 --> 01:05:04,060 And either way no matter what the results of the test 1291 01:05:04,060 --> 01:05:07,990 are let GitHub know the results such that on GitHub I can now see, 1292 01:05:07,990 --> 01:05:09,520 did the test pass? 1293 01:05:09,520 --> 01:05:10,690 Did the test fail? 1294 01:05:10,690 --> 01:05:12,880 And that can tell me something about whether or not 1295 01:05:12,880 --> 01:05:15,190 the change that I just made needs to be looked at again 1296 01:05:15,190 --> 01:05:17,660 or needs to be reworked, for instance. 1297 01:05:17,660 --> 01:05:21,190 And so before we actually look at specifics as to how that works, 1298 01:05:21,190 --> 01:05:25,890 questions about the general idea of what we're trying to achieve here? 1299 01:05:25,890 --> 01:05:26,475 Yeah. 1300 01:05:26,475 --> 01:05:31,648 AUDIENCE: [INAUDIBLE] into the branch [INAUDIBLE]?? 1301 01:05:31,648 --> 01:05:35,271 1302 01:05:35,271 --> 01:05:36,270 BRIAN YU: Good question. 1303 01:05:36,270 --> 01:05:39,637 Question is about how you're merging and what branches we're merging into. 1304 01:05:39,637 --> 01:05:41,970 Different web applications will have different practices 1305 01:05:41,970 --> 01:05:43,230 for how this generally works. 1306 01:05:43,230 --> 01:05:47,100 But frequently there will be one branch on which the latest version 1307 01:05:47,100 --> 01:05:49,350 of the project being developed is on. 1308 01:05:49,350 --> 01:05:52,230 And as people work on individual features, 1309 01:05:52,230 --> 01:05:54,420 they'll work on those features individually 1310 01:05:54,420 --> 01:05:56,730 on different branches for each individual feature 1311 01:05:56,730 --> 01:05:58,920 such that if you make a new change to a feature that 1312 01:05:58,920 --> 01:06:00,507 gets pushed on to that feature branch. 1313 01:06:00,507 --> 01:06:02,340 And only once you're done with that feature, 1314 01:06:02,340 --> 01:06:05,160 or at a place where it can be merged in with the rest of the code 1315 01:06:05,160 --> 01:06:07,230 and it's working, then you'll want to merge that 1316 01:06:07,230 --> 01:06:09,400 back in with the rest of the code. 1317 01:06:09,400 --> 01:06:12,240 But what Travis can do is it can run these tests no matter 1318 01:06:12,240 --> 01:06:13,270 what branch you're on. 1319 01:06:13,270 --> 01:06:16,470 So even if you're not on the main branch, it can test your code, 1320 01:06:16,470 --> 01:06:19,260 make sure that it's going to work such that you can be confident 1321 01:06:19,260 --> 01:06:23,160 before you merge back in to the main branch on which all of the code is. 1322 01:06:23,160 --> 01:06:25,260 Because you want to know that when you merge it 1323 01:06:25,260 --> 01:06:29,580 in that those tests are going to pass, that you're not breaking anything 1324 01:06:29,580 --> 01:06:32,850 when you go about making that change. 1325 01:06:32,850 --> 01:06:35,170 OK, so how does Travis work? 1326 01:06:35,170 --> 01:06:38,100 How are we going to be able to take advantage and use this? 1327 01:06:38,100 --> 01:06:40,650 Well we're going to need some sort of configuration file 1328 01:06:40,650 --> 01:06:45,420 to tell Travis what to perform, what tests to run, 1329 01:06:45,420 --> 01:06:48,690 how to install all the things that need to be installed, for instance. 1330 01:06:48,690 --> 01:06:51,570 And to do that we're going to use a file format called YAML. 1331 01:06:51,570 --> 01:06:54,390 YAML is just a common file format that's used 1332 01:06:54,390 --> 01:06:56,550 for creating configuration files usually-- 1333 01:06:56,550 --> 01:07:00,160 files that are configuring some service to behave in a particular way. 1334 01:07:00,160 --> 01:07:01,380 We'll see it for Travis now. 1335 01:07:01,380 --> 01:07:04,110 We'll see it again later on today in a different context. 1336 01:07:04,110 --> 01:07:07,050 But the general idea for YAML is that a file in YAML 1337 01:07:07,050 --> 01:07:09,030 is going to look something like this. 1338 01:07:09,030 --> 01:07:12,540 It's going to be a set of keys and values, just like in an JSON object, 1339 01:07:12,540 --> 01:07:14,100 for instance, which we saw before. 1340 01:07:14,100 --> 01:07:17,100 There were keys and values where we had key colon value. 1341 01:07:17,100 --> 01:07:18,270 Same general idea here-- 1342 01:07:18,270 --> 01:07:19,870 key colon value. 1343 01:07:19,870 --> 01:07:21,690 And if we had a list of items, for instance 1344 01:07:21,690 --> 01:07:24,510 we had one key associated with multiple values, 1345 01:07:24,510 --> 01:07:27,390 it would look something like this, where a list is indented and has 1346 01:07:27,390 --> 01:07:28,720 a hyphen in front of it. 1347 01:07:28,720 --> 01:07:32,070 And so that would be what a YAML file would look like. 1348 01:07:32,070 --> 01:07:34,860 And what a Travis YAML file is going to look like, 1349 01:07:34,860 --> 01:07:37,740 configuration for Travis, our CI service, 1350 01:07:37,740 --> 01:07:39,550 is it's going to look something like this. 1351 01:07:39,550 --> 01:07:41,850 This is going to be the configuration file telling 1352 01:07:41,850 --> 01:07:44,940 Travis what to do in order to test our code to make sure 1353 01:07:44,940 --> 01:07:46,230 that it's working properly. 1354 01:07:46,230 --> 01:07:49,560 So we'll first specify what language we should be using, in this case Python 1355 01:07:49,560 --> 01:07:51,570 because our application is written in Python. 1356 01:07:51,570 --> 01:07:55,670 We specify what version of Python we want to run, 3.6. 1357 01:07:55,670 --> 01:07:59,220 We are going to specify what command or commands to run in order 1358 01:07:59,220 --> 01:08:04,170 to install the things necessary before we actually run our tests. 1359 01:08:04,170 --> 01:08:08,370 And so in this case, if we had all of our dependencies and requirements 1360 01:08:08,370 --> 01:08:10,620 stored inside of a file like requirements.txt, 1361 01:08:10,620 --> 01:08:13,650 for instance Django would be an example of a requirement 1362 01:08:13,650 --> 01:08:17,470 that we would need to install, we would include a line like pip 1363 01:08:17,470 --> 01:08:21,060 install -4 requirements.txt to say, go through all 1364 01:08:21,060 --> 01:08:24,779 the lines in requirements.txt and install all of those dependencies, 1365 01:08:24,779 --> 01:08:28,870 because Travis by default is not going to have Django installed, for instance. 1366 01:08:28,870 --> 01:08:31,410 So if we want to run our Django tests, then we 1367 01:08:31,410 --> 01:08:35,340 need to tell Travis that you need to install Django first 1368 01:08:35,340 --> 01:08:37,750 before we can actually run any of that. 1369 01:08:37,750 --> 01:08:41,109 And then finally here we're specifying what script we want to run. 1370 01:08:41,109 --> 01:08:44,340 In other words, what should we actually do in order to run these tests? 1371 01:08:44,340 --> 01:08:47,100 In this case run Python manage.py test. 1372 01:08:47,100 --> 01:08:49,620 Same command we ran in Django ourselves in order 1373 01:08:49,620 --> 01:08:53,130 to run the tests but by putting it inside of the Travis YAML file, 1374 01:08:53,130 --> 01:08:55,899 we're going to have Travis run those tests for us. 1375 01:08:55,899 --> 01:09:00,620 And so let's see what that actually looks like. 1376 01:09:00,620 --> 01:09:10,479 So what I have here is airline3, inside of which is this .travis.yml file, 1377 01:09:10,479 --> 01:09:17,800 which is exactly the contents of what was inside of the slide before, 1378 01:09:17,800 --> 01:09:20,819 specifying the language, specifying what version of Python, 1379 01:09:20,819 --> 01:09:23,479 saying install all of the dependencies first. 1380 01:09:23,479 --> 01:09:26,470 And then after that, go ahead and run Django's built in test. 1381 01:09:26,470 --> 01:09:30,020 This is what I want Travis to do any time I make a change. 1382 01:09:30,020 --> 01:09:33,490 So how do I actually configure Travis and set it up? 1383 01:09:33,490 --> 01:09:38,200 Well if I go to GitHub, you'll notice that I have on my profile 1384 01:09:38,200 --> 01:09:40,540 this repository called Airline which is going 1385 01:09:40,540 --> 01:09:45,609 to be the repository where I store my code for this airline application. 1386 01:09:45,609 --> 01:09:49,500 If I go to TravisCI.org now, which is where 1387 01:09:49,500 --> 01:09:54,410 Travis lives, and I go to my profile, I'll see my repository here. 1388 01:09:54,410 --> 01:09:56,710 Here is my repository, Airline. 1389 01:09:56,710 --> 01:09:59,950 And I'd like to enable Travis for this repository. 1390 01:09:59,950 --> 01:10:01,951 I want to start using Travis on this repository. 1391 01:10:01,951 --> 01:10:03,658 So after I've linked it up with my GitHub 1392 01:10:03,658 --> 01:10:07,330 account, which is fairly straightforward to do, I'll go ahead and enable that. 1393 01:10:07,330 --> 01:10:11,200 And what that's going to do is set it up so that any time I 1394 01:10:11,200 --> 01:10:16,000 make a commit or a push to GitHub, it's going to notify Travis And GitHub does 1395 01:10:16,000 --> 01:10:20,050 this through web hooks, where GitHub has this built in idea of hooks 1396 01:10:20,050 --> 01:10:23,260 on particular events on my repository where I can say, 1397 01:10:23,260 --> 01:10:27,080 anytime someone pushes to a repository, GitHub should know to do this. 1398 01:10:27,080 --> 01:10:31,110 And so in this case I'm saying, anytime I push to my Airline repository, 1399 01:10:31,110 --> 01:10:34,120 GitHub should notify Travis, the CI software that I'm using, 1400 01:10:34,120 --> 01:10:36,700 and let it know that there's been some new change that you 1401 01:10:36,700 --> 01:10:38,760 should run these new tests. 1402 01:10:38,760 --> 01:10:43,330 And so now on the command line I'll go ahead and add .travis.yml. 1403 01:10:43,330 --> 01:10:46,900 Because I want to now include this file inside of my repository. 1404 01:10:46,900 --> 01:10:51,370 I'll go ahead and commit and say I've added .travis.yml. 1405 01:10:51,370 --> 01:10:55,100 And I'll push those changes. 1406 01:10:55,100 --> 01:10:58,140 So now I've pushed those changes to GitHub. 1407 01:10:58,140 --> 01:11:04,410 And if I go here and go to this repository page, what we should see 1408 01:11:04,410 --> 01:11:08,040 is this build. 1409 01:11:08,040 --> 01:11:11,550 So a build is just Travis's way of saying it's detected the fact that 1410 01:11:11,550 --> 01:11:15,180 I've made a new commit, here's my commit, this add .travis-yml. 1411 01:11:15,180 --> 01:11:18,150 And now it's running a whole bunch of commands 1412 01:11:18,150 --> 01:11:20,020 in order to actually run these tests. 1413 01:11:20,020 --> 01:11:21,600 So it's first just doing some set up. 1414 01:11:21,600 --> 01:11:24,030 Here you can see that it's actually cloning the repository 1415 01:11:24,030 --> 01:11:26,280 by saying get clone, for instance. 1416 01:11:26,280 --> 01:11:30,460 And now it's checking what version of Python I have. 1417 01:11:30,460 --> 01:11:33,480 I have Python version 3.6. 1418 01:11:33,480 --> 01:11:35,550 The next thing it's going to do is right here, 1419 01:11:35,550 --> 01:11:38,340 it's running that command that we told it to run, pip install 1420 01:11:38,340 --> 01:11:43,170 -r requirements.txt, to say install all of those requirements. 1421 01:11:43,170 --> 01:11:46,300 And now after it's done installing those requirements 1422 01:11:46,300 --> 01:11:49,590 it's going to run Python manage.py test. 1423 01:11:49,590 --> 01:11:53,100 That line of code that I told Travis to run in order to say, 1424 01:11:53,100 --> 01:11:56,250 I want to run all these tests now and make sure that they actually pass. 1425 01:11:56,250 --> 01:11:57,660 I didn't have to do this myself. 1426 01:11:57,660 --> 01:12:00,090 Travis is running these tests automatically as soon 1427 01:12:00,090 --> 01:12:02,430 as I make a push to the repository. 1428 01:12:02,430 --> 01:12:06,270 And so what I see here is, OK, I failed a couple of tests. 1429 01:12:06,270 --> 01:12:11,730 So I failed the test flight page non-passengers test and the test flight 1430 01:12:11,730 --> 01:12:15,780 page passengers test, in both cases because zero didn't equal one. 1431 01:12:15,780 --> 01:12:19,740 And it told me I ran 10 tests and that two of them failed. 1432 01:12:19,740 --> 01:12:23,520 And therefore my build exited with status code one. 1433 01:12:23,520 --> 01:12:26,800 And so remember these exit codes that we talked about before, 1434 01:12:26,800 --> 01:12:29,640 where an exit code of zero generally means everything was fine. 1435 01:12:29,640 --> 01:12:33,690 Exit code of one, or some other non-zero number, means something went wrong. 1436 01:12:33,690 --> 01:12:36,180 If I run Python manage.py test and I pass 1437 01:12:36,180 --> 01:12:39,300 all of the tests, that command is going to return with exit code 1438 01:12:39,300 --> 01:12:41,190 zero meaning everything is fine. 1439 01:12:41,190 --> 01:12:44,460 And Travis will see that exit code and say, all right, exit code zero. 1440 01:12:44,460 --> 01:12:45,960 Everything must be fine. 1441 01:12:45,960 --> 01:12:47,950 We can therefore say we've passed this build. 1442 01:12:47,950 --> 01:12:49,890 This was all good. 1443 01:12:49,890 --> 01:12:53,070 Whereas on the other hand, in this case, if I failed some of my tests 1444 01:12:53,070 --> 01:12:57,330 for instance, and therefore Python manage.py test 1445 01:12:57,330 --> 01:13:00,500 exited with a status code, an exit code, that was not zero, 1446 01:13:00,500 --> 01:13:03,390 like one for instance, then Travis sees that. 1447 01:13:03,390 --> 01:13:05,340 It notices that something went wrong. 1448 01:13:05,340 --> 01:13:07,930 And therefore it reports that this build has failed. 1449 01:13:07,930 --> 01:13:12,060 So if I scroll up to the top, I see this red X. That means this build failed. 1450 01:13:12,060 --> 01:13:14,165 I didn't actually pass these tests. 1451 01:13:14,165 --> 01:13:17,040 And if I go back to GitHub and actually click on this commit history, 1452 01:13:17,040 --> 01:13:20,590 I can see that on this commit, this commit that I just added, 1453 01:13:20,590 --> 01:13:24,510 this add .travis.yml, on the right side of it now I see that red X that 1454 01:13:24,510 --> 01:13:29,130 actually indicates to me on GitHub that I did not pass all of the tests. 1455 01:13:29,130 --> 01:13:32,580 That Travis tried to run the tests and this build failed 1456 01:13:32,580 --> 01:13:35,990 and therefore it's reporting to me the fact that this build failed. 1457 01:13:35,990 --> 01:13:38,250 And we can use that information to our benefit 1458 01:13:38,250 --> 01:13:40,800 when it comes to things like pull requests 1459 01:13:40,800 --> 01:13:44,430 and merging between branches where you can start to impose constraints 1460 01:13:44,430 --> 01:13:48,330 in GitHub and say, in order to be able to merge from whatever branch 1461 01:13:48,330 --> 01:13:51,060 I'm working on to the original branch, this better not 1462 01:13:51,060 --> 01:13:53,550 be an X. I better have passed all of the tests 1463 01:13:53,550 --> 01:13:56,640 before I actually go about merging whatever change 1464 01:13:56,640 --> 01:13:58,870 I made back into the master branch. 1465 01:13:58,870 --> 01:14:01,140 And so what Travis is doing for me is automating 1466 01:14:01,140 --> 01:14:03,860 the process of running these tests such that now I notice, 1467 01:14:03,860 --> 01:14:07,260 OK, it looks like there was something wrong with my code. 1468 01:14:07,260 --> 01:14:09,180 So I can go back to my code now and say, OK, 1469 01:14:09,180 --> 01:14:13,980 if the problem was with my flight page I can check here. 1470 01:14:13,980 --> 01:14:15,930 What seems to have been the problem? 1471 01:14:15,930 --> 01:14:19,380 Well, it looks like I screwed up which one was passengers 1472 01:14:19,380 --> 01:14:20,880 and which one was non-passenger. 1473 01:14:20,880 --> 01:14:23,580 Just a mistake between which key was which. 1474 01:14:23,580 --> 01:14:27,580 This one should be passengers, this one should be non-passengers. 1475 01:14:27,580 --> 01:14:36,850 And now if I go ahead and add those files and say fix passenger list bug 1476 01:14:36,850 --> 01:14:38,320 and push those changes to GitHub. 1477 01:14:38,320 --> 01:14:40,940 1478 01:14:40,940 --> 01:14:43,820 And now I check GitHub's commit history. 1479 01:14:43,820 --> 01:14:46,790 So far I don't see anything there because the build's not done. 1480 01:14:46,790 --> 01:14:48,530 You'll notice that changes to this circle 1481 01:14:48,530 --> 01:14:50,750 to indicate the fact that the build is currently 1482 01:14:50,750 --> 01:14:53,250 running, that it doesn't know yet whether or not I've passed 1483 01:14:53,250 --> 01:14:54,960 or failed those individual tests. 1484 01:14:54,960 --> 01:14:59,719 But if I check back here I can see that now Travis 1485 01:14:59,719 --> 01:15:02,260 has detected the fact that I've made this new push, that I've 1486 01:15:02,260 --> 01:15:04,550 tried to fix the passenger list bug. 1487 01:15:04,550 --> 01:15:07,820 And now it's going to go ahead and, again, run all the requirements 1488 01:15:07,820 --> 01:15:10,280 and redo that exact same set of steps that it did before. 1489 01:15:10,280 --> 01:15:14,720 It's automating the process of building my code, running my tests, 1490 01:15:14,720 --> 01:15:16,130 and making sure that they pass. 1491 01:15:16,130 --> 01:15:19,056 In this case, it looks like it ran all 10 tests. 1492 01:15:19,056 --> 01:15:20,430 Nothing seems to have gone wrong. 1493 01:15:20,430 --> 01:15:21,470 It says OK. 1494 01:15:21,470 --> 01:15:25,160 Manage.py test exited with zero, so now we're done. 1495 01:15:25,160 --> 01:15:27,200 And if we go back and check this commit history, 1496 01:15:27,200 --> 01:15:30,110 now we see next to each one of these changes, 1497 01:15:30,110 --> 01:15:33,350 this add .travis.yml commit that I made, something went wrong. 1498 01:15:33,350 --> 01:15:35,010 I didn't pass the test here. 1499 01:15:35,010 --> 01:15:38,300 But now here when I tried to actually fix the passenger list bug, 1500 01:15:38,300 --> 01:15:40,190 then that did in fact pass. 1501 01:15:40,190 --> 01:15:43,170 So all is well in that case now. 1502 01:15:43,170 --> 01:15:46,340 And so what that's ultimately allowing us to do is automating this process. 1503 01:15:46,340 --> 01:15:50,810 Using continuous integration to say, any time I make a change, let's 1504 01:15:50,810 --> 01:15:52,126 just push it to some branch. 1505 01:15:52,126 --> 01:15:54,000 Maybe it's not going to be the master branch. 1506 01:15:54,000 --> 01:15:55,670 Maybe it's going to be some other branch. 1507 01:15:55,670 --> 01:15:57,050 But then let's run the test suit on it. 1508 01:15:57,050 --> 01:15:59,180 Make sure that the tests are all passing in order 1509 01:15:59,180 --> 01:16:02,990 to make sure that everything is in order before I try and merge things together. 1510 01:16:02,990 --> 01:16:05,660 With the goal being such that when I do merge things together, 1511 01:16:05,660 --> 01:16:07,790 I don't run into problems when I try and integrate everything 1512 01:16:07,790 --> 01:16:10,499 all together, that I've been checking all along with every change 1513 01:16:10,499 --> 01:16:12,248 that I make, am I going to break anything? 1514 01:16:12,248 --> 01:16:13,470 Am I going to break anything? 1515 01:16:13,470 --> 01:16:15,450 If I do I have the opportunity to fix it. 1516 01:16:15,450 --> 01:16:17,510 And if not, then I can feel confident at the end 1517 01:16:17,510 --> 01:16:19,940 that as long as my tests are thorough and comprehensive, 1518 01:16:19,940 --> 01:16:23,150 that my change is not going to cause any problems with the rest of my web 1519 01:16:23,150 --> 01:16:24,930 application. 1520 01:16:24,930 --> 01:16:27,510 Questions about any of that so far? 1521 01:16:27,510 --> 01:16:30,530 About what Travis or other continuous integration tools are used for? 1522 01:16:30,530 --> 01:16:32,050 Travis certainly isn't the only one. 1523 01:16:32,050 --> 01:16:33,930 Just happens to be one of many. 1524 01:16:33,930 --> 01:16:36,600 But they're all with the ultimate goal of doing things 1525 01:16:36,600 --> 01:16:39,394 like this, of automating the process of integrating code together. 1526 01:16:39,394 --> 01:16:41,310 And they can be used for other things as well. 1527 01:16:41,310 --> 01:16:45,990 Travis can be used in order to make sure that we are sending notifications, 1528 01:16:45,990 --> 01:16:50,250 for instance, such that Travis can notify a slack channel, for instance, 1529 01:16:50,250 --> 01:16:52,770 to say that this succeeded or this failed. 1530 01:16:52,770 --> 01:16:55,260 It can also be used for automatically deploying an app. 1531 01:16:55,260 --> 01:17:01,230 So if you're using Amazon AWS in order to deploy your application to a server 1532 01:17:01,230 --> 01:17:03,210 somewhere, Travis can take care of that for you 1533 01:17:03,210 --> 01:17:07,350 where it can say if all of the tests passed, then let's go ahead 1534 01:17:07,350 --> 01:17:10,612 and deploy this application to the internet and automatically deliver it. 1535 01:17:10,612 --> 01:17:12,820 And if the tests didn't pass, then let's not do that. 1536 01:17:12,820 --> 01:17:15,224 Let's not actually push those changes because we 1537 01:17:15,224 --> 01:17:17,640 want to make sure the tests are passing before we go ahead 1538 01:17:17,640 --> 01:17:19,950 and make those changes. 1539 01:17:19,950 --> 01:17:21,580 Questions about any of that? 1540 01:17:21,580 --> 01:17:24,570 1541 01:17:24,570 --> 01:17:29,120 OK so what other issues might we run into when 1542 01:17:29,120 --> 01:17:32,870 we're thinking about developing software, about building applications, 1543 01:17:32,870 --> 01:17:35,040 deploying applications to the internet? 1544 01:17:35,040 --> 01:17:38,120 Well, one thing that you may have already come across 1545 01:17:38,120 --> 01:17:42,030 is just making sure the application even runs in different environments. 1546 01:17:42,030 --> 01:17:45,410 If I took one web application that I've just written and put it 1547 01:17:45,410 --> 01:17:49,930 onto a different computer and then try to run that Django application, 1548 01:17:49,930 --> 01:17:51,992 there is no guarantee that that application 1549 01:17:51,992 --> 01:17:53,450 is going to behave in the same way. 1550 01:17:53,450 --> 01:17:56,030 Maybe the computer that I put it on doesn't even 1551 01:17:56,030 --> 01:17:57,530 have Django installed, for instance. 1552 01:17:57,530 --> 01:17:59,238 And so I'm going to get an error if I try 1553 01:17:59,238 --> 01:18:02,366 and run the server because Django doesn't exist on the computer 1554 01:18:02,366 --> 01:18:02,990 that I'm using. 1555 01:18:02,990 --> 01:18:07,465 Or maybe it's using an older version of Django whereby as a result 1556 01:18:07,465 --> 01:18:09,590 some of the newer features of Django that I'm using 1557 01:18:09,590 --> 01:18:11,381 aren't going to work on the older computer. 1558 01:18:11,381 --> 01:18:13,940 And so in web application development you 1559 01:18:13,940 --> 01:18:15,740 might run into all sorts of these problems 1560 01:18:15,740 --> 01:18:17,886 where the computer you're running things on, 1561 01:18:17,886 --> 01:18:19,760 or the computer you're developing locally on, 1562 01:18:19,760 --> 01:18:22,310 might be different from the computer where you're actually 1563 01:18:22,310 --> 01:18:25,040 running the application in production, where things are actually 1564 01:18:25,040 --> 01:18:26,720 running that users are really using. 1565 01:18:26,720 --> 01:18:30,740 And as a result you might run into all these sorts of compatibility errors 1566 01:18:30,740 --> 01:18:33,740 between your machine and some other machine 1567 01:18:33,740 --> 01:18:36,260 because there is no guarantee that different machines are 1568 01:18:36,260 --> 01:18:37,700 behaving in the same way. 1569 01:18:37,700 --> 01:18:41,364 When really we would like to make sure that when I'm running my application 1570 01:18:41,364 --> 01:18:44,030 it's going to be working in the same environment the whole time. 1571 01:18:44,030 --> 01:18:46,910 That if I have these dependencies and libraries installed 1572 01:18:46,910 --> 01:18:50,155 on my application on my computer, then someone else running my application 1573 01:18:50,155 --> 01:18:53,030 on a different computer will have all of those same things installed, 1574 01:18:53,030 --> 01:18:57,720 will have the same set up ready to go without any of those issues. 1575 01:18:57,720 --> 01:19:00,170 So how might we go about achieving that? 1576 01:19:00,170 --> 01:19:02,150 What are some strategies we could use to make 1577 01:19:02,150 --> 01:19:05,135 sure that the way I'm developing my application 1578 01:19:05,135 --> 01:19:07,760 is going to be in the same sort of environment to someone else? 1579 01:19:07,760 --> 01:19:12,020 1580 01:19:12,020 --> 01:19:14,590 Multiple possible things you could throw out here as ideas. 1581 01:19:14,590 --> 01:19:15,090 Yeah? 1582 01:19:15,090 --> 01:19:16,070 AUDIENCE: Virtual machine. 1583 01:19:16,070 --> 01:19:17,120 BRIAN YU: A virtual machine, yeah. 1584 01:19:17,120 --> 01:19:18,830 And for a long time, a virtual machine was really 1585 01:19:18,830 --> 01:19:20,454 the best way to do something like this. 1586 01:19:20,454 --> 01:19:24,020 That you would, in a virtual machine you would set up a virtual operating 1587 01:19:24,020 --> 01:19:26,720 system, the guest operating system that was operating 1588 01:19:26,720 --> 01:19:29,780 on top of your existing operating system where you could virtually 1589 01:19:29,780 --> 01:19:33,680 run your own computer that had whatever configuration settings you wanted it 1590 01:19:33,680 --> 01:19:34,310 to. 1591 01:19:34,310 --> 01:19:36,620 And you could therefore say, this virtual machine 1592 01:19:36,620 --> 01:19:39,050 is going to have these dependencies installed on it. 1593 01:19:39,050 --> 01:19:41,100 It's going to be running this operating system. 1594 01:19:41,100 --> 01:19:43,580 And so when I run my application on that virtual machine 1595 01:19:43,580 --> 01:19:47,540 then I can be sure that if I'm running it on this particular virtual machine 1596 01:19:47,540 --> 01:19:51,230 then everything is going to behave exactly as expected 1597 01:19:51,230 --> 01:19:53,090 because the machines are identical. 1598 01:19:53,090 --> 01:19:56,060 We're running the same virtual machine on my own computer 1599 01:19:56,060 --> 01:19:59,035 and on wherever I'm trying to run the application. 1600 01:19:59,035 --> 01:20:01,160 What might be some drawbacks of the virtual machine 1601 01:20:01,160 --> 01:20:05,000 that we can think of, for running a web application like a Django web 1602 01:20:05,000 --> 01:20:07,720 application on the internet? 1603 01:20:07,720 --> 01:20:08,220 Yeah? 1604 01:20:08,220 --> 01:20:08,910 AUDIENCE: [INAUDIBLE]. 1605 01:20:08,910 --> 01:20:09,440 BRIAN YU: It's slower, yeah. 1606 01:20:09,440 --> 01:20:11,148 It tends to be pretty slow because I need 1607 01:20:11,148 --> 01:20:13,860 to start up this entire virtual machine with an entire operating 1608 01:20:13,860 --> 01:20:17,040 system of its own, with its entire any libraries that 1609 01:20:17,040 --> 01:20:18,720 are necessary for the operating system. 1610 01:20:18,720 --> 01:20:21,570 And generally that can be overkill if all I want to do 1611 01:20:21,570 --> 01:20:24,840 is run a web application, for instance run a simple Django application that 1612 01:20:24,840 --> 01:20:27,297 just needs to have certain dependencies installed. 1613 01:20:27,297 --> 01:20:29,130 And so one solution that people have come up 1614 01:20:29,130 --> 01:20:32,600 with in order to deal with that problem is containerization. 1615 01:20:32,600 --> 01:20:37,167 The idea of creating containers that are not quite virtual machines in the sense 1616 01:20:37,167 --> 01:20:39,750 that they're not fully blown operating systems on top of which 1617 01:20:39,750 --> 01:20:41,850 are all these additional layers but are just 1618 01:20:41,850 --> 01:20:43,980 isolated containers that have just the things 1619 01:20:43,980 --> 01:20:45,660 we want to have installed on them. 1620 01:20:45,660 --> 01:20:49,500 Such that if we define what should go into a particular container 1621 01:20:49,500 --> 01:20:53,160 inside of what we'll call an image, we can make sure that the image 1622 01:20:53,160 --> 01:20:55,320 that I'm using on my computer is the same 1623 01:20:55,320 --> 01:20:58,110 as the image that's being used on the server 1624 01:20:58,110 --> 01:21:00,120 that my application is eventually running on. 1625 01:21:00,120 --> 01:21:03,760 And therefore I can be confident that whatever environment 1626 01:21:03,760 --> 01:21:05,850 I'm running my application on my local computer 1627 01:21:05,850 --> 01:21:10,300 will be the same for anyone else who's also running the application as well. 1628 01:21:10,300 --> 01:21:13,170 And so one of the most popular tools that I'll introduce now 1629 01:21:13,170 --> 01:21:15,720 for containerization is called Docker. 1630 01:21:15,720 --> 01:21:19,320 And the way that Docker works is-- we can use this as an example 1631 01:21:19,320 --> 01:21:21,470 by comparing Docker to a virtual machine. 1632 01:21:21,470 --> 01:21:24,870 So on a virtual machine we have the original computer on which you're 1633 01:21:24,870 --> 01:21:26,800 running your own operating system. 1634 01:21:26,800 --> 01:21:29,200 And on top of that, for each application you would run, 1635 01:21:29,200 --> 01:21:32,784 you'd have a virtual machine which has a guest operating system on it 1636 01:21:32,784 --> 01:21:35,200 on top of which are all the libraries that you might need. 1637 01:21:35,200 --> 01:21:38,130 And then the application is running on top of that. 1638 01:21:38,130 --> 01:21:40,440 Whereas the way Doctor is going to work is 1639 01:21:40,440 --> 01:21:44,519 that Docker is just going to build directly on top of the operating 1640 01:21:44,519 --> 01:21:45,810 system on which you're running. 1641 01:21:45,810 --> 01:21:48,780 It's going to have direct access to the operating system 1642 01:21:48,780 --> 01:21:50,436 that your computer is running on. 1643 01:21:50,436 --> 01:21:52,560 And it's just going to add an additional layer that 1644 01:21:52,560 --> 01:21:54,900 helps to keep these containers isolated from each other 1645 01:21:54,900 --> 01:21:58,230 so that each container can still have its own libraries and dependencies. 1646 01:21:58,230 --> 01:22:03,420 I can say, inside of this container I want to install version two of Django, 1647 01:22:03,420 --> 01:22:06,960 for instance, and then run my application on top of that container. 1648 01:22:06,960 --> 01:22:09,420 So it's a slightly different model but it's still 1649 01:22:09,420 --> 01:22:11,790 trying to get at the idea of, separate out 1650 01:22:11,790 --> 01:22:15,002 our applications into these containers where each container 1651 01:22:15,002 --> 01:22:16,710 has just the things that it needs to have 1652 01:22:16,710 --> 01:22:19,766 installed in order to run that application in such a way 1653 01:22:19,766 --> 01:22:20,640 that it's consistent. 1654 01:22:20,640 --> 01:22:24,000 Such that so long as I know what the instructions 1655 01:22:24,000 --> 01:22:26,250 are for creating one of these containers, 1656 01:22:26,250 --> 01:22:28,980 then when I need to run my application somewhere else 1657 01:22:28,980 --> 01:22:31,980 I can just create one of these containers 1658 01:22:31,980 --> 01:22:34,060 and then run the application. 1659 01:22:34,060 --> 01:22:36,900 And this will also be useful when it comes towards applications 1660 01:22:36,900 --> 01:22:39,090 that have multiple different parts. 1661 01:22:39,090 --> 01:22:42,690 So for instance in project one where you were 1662 01:22:42,690 --> 01:22:47,100 using a PostgreSQL database and a Flask web application, 1663 01:22:47,100 --> 01:22:49,740 there were sort of two different parts to that web application. 1664 01:22:49,740 --> 01:22:51,700 We had the Flask application itself. 1665 01:22:51,700 --> 01:22:54,450 And then we also had your database running in some separate server 1666 01:22:54,450 --> 01:22:55,350 elsewhere. 1667 01:22:55,350 --> 01:22:58,266 And what Docker is going to let us do, and what we'll see in a moment, 1668 01:22:58,266 --> 01:23:00,240 is let us take all of those different services 1669 01:23:00,240 --> 01:23:04,291 and compose them together, so to speak, in such a way that so long 1670 01:23:04,291 --> 01:23:07,290 as you have the instructions for how do you start up the web application 1671 01:23:07,290 --> 01:23:08,050 server? 1672 01:23:08,050 --> 01:23:09,780 How do you start up the database server? 1673 01:23:09,780 --> 01:23:12,960 It can do all of those things on its own such 1674 01:23:12,960 --> 01:23:15,510 that you don't need to worry about whether or not 1675 01:23:15,510 --> 01:23:18,240 you have all the services connected in the right way. 1676 01:23:18,240 --> 01:23:20,760 So we'll take a look at some concrete examples of that 1677 01:23:20,760 --> 01:23:24,540 by taking our application for airlines and Dockerizing it 1678 01:23:24,540 --> 01:23:27,420 by using Docker images and containers in a moment 1679 01:23:27,420 --> 01:23:32,137 to explore how we can actually take advantage of Docker to make it easier, 1680 01:23:32,137 --> 01:23:33,720 ultimately, to deploy our application. 1681 01:23:33,720 --> 01:23:37,125 The goal of all of this is going to be to make it easier to test and deploy 1682 01:23:37,125 --> 01:23:40,710 our application in such a way that everything is all in one place, 1683 01:23:40,710 --> 01:23:43,620 that we don't need to worry too much about compatibility issues that 1684 01:23:43,620 --> 01:23:45,150 might arise. 1685 01:23:45,150 --> 01:23:49,350 So let's take a look at airline4 now. 1686 01:23:49,350 --> 01:23:51,270 And we'll open that up. 1687 01:23:51,270 --> 01:23:55,080 And so the first thing that you'll notice that's different about airline4 1688 01:23:55,080 --> 01:24:01,080 is that I have this file called Docker file inside of my repository. 1689 01:24:01,080 --> 01:24:04,770 And so Docker file is going to be the file that's 1690 01:24:04,770 --> 01:24:08,240 going to define a Docker image, where a Docker image you can think of it 1691 01:24:08,240 --> 01:24:12,150 as just the instructions for how I'm going to create one of these containers 1692 01:24:12,150 --> 01:24:15,280 that my application is going to live in. 1693 01:24:15,280 --> 01:24:17,670 So here are the instructions inside this Docker file 1694 01:24:17,670 --> 01:24:22,540 for how to create a container that I want to run my web application in. 1695 01:24:22,540 --> 01:24:27,030 So what I'm going to say is, from Python:3 it's just saying, 1696 01:24:27,030 --> 01:24:30,600 from is going to tell me sort of like inheriting from some existing Docker 1697 01:24:30,600 --> 01:24:31,170 image. 1698 01:24:31,170 --> 01:24:33,960 There's an existing Docker image called Python 3 that's 1699 01:24:33,960 --> 01:24:36,330 just a default image for creating a container that 1700 01:24:36,330 --> 01:24:38,470 runs Python 3 applications. 1701 01:24:38,470 --> 01:24:40,560 And I'm going to use that. 1702 01:24:40,560 --> 01:24:45,050 I'm going to set my working directory, the directory that I'm in, 1703 01:24:45,050 --> 01:24:47,169 to a user source app. 1704 01:24:47,169 --> 01:24:49,710 So this app directory is just going to be the directory where 1705 01:24:49,710 --> 01:24:51,100 I'm running my application. 1706 01:24:51,100 --> 01:24:53,280 I could have used some other directory but this just 1707 01:24:53,280 --> 01:24:56,070 happens to be a convention to put everything inside of this app 1708 01:24:56,070 --> 01:24:57,180 directory. 1709 01:24:57,180 --> 01:24:59,700 And now what do I want to do to make sure 1710 01:24:59,700 --> 01:25:02,206 that whenever I'm running this application 1711 01:25:02,206 --> 01:25:04,580 I'm always going to be running it in the same environment 1712 01:25:04,580 --> 01:25:06,780 so I have the necessary dependencies installed? 1713 01:25:06,780 --> 01:25:08,821 Well the first thing I wanted to do was make sure 1714 01:25:08,821 --> 01:25:11,040 that I've got Python 3 installed because I want to be 1715 01:25:11,040 --> 01:25:12,794 using Python 3 for this application. 1716 01:25:12,794 --> 01:25:15,460 But I also want to make sure that my requirements are installed. 1717 01:25:15,460 --> 01:25:20,190 That if I need Django installed in order to run this Django application, then 1718 01:25:20,190 --> 01:25:26,100 I better first add requirements.txt to my app directory. 1719 01:25:26,100 --> 01:25:30,240 And then I'm going to run pip install -r requirements.txt, to say go ahead 1720 01:25:30,240 --> 01:25:32,970 and run the installation of those requirements. 1721 01:25:32,970 --> 01:25:36,330 Such that now if I were to ever create a Docker container based 1722 01:25:36,330 --> 01:25:40,830 on this Docker file what that's going to do is install all the requirements. 1723 01:25:40,830 --> 01:25:48,390 Where if I look at my requirements file I have a Django version two and also 1724 01:25:48,390 --> 01:25:51,790 a Python library that's going to be useful for dealing with PostgreSQL 1725 01:25:51,790 --> 01:25:55,180 if I have a Postgres database, for instance. 1726 01:25:55,180 --> 01:25:59,530 And finally, the last step is going to be add all the contents of my current 1727 01:25:59,530 --> 01:26:01,660 directory, dot meaning the current directory, 1728 01:26:01,660 --> 01:26:05,290 to that app directory as well such that all of my Django files-- 1729 01:26:05,290 --> 01:26:08,860 my application files, my settings files, URLs and views and templates and all 1730 01:26:08,860 --> 01:26:09,430 that-- 1731 01:26:09,430 --> 01:26:11,410 gets added to my application directory. 1732 01:26:11,410 --> 01:26:14,560 And so that is going to be how I define-- 1733 01:26:14,560 --> 01:26:17,050 this is going to help me define my Docker image such 1734 01:26:17,050 --> 01:26:20,710 that when I create a container it's going to run all of these instructions, 1735 01:26:20,710 --> 01:26:23,110 first installing those requirements and then adding 1736 01:26:23,110 --> 01:26:27,190 the entire contents of my application to this app directory 1737 01:26:27,190 --> 01:26:29,600 inside of my container. 1738 01:26:29,600 --> 01:26:34,750 So how do I then say what my application needs to do? 1739 01:26:34,750 --> 01:26:39,470 So let me first show you settings.py. 1740 01:26:39,470 --> 01:26:42,590 So settings.py is one of those files we took a look at in Django 1741 01:26:42,590 --> 01:26:45,740 before that lets us define various settings for our application. 1742 01:26:45,740 --> 01:26:48,500 Last week inside of settings.py we specified 1743 01:26:48,500 --> 01:26:52,130 that our database would be SQLite, which is a simple database and very 1744 01:26:52,130 --> 01:26:53,681 easy to use when testing. 1745 01:26:53,681 --> 01:26:56,180 But in reality if you were to develop a web application that 1746 01:26:56,180 --> 01:26:58,730 was going to be used by a large number of users, 1747 01:26:58,730 --> 01:27:00,590 SQLite's probably not the best one to use. 1748 01:27:00,590 --> 01:27:03,715 You'll probably want to use a database that will scale a little bit better. 1749 01:27:03,715 --> 01:27:06,350 And for instance, Postgres is a good one for that, 1750 01:27:06,350 --> 01:27:08,600 which is what we used in project one. 1751 01:27:08,600 --> 01:27:13,730 And so what we might do is change our database, for instance, 1752 01:27:13,730 --> 01:27:17,510 to instead of using SQLite as our database 1753 01:27:17,510 --> 01:27:21,200 that we defined in settings.py, we might instead specify inside of our settings 1754 01:27:21,200 --> 01:27:24,140 file that we want to use PostgreSQL instead. 1755 01:27:24,140 --> 01:27:26,750 So instead of engine, which with SQLite before, 1756 01:27:26,750 --> 01:27:28,640 now I'm going to specify Postgres. 1757 01:27:28,640 --> 01:27:32,055 And I can use name and user, Postgres is just 1758 01:27:32,055 --> 01:27:33,680 the name and user name of the database. 1759 01:27:33,680 --> 01:27:34,804 It might be something else. 1760 01:27:34,804 --> 01:27:36,710 But in this case, we use Postgres. 1761 01:27:36,710 --> 01:27:41,570 And specifying the port on which that database is run. 1762 01:27:41,570 --> 01:27:44,150 And so depending on where your database is, 1763 01:27:44,150 --> 01:27:45,740 these might be a little bit different. 1764 01:27:45,740 --> 01:27:48,230 But you'll see how this information is going 1765 01:27:48,230 --> 01:27:50,990 to be useful now because what I want to do, now, 1766 01:27:50,990 --> 01:27:55,336 is tell Docker that when I start up this application 1767 01:27:55,336 --> 01:27:57,710 there are a couple of different things that I want to do. 1768 01:27:57,710 --> 01:27:59,390 On one hand I want to start up the database. 1769 01:27:59,390 --> 01:28:01,431 I want to make sure that my Postgres databases is 1770 01:28:01,431 --> 01:28:03,620 up and running, because my Django application is 1771 01:28:03,620 --> 01:28:05,480 going to need to talk to it. 1772 01:28:05,480 --> 01:28:09,170 Secondly, I'm also going to want to actually start up the web application 1773 01:28:09,170 --> 01:28:10,942 itself because I want to make sure Django 1774 01:28:10,942 --> 01:28:12,650 is up and running, that that server is up 1775 01:28:12,650 --> 01:28:15,170 and running for when I want to interact with it. 1776 01:28:15,170 --> 01:28:17,210 And finally, there's actually a third thing 1777 01:28:17,210 --> 01:28:19,760 that I might want to do which is I might want to, 1778 01:28:19,760 --> 01:28:24,020 anytime I run my application, to automatically run any of the migrations 1779 01:28:24,020 --> 01:28:25,250 that I need to apply. 1780 01:28:25,250 --> 01:28:29,390 Such that if I bring my web application to some new place 1781 01:28:29,390 --> 01:28:32,150 where it's got some new database, I want to make sure that any 1782 01:28:32,150 --> 01:28:34,080 of those migrations that I had-- migrations 1783 01:28:34,080 --> 01:28:37,400 being ways of applying changes to the existing tables that 1784 01:28:37,400 --> 01:28:39,410 existed inside my web application. 1785 01:28:39,410 --> 01:28:42,570 I want those migrations to be applied to my new database. 1786 01:28:42,570 --> 01:28:45,710 And so I would like it for whenever my application starts up, 1787 01:28:45,710 --> 01:28:47,510 go ahead and migrate everything over, any 1788 01:28:47,510 --> 01:28:49,760 changes I've made to those tables, such that I 1789 01:28:49,760 --> 01:28:51,920 make sure that my web application is running 1790 01:28:51,920 --> 01:28:55,140 on the latest version of my database. 1791 01:28:55,140 --> 01:28:57,320 So those are all of the different services 1792 01:28:57,320 --> 01:29:01,110 that I want running when I run my Python application. 1793 01:29:01,110 --> 01:29:03,320 And the way to make all of that come together 1794 01:29:03,320 --> 01:29:06,740 is by using what's called Docker Compose. 1795 01:29:06,740 --> 01:29:11,220 And so DockerCompose.yml will be the last new file that we'll look at today. 1796 01:29:11,220 --> 01:29:14,330 Which is going to be a YAML file in the same file format as before, 1797 01:29:14,330 --> 01:29:18,560 which is going to define all of the different services that make up my web 1798 01:29:18,560 --> 01:29:20,070 application. 1799 01:29:20,070 --> 01:29:24,980 And so the first service that I'm going to have is a database. 1800 01:29:24,980 --> 01:29:28,010 And I need to specify what Docker image should 1801 01:29:28,010 --> 01:29:29,842 I use in order to construct this database. 1802 01:29:29,842 --> 01:29:31,550 We'll go ahead and use Postgres, which is 1803 01:29:31,550 --> 01:29:36,330 just one of the default ones for just creating a Postgres database. 1804 01:29:36,330 --> 01:29:39,219 In addition to the database, I also want to set up a service 1805 01:29:39,219 --> 01:29:41,510 for migrating everything over such that when I start up 1806 01:29:41,510 --> 01:29:45,170 an application, whether it's on my local computer or on a server somewhere, 1807 01:29:45,170 --> 01:29:47,630 I automatically migrate any changes. 1808 01:29:47,630 --> 01:29:51,830 And so build dot means I'm going to build based on the Docker 1809 01:29:51,830 --> 01:29:54,620 file in my current directory, because I have this Docker file here 1810 01:29:54,620 --> 01:29:59,570 that's going to tell me how to install all the requirements, for instance. 1811 01:29:59,570 --> 01:30:02,510 The command is going to be what command to actually run 1812 01:30:02,510 --> 01:30:04,070 to make the service happen. 1813 01:30:04,070 --> 01:30:07,600 And so I'm going to run Python manage.py migrate in order 1814 01:30:07,600 --> 01:30:10,850 to run all of those migrations because that's what I want to happen every time 1815 01:30:10,850 --> 01:30:13,170 I start up the application. 1816 01:30:13,170 --> 01:30:16,940 Volumes helps to link between different files. 1817 01:30:16,940 --> 01:30:19,940 So I'm saying, link between the current directory 1818 01:30:19,940 --> 01:30:24,860 that I'm in with this app directory, which is where in the Docker file 1819 01:30:24,860 --> 01:30:26,930 I said my container was running. 1820 01:30:26,930 --> 01:30:30,110 And then depends_on is saying, what should be done first 1821 01:30:30,110 --> 01:30:31,970 before I actually work on this service? 1822 01:30:31,970 --> 01:30:35,390 And in particular, I need this DB service running, this database service 1823 01:30:35,390 --> 01:30:37,820 running, which is my Postgres database to be working 1824 01:30:37,820 --> 01:30:41,120 before I do my migration because I want to make sure the database is there 1825 01:30:41,120 --> 01:30:43,790 before I try and migrate anything there. 1826 01:30:43,790 --> 01:30:45,980 So now I've set up the database. 1827 01:30:45,980 --> 01:30:49,650 Now I've set up the migration such that when I start up my application using 1828 01:30:49,650 --> 01:30:53,510 Docker it's going to create the database as its own container, 1829 01:30:53,510 --> 01:30:56,030 then migrate any changes that need to be made. 1830 01:30:56,030 --> 01:30:58,220 And now let's add another service just called 1831 01:30:58,220 --> 01:31:02,750 Web, which is going to be the one that's actually running the web application. 1832 01:31:02,750 --> 01:31:07,880 So again that's going to be based on the Docker file in my current directory. 1833 01:31:07,880 --> 01:31:09,530 What command am I going to run? 1834 01:31:09,530 --> 01:31:12,890 I'm just going to run that same Python manage.py run server. 1835 01:31:12,890 --> 01:31:16,640 And specifying what IP address and port I want it to run on, 1836 01:31:16,640 --> 01:31:20,490 just setting that explicitly though you might not necessarily need to do that. 1837 01:31:20,490 --> 01:31:25,520 And then the only different things here are what's happening here in ports. 1838 01:31:25,520 --> 01:31:29,750 What this is saying is, my container has its own set 1839 01:31:29,750 --> 01:31:32,060 of ports where things are running on. 1840 01:31:32,060 --> 01:31:34,670 And what this is saying is, take port 8,000 1841 01:31:34,670 --> 01:31:38,060 that is running inside of my container and map 1842 01:31:38,060 --> 01:31:41,600 that to port 8,000 that's running on my own computers. 1843 01:31:41,600 --> 01:31:45,170 So that if I, on my computer, go to this URL 1844 01:31:45,170 --> 01:31:47,210 I can actually access the web application that's 1845 01:31:47,210 --> 01:31:49,880 running inside of that container. 1846 01:31:49,880 --> 01:31:53,310 And then finally, I'm saying this depends on two things. 1847 01:31:53,310 --> 01:31:54,980 It depends on the database being there. 1848 01:31:54,980 --> 01:31:56,980 I want to make sure the database is there first. 1849 01:31:56,980 --> 01:31:59,760 And I also want to make sure that the migration was successful. 1850 01:31:59,760 --> 01:32:02,450 So make sure the database is there, make sure 1851 01:32:02,450 --> 01:32:04,040 that I've migrated everything over. 1852 01:32:04,040 --> 01:32:07,370 And once that's done, then I can start up this web application 1853 01:32:07,370 --> 01:32:09,360 by running the server. 1854 01:32:09,360 --> 01:32:12,381 And so this is a lot of new syntax. 1855 01:32:12,381 --> 01:32:14,630 The general idea-- don't worry about memorizing this-- 1856 01:32:14,630 --> 01:32:17,780 is just getting a sense for what Docker is designed to let us do. 1857 01:32:17,780 --> 01:32:22,370 Where the idea is that it's a way to allow us to easily create containers 1858 01:32:22,370 --> 01:32:24,890 that have all of the requirements and dependencies 1859 01:32:24,890 --> 01:32:28,040 that we need by just automatically installing whatever requirements we 1860 01:32:28,040 --> 01:32:29,960 have, making sure we have the right version 1861 01:32:29,960 --> 01:32:33,620 of Python installed, and then, in Docker Compose, 1862 01:32:33,620 --> 01:32:36,680 defining the different parts that make up my web application-- defining 1863 01:32:36,680 --> 01:32:40,190 the database that I have, any migrations that I might need to perform, 1864 01:32:40,190 --> 01:32:43,100 and the web application that I actually want to start up. 1865 01:32:43,100 --> 01:32:46,160 And so now in order to start up my application, 1866 01:32:46,160 --> 01:32:49,730 I can run something like Docker Compose up, 1867 01:32:49,730 --> 01:32:53,030 meaning start up my web application using Docker Compose. 1868 01:32:53,030 --> 01:32:56,610 It's going to look for that DockerCompose.yml file. 1869 01:32:56,610 --> 01:32:58,610 And what you'll see is the first thing that it's 1870 01:32:58,610 --> 01:33:00,500 going to do is start up the database. 1871 01:33:00,500 --> 01:33:03,050 Then it's going to start up the migration. 1872 01:33:03,050 --> 01:33:05,820 Then it's going to actually start up the web server. 1873 01:33:05,820 --> 01:33:12,140 And now I can go to 0.0.0.800, and now I see 1874 01:33:12,140 --> 01:33:15,020 that I have this interface where right now I don't have any flights 1875 01:33:15,020 --> 01:33:17,900 but now I'm running my web application using Docker. 1876 01:33:17,900 --> 01:33:20,780 And so all I've done so far is just start up 1877 01:33:20,780 --> 01:33:24,090 the web application, which doesn't seem like I've gained a whole lot. 1878 01:33:24,090 --> 01:33:27,050 But what I have gained is, I've gained at these two files, the Docker 1879 01:33:27,050 --> 01:33:31,770 file and the Docker Compose file, that define exactly how to start up this web 1880 01:33:31,770 --> 01:33:32,270 application. 1881 01:33:32,270 --> 01:33:34,490 And this is using a Postgres database which 1882 01:33:34,490 --> 01:33:37,997 I didn't need to specifically download Postgres and install it and figure out 1883 01:33:37,997 --> 01:33:38,580 how to use it. 1884 01:33:38,580 --> 01:33:41,870 Docker is taking care of that for me by just creating a container that's 1885 01:33:41,870 --> 01:33:43,490 running a Postgres database. 1886 01:33:43,490 --> 01:33:47,120 And what this means now is that I can give this repository to anyone. 1887 01:33:47,120 --> 01:33:49,460 And so long as they have Docker installed 1888 01:33:49,460 --> 01:33:51,500 they can run this web application and expect 1889 01:33:51,500 --> 01:33:53,600 it to behave in exactly the same way. 1890 01:33:53,600 --> 01:33:56,350 And so this is very useful where, in web applications, 1891 01:33:56,350 --> 01:34:01,070 it can often be a challenge to make sure that the way an application is behaving 1892 01:34:01,070 --> 01:34:02,990 on your computer will be the same as the way 1893 01:34:02,990 --> 01:34:05,210 the application is behaving on different computer. 1894 01:34:05,210 --> 01:34:07,310 By taking advantage of containerization, we 1895 01:34:07,310 --> 01:34:11,210 can A, simplify the process of combining these services together, but B, 1896 01:34:11,210 --> 01:34:14,390 simplify the process of making sure that the application is 1897 01:34:14,390 --> 01:34:18,900 going to behave the same way across different places as well. 1898 01:34:18,900 --> 01:34:24,216 Questions about what Docker is, what it's used for, the general ideas here? 1899 01:34:24,216 --> 01:34:24,716 Yeah. 1900 01:34:24,716 --> 01:34:26,183 AUDIENCE: [INAUDIBLE]? 1901 01:34:26,183 --> 01:34:34,051 1902 01:34:34,051 --> 01:34:35,050 BRIAN YU: Good question. 1903 01:34:35,050 --> 01:34:37,240 So who is actually going to be using Docker? 1904 01:34:37,240 --> 01:34:41,320 So, if your project uses Docker any developer working on the same project 1905 01:34:41,320 --> 01:34:43,390 is likely also going to be using Docker. 1906 01:34:43,390 --> 01:34:45,820 Whereby instead of running Python manage.py run 1907 01:34:45,820 --> 01:34:49,360 server, which is going to run a server based on whatever libraries 1908 01:34:49,360 --> 01:34:52,690 you have installed on your computer, you will instead 1909 01:34:52,690 --> 01:34:56,410 use something like Docker Compose up to start up the Docker container 1910 01:34:56,410 --> 01:34:58,900 and then run things inside the Docker container. 1911 01:34:58,900 --> 01:35:02,530 And that way if there's something missing from your Docker configuration, 1912 01:35:02,530 --> 01:35:06,640 if you're missing a dependency for instance, it won't run in Docker 1913 01:35:06,640 --> 01:35:09,460 and you'll know that you need to add added dependency, for example, 1914 01:35:09,460 --> 01:35:10,910 to requirements.txt. 1915 01:35:10,910 --> 01:35:14,570 And once you do, then it will behave the same way on someone else's machine. 1916 01:35:14,570 --> 01:35:18,280 Then on the deployment side, all sorts of application Deployment Services 1917 01:35:18,280 --> 01:35:22,450 now are able to use a Docker image and build the application based 1918 01:35:22,450 --> 01:35:23,590 on that Docker file. 1919 01:35:23,590 --> 01:35:27,170 Such that some common ones include Heroku, for instance, 1920 01:35:27,170 --> 01:35:30,460 is a common service for deploying applications to the internet. 1921 01:35:30,460 --> 01:35:34,900 Amazon AWS is also a very popular service for taking web applications 1922 01:35:34,900 --> 01:35:36,020 and deploying them. 1923 01:35:36,020 --> 01:35:37,780 And normally, you might have to worry. 1924 01:35:37,780 --> 01:35:40,090 Do the servers that Amazon's running things on 1925 01:35:40,090 --> 01:35:42,970 have the same version of all the software that I'm running things on? 1926 01:35:42,970 --> 01:35:45,670 There are ways now with Amazon AWS to say, 1927 01:35:45,670 --> 01:35:49,690 let me just give Amazon AWS the Docker configuration for my application 1928 01:35:49,690 --> 01:35:52,720 and now Amazon can use Docker to ensure that when 1929 01:35:52,720 --> 01:35:54,790 they start up the application on their servers, 1930 01:35:54,790 --> 01:35:56,831 that it's going to behave in exactly the same way 1931 01:35:56,831 --> 01:36:00,190 because we defined exactly what should be inside the container. 1932 01:36:00,190 --> 01:36:04,300 And therefore we can be confident that that behavior will be emulated when 1933 01:36:04,300 --> 01:36:07,464 the application is run on the server. 1934 01:36:07,464 --> 01:36:08,360 Yeah. 1935 01:36:08,360 --> 01:36:09,290 AUDIENCE: [INAUDIBLE]? 1936 01:36:09,290 --> 01:36:11,999 1937 01:36:11,999 --> 01:36:13,040 BRIAN YU: Great question. 1938 01:36:13,040 --> 01:36:15,800 Are all of the services running in the same Docker container? 1939 01:36:15,800 --> 01:36:16,370 They're not. 1940 01:36:16,370 --> 01:36:21,800 So in fact, if I go ahead and open up a new tab and I run Docker ps, 1941 01:36:21,800 --> 01:36:24,950 that's going to list for me all of the Docker containers 1942 01:36:24,950 --> 01:36:27,260 that I currently have. 1943 01:36:27,260 --> 01:36:29,690 And I'll just shrink things down so it's all on one line. 1944 01:36:29,690 --> 01:36:34,350 And what you'll notice is that I have two containers that are up right now. 1945 01:36:34,350 --> 01:36:37,700 Here's one container that's based on the airline4 web 1946 01:36:37,700 --> 01:36:41,662 image, which is the image based on the Docker file I showed you a moment ago. 1947 01:36:41,662 --> 01:36:43,370 And then I also have this Postgres image, 1948 01:36:43,370 --> 01:36:45,620 which is going to be the database that I have running. 1949 01:36:45,620 --> 01:36:47,570 So I have two different containers, one that's 1950 01:36:47,570 --> 01:36:50,339 running the actual Django web application 1951 01:36:50,339 --> 01:36:52,130 and one that's running the Postgres server. 1952 01:36:52,130 --> 01:36:54,320 But they're able to communicate with each other. 1953 01:36:54,320 --> 01:36:59,480 And if I wanted to actually get inside of one of the Docker containers 1954 01:36:59,480 --> 01:37:02,030 in order to run commands in it, for instance, 1955 01:37:02,030 --> 01:37:03,690 that would look something like this. 1956 01:37:03,690 --> 01:37:05,810 I could say, Docker exec-- 1957 01:37:05,810 --> 01:37:07,490 meaning run a command-- 1958 01:37:07,490 --> 01:37:11,510 -it to say I want to be interactively working in a terminal. 1959 01:37:11,510 --> 01:37:15,120 Paste in the container ID of the container that I want to get into. 1960 01:37:15,120 --> 01:37:17,870 And then I want to run bash, just run a shell. 1961 01:37:17,870 --> 01:37:23,510 And now you'll see that I'm inside of the container now. 1962 01:37:23,510 --> 01:37:27,650 I'm inside of the container, inside of this user source app directory. 1963 01:37:27,650 --> 01:37:30,380 And now I can do all the same things that I could do before, 1964 01:37:30,380 --> 01:37:34,160 where if I want to go into Python manage.py shell, 1965 01:37:34,160 --> 01:37:38,180 I'm now inside of the shell of the Django application 1966 01:37:38,180 --> 01:37:40,190 that's running inside of this container. 1967 01:37:40,190 --> 01:37:42,470 And so if you ever need to get into a container 1968 01:37:42,470 --> 01:37:45,870 in order to run commands in order to make modifications so on and so forth, 1969 01:37:45,870 --> 01:37:49,350 you can do something like that in order to get access to it as well. 1970 01:37:49,350 --> 01:37:52,820 But the long story short answer to your question is that each of these services 1971 01:37:52,820 --> 01:37:55,269 are running separately in its own container. 1972 01:37:55,269 --> 01:37:56,676 AUDIENCE: [INAUDIBLE]? 1973 01:37:56,676 --> 01:37:59,530 1974 01:37:59,530 --> 01:38:00,530 BRIAN YU: Good question. 1975 01:38:00,530 --> 01:38:03,870 If I was doing development, would I want to go into the container to do it? 1976 01:38:03,870 --> 01:38:08,030 I don't need to get into the container in order to use the web application, 1977 01:38:08,030 --> 01:38:13,850 just to go to the 0.00:8000, because I've linked these ports together. 1978 01:38:13,850 --> 01:38:15,710 But I might want to go into the container 1979 01:38:15,710 --> 01:38:18,300 if I needed to modify the database, for instance. 1980 01:38:18,300 --> 01:38:22,550 If I wanted to manually add a new airport or flight, for instance, 1981 01:38:22,550 --> 01:38:29,300 I couldn't just say, inside of my airline4 directory now, 1982 01:38:29,300 --> 01:38:33,050 I couldn't just say Python manage.py shell 1983 01:38:33,050 --> 01:38:38,660 and go there because this is going to be the shell for the Django application 1984 01:38:38,660 --> 01:38:42,890 that's running on my computer which is different from the database, 1985 01:38:42,890 --> 01:38:45,540 for instance, that I have running inside the Docker container. 1986 01:38:45,540 --> 01:38:47,690 So if I wanted to go into the Docker container 1987 01:38:47,690 --> 01:38:50,240 in order to modify things inside the database, 1988 01:38:50,240 --> 01:38:52,400 then I would need to first enter that container. 1989 01:38:52,400 --> 01:38:54,566 And then I could modify things that are specifically 1990 01:38:54,566 --> 01:38:58,742 happening within that container. 1991 01:38:58,742 --> 01:39:03,080 AUDIENCE: So in this case, you have a full version of the [INAUDIBLE]?? 1992 01:39:03,080 --> 01:39:08,432 1993 01:39:08,432 --> 01:39:09,640 BRIAN YU: Good question, yes. 1994 01:39:09,640 --> 01:39:11,650 So the question is, I have a full version of Postgres 1995 01:39:11,650 --> 01:39:12,941 contained inside the container? 1996 01:39:12,941 --> 01:39:18,190 And yeah, inside of this Postgres container here, this one, 1997 01:39:18,190 --> 01:39:21,280 I have an entire Postgres server running and storing the data 1998 01:39:21,280 --> 01:39:24,820 and Docker knows where things are located and just abstracts all that 1999 01:39:24,820 --> 01:39:25,320 for us. 2000 01:39:25,320 --> 01:39:27,970 We don't need to worry about where exactly this is happening. 2001 01:39:27,970 --> 01:39:32,170 But it's effectively just the same as, whereas in Heroku 2002 01:39:32,170 --> 01:39:36,250 when we set up a database there we were able to access it 2003 01:39:36,250 --> 01:39:38,832 because it was its own database server that was running. 2004 01:39:38,832 --> 01:39:39,790 This is the same thing. 2005 01:39:39,790 --> 01:39:42,581 It's just a database that just happens to be running in a container 2006 01:39:42,581 --> 01:39:44,860 that Docker is running for me on my own computer. 2007 01:39:44,860 --> 01:39:47,350 And because it's following these strict instructions 2008 01:39:47,350 --> 01:39:49,600 for how to create the container based on this image, 2009 01:39:49,600 --> 01:39:52,780 then when someone else tries to create the same container 2010 01:39:52,780 --> 01:39:55,840 they'll be in the same environment and everything will work the same way. 2011 01:39:55,840 --> 01:39:58,690 2012 01:39:58,690 --> 01:40:03,010 OK, so all of these tools of testing, and Travis, 2013 01:40:03,010 --> 01:40:05,440 and other CI software and Docker and such, 2014 01:40:05,440 --> 01:40:08,230 are designed to be part of this CI/CD pipeline. 2015 01:40:08,230 --> 01:40:11,530 Of making it easy to take changes you make to your code, 2016 01:40:11,530 --> 01:40:14,707 integrate them together frequently in order to run automated tests on them 2017 01:40:14,707 --> 01:40:16,540 to make sure that they're working, make sure 2018 01:40:16,540 --> 01:40:18,700 that when you are running those automated tests that they're 2019 01:40:18,700 --> 01:40:20,080 running on the same environment. 2020 01:40:20,080 --> 01:40:22,750 Travis also has ways of integrating with Docker, for instance, 2021 01:40:22,750 --> 01:40:24,905 so that when you're running tests on Travis 2022 01:40:24,905 --> 01:40:27,280 you're making sure that the application running on Travis 2023 01:40:27,280 --> 01:40:29,260 is running in the same environment as the environment 2024 01:40:29,260 --> 01:40:31,480 that you're developing on, or the environment that your application is 2025 01:40:31,480 --> 01:40:32,800 eventually running on. 2026 01:40:32,800 --> 01:40:35,620 And using Docker, that makes this process a whole lot 2027 01:40:35,620 --> 01:40:38,161 easier by making sure we have consistent environments 2028 01:40:38,161 --> 01:40:41,410 for when we're running tests or when we deploy an application to the internet. 2029 01:40:41,410 --> 01:40:44,020 And these are all just good software design practices 2030 01:40:44,020 --> 01:40:45,940 that come in handy for making sure that as we 2031 01:40:45,940 --> 01:40:49,510 begin to work on larger, more complicated web applications, that we 2032 01:40:49,510 --> 01:40:52,179 have the tools in place to make sure that we're avoiding bugs, 2033 01:40:52,179 --> 01:40:54,220 that we're making sure that our application works 2034 01:40:54,220 --> 01:40:57,250 at every stage in the process, and that the deployment process is 2035 01:40:57,250 --> 01:40:59,810 clean and ultimately efficient. 2036 01:40:59,810 --> 01:41:01,280 So we'll wrap up there for today. 2037 01:41:01,280 --> 01:41:02,240 Thank you so much. 2038 01:41:02,240 --> 01:41:05,650 And we'll have some special guests for you next week as well. 2039 01:41:05,650 --> 01:41:07,500 Thank you. 2040 01:41:07,500 --> 01:41:08,302