1 00:00:00,000 --> 00:00:01,860 Let's take a look at some unit testing. 2 00:00:01,870 --> 00:00:05,820 Unit testing allows you to write code to test your assumptions 3 00:00:05,830 --> 00:00:06,870 with your live code. 4 00:00:06,880 --> 00:00:13,430 So, for instance, if we had 'def greeting', and we wanted it 5 00:00:13,440 --> 00:00:17,580 to return "hello" every single time. We could write a test, 6 00:00:17,590 --> 00:00:20,220 an automated test to make sure that it always returns "hello". 7 00:00:20,640 --> 00:00:23,590 Now, that's an overly simplistic example. 8 00:00:23,600 --> 00:00:25,660 But nonetheless, that's important. 9 00:00:25,880 --> 00:00:30,080 So the goal is to really send data to your program, analyze 10 00:00:30,540 --> 00:00:35,250 the results of the program, and see if it gives you the expected 11 00:00:35,250 --> 00:00:38,790 result. So basically, we throw this into, like, a another 12 00:00:38,800 --> 00:00:41,970 program, for instance, and it's going to test 'greeting', and 13 00:00:41,980 --> 00:00:44,220 it's going to make sure that it says, '"hello". 14 00:00:44,280 --> 00:00:46,740 And if it doesn't, it's going to raise some sort of error 15 00:00:46,750 --> 00:00:48,270 and let us know that something's changed. 16 00:00:48,270 --> 00:00:49,590 Our test has broken. 17 00:00:49,860 --> 00:00:52,770 Now, this is actually a really good way to build confidence 18 00:00:52,780 --> 00:00:56,780 in your programs and to help reduce errors that users or 19 00:00:56,790 --> 00:00:59,000 other developers might introduce in the future. 20 00:00:59,010 --> 00:01:01,940 So especially when you're writing code with other people, 21 00:01:02,260 --> 00:01:04,900 you might want to write unit tests because you do not 22 00:01:04,910 --> 00:01:07,330 necessarily know if they're going to be changing your function. 23 00:01:07,330 --> 00:01:13,030 Maybe their function changes from "hello" to returning "hi". 24 00:01:13,750 --> 00:01:17,490 And your unit test is going to say, "No, it's supposed to say 25 00:01:17,500 --> 00:01:18,870 hello. Something broke. 26 00:01:18,880 --> 00:01:20,820 Someone broke something". 27 00:01:21,020 --> 00:01:23,900 So for tests, we're actually going to need to know a little 28 00:01:23,910 --> 00:01:25,550 bit of Object Oriented Programming. 29 00:01:25,560 --> 00:01:28,280 So if you need a little refresher on that, definitely head 30 00:01:28,290 --> 00:01:31,770 back to the OOP section, and give those videos a quick watch 31 00:01:31,780 --> 00:01:34,560 once again, and then feel free to jump back over here. 32 00:01:34,570 --> 00:01:37,530 If you feel like you're good enough with Object Oriented 33 00:01:37,540 --> 00:01:39,240 Programming, let's just jump right into this. 34 00:01:39,320 --> 00:01:43,700 So first, we need to create two scripts, and before I do 35 00:01:43,710 --> 00:01:47,090 that, I'm going to throw this into a folder called 'Unit Testing'. 36 00:01:47,100 --> 00:01:50,300 And in here I'm going to create my first script, and this 37 00:01:50,310 --> 00:01:54,630 one is going to be called 'my_program.py' 38 00:01:54,630 --> 00:01:59,600 And the second one I'm going to call is 'tests.py'. 39 00:01:59,650 --> 00:02:02,490 So I'm going to have a program in here, and all it's going 40 00:02:02,500 --> 00:02:06,080 to do is change some text to make it uppercase. 41 00:02:06,260 --> 00:02:12,240 So I'm going to create a new function, 'make_it_uppercase', 42 00:02:12,699 --> 00:02:15,900 and it's going to take some text, and all it's going to do 43 00:02:15,900 --> 00:02:19,300 is return that 'text.upper()'. 44 00:02:20,100 --> 00:02:24,500 And so the idea here in a Docstring, we could write "ie. 45 00:02:24,500 --> 00:02:29,800 "hello world", and it's going to output "HELLO WORLD" as if it's 46 00:02:29,800 --> 00:02:32,900 yelling at us. Now in our test.py file 47 00:02:32,900 --> 00:02:34,070 we want to import this. 48 00:02:34,080 --> 00:02:40,350 We want to, 'from my_program import', what did I call that? 49 00:02:40,350 --> 00:02:41,800 'make_it_uppercase'. 50 00:02:41,880 --> 00:02:47,220 We also need to 'import unittest' entirely. 51 00:02:47,230 --> 00:02:50,580 We're not going to do 'from unittest import' something. 52 00:02:50,580 --> 00:02:52,900 We're just going to import all of unittest. 53 00:02:52,960 --> 00:02:54,880 So now we need to create a class. 54 00:02:54,880 --> 00:02:58,000 The class is going to be 'TestMyProgram', 55 00:02:58,000 --> 00:03:02,400 that's what it's going to be called, and this uses class inheritance. 56 00:03:02,400 --> 00:03:06,900 So we're going to use 'unittest.TestCase'. 57 00:03:06,900 --> 00:03:10,300 [no audio] 58 00:03:10,300 --> 00:03:13,300 And in here we can write all of our tests. 59 00:03:13,390 --> 00:03:15,690 So we want to start our test with the word 'test'. 60 00:03:15,700 --> 00:03:20,700 So 'test', what are we testing? Let's 'test_hello_world'. 61 00:03:20,700 --> 00:03:26,030 And because this is an object, and this is a method, it always 62 00:03:26,030 --> 00:03:28,280 takes 'self' as its first parameter. 63 00:03:28,500 --> 00:03:34,900 So in here we could say 'text = make_it_uppercase("hello world")'. 64 00:03:34,940 --> 00:03:38,450 And we know that if things are running properly, it will 65 00:03:38,450 --> 00:03:41,600 turn "hello world" into all caps, "HELLO WORLD". 66 00:03:41,640 --> 00:03:44,700 And now we can do this thing called an assertion. 67 00:03:44,940 --> 00:03:49,560 So we can do 'self.assertEqual', 68 00:03:49,560 --> 00:03:51,960 or actually, there's quite a few of them in here, 69 00:03:51,970 --> 00:03:53,130 you can see this with VS code 70 00:03:53,140 --> 00:03:55,560 it's automatically popping this up for me. 71 00:03:55,570 --> 00:03:59,640 So we could do 'assertNotEqual', 'assertLess', 'assertList'. 72 00:03:59,640 --> 00:04:01,800 We could do all sorts of things, but we just want to 73 00:04:01,800 --> 00:04:04,600 'assertEqual', and it's going to take two parameters. 74 00:04:04,610 --> 00:04:07,250 It's going to take our result, 75 00:04:07,260 --> 00:04:10,950 actually let's call this 'result', and it's going to take our 76 00:04:10,980 --> 00:04:13,380 assumption, which is going to be "HELLO WORLD". 77 00:04:13,600 --> 00:04:19,060 Now, at the bottom of our tests, we can do 'if __name__ == 78 00:04:19,070 --> 00:04:25,540 "__main__": unittest.__main__'. 79 00:04:25,550 --> 00:04:29,140 So if this script is being called by Python not as an import, 80 00:04:29,230 --> 00:04:32,920 but as an actual program, automatically take 'unittest' and 81 00:04:32,920 --> 00:04:36,800 run '__main__', and that's going to run our tests here. 82 00:04:36,810 --> 00:04:37,650 So let's go ahead. 83 00:04:38,000 --> 00:04:43,580 'cd../', upper directory, 'cd' into, what did I call this, 'Unit\ Testing/' 84 00:04:43,590 --> 00:04:46,340 'ls -la' or 'dir' if you're on Windows, 85 00:04:46,400 --> 00:04:49,400 and let's do 'python tests.py', 86 00:04:49,460 --> 00:04:51,110 And look at that. 87 00:04:51,120 --> 00:04:53,950 I actually have a little bit of an error here. 88 00:04:53,960 --> 00:04:56,230 'make_it_uppercase' on 'Unit Testing', 89 00:04:56,230 --> 00:04:58,600 'my_program.py', line 2. 90 00:04:58,670 --> 00:05:00,980 And if you're screaming at your screen saying, "Kalob, you 91 00:05:00,990 --> 00:05:02,960 missed your your colon there", good catch. 92 00:05:03,200 --> 00:05:05,840 If not, that's okay, because I didn't catch it either. 93 00:05:06,140 --> 00:05:07,700 Let's go ahead and rerun this. 94 00:05:08,110 --> 00:05:10,670 "Ran 1 test in zero seconds", 95 00:05:10,680 --> 00:05:11,600 that's how fast it is. 96 00:05:11,610 --> 00:05:12,350 And it was okay. 97 00:05:12,580 --> 00:05:16,660 Now let's say someone down the line was working with our 98 00:05:16,670 --> 00:05:18,850 code and they said, "Make it uppercase, 99 00:05:19,060 --> 00:05:22,870 maybe we actually just want to make the first letters upper 100 00:05:22,880 --> 00:05:24,640 case and everything else could be lower case". 101 00:05:24,780 --> 00:05:26,220 So we could use 'title()'. 102 00:05:26,720 --> 00:05:29,370 Let's rerun our test and see what happens. 103 00:05:29,460 --> 00:05:33,810 Because if we run 'title', it's not going to print HELLO WORLD 104 00:05:33,810 --> 00:05:34,900 in all caps anymore. 105 00:05:34,900 --> 00:05:37,500 [no audio] 106 00:05:37,590 --> 00:05:39,000 And look at this. Here it is. 107 00:05:39,480 --> 00:05:41,610 F is for failure. 108 00:05:41,610 --> 00:05:46,000 It failed that 'test_hello_world' on 'TestMyProgram'. 109 00:05:46,020 --> 00:05:46,800 That's over here. 110 00:05:46,800 --> 00:05:48,100 'TestMyProgram'. 111 00:05:48,100 --> 00:05:51,300 There's a trace back, which is going to give you all sorts of details. 112 00:05:51,300 --> 00:05:57,400 So in 'test.py' on line 9, 'test.py', line 9, 113 00:05:57,400 --> 00:06:00,100 [no audio] 114 00:06:00,100 --> 00:06:01,700 the assertEqual' failed, 115 00:06:01,700 --> 00:06:04,340 and it was looking for this, 116 00:06:04,350 --> 00:06:06,290 'Hello World!' != 'HELLO WORLD'', 117 00:06:06,350 --> 00:06:07,310 one is all caps, 118 00:06:07,400 --> 00:06:11,700 one just has the first letter capitalized of each word. 119 00:06:11,700 --> 00:06:14,300 What it got, what it was expecting. 120 00:06:14,340 --> 00:06:17,400 And so now we have a failure in here. 121 00:06:17,580 --> 00:06:21,000 Now let's go ahead and create another test. 122 00:06:21,340 --> 00:06:24,040 Let's do one a little more complicated. 123 00:06:24,050 --> 00:06:29,450 Let's do 'def get_first_word()' from a sentence, and it's going 124 00:06:29,460 --> 00:06:32,790 to take a 'sentence', and all it's going to do is get the first 125 00:06:32,800 --> 00:06:40,400 word. So let's try something here, 'return sentence.split()' 126 00:06:41,320 --> 00:06:46,030 on a space, and let's return the first one. 127 00:06:46,040 --> 00:06:47,680 Because we know that this is going to be a list, 128 00:06:47,690 --> 00:06:51,340 we can just automatically return the first one. If you want 129 00:06:51,350 --> 00:06:54,510 to you can always split this on two different lines as well. 130 00:06:54,520 --> 00:06:58,410 You could say something like 'words =', and then 131 00:06:58,420 --> 00:07:00,120 you could just return the first word. 132 00:07:00,440 --> 00:07:02,000 And that's okay, too. 133 00:07:02,000 --> 00:07:04,310 So let's go ahead and create another test in here. 134 00:07:04,380 --> 00:07:09,970 First, we need to import this, 'get_first_word'. Let's go ahead and 135 00:07:09,970 --> 00:07:13,600 import a second import here from that same file called 'my_program' 136 00:07:13,600 --> 00:07:19,400 'def test_first_word', and it's going to take 'self'. 137 00:07:19,980 --> 00:07:24,640 Now let's go ahead and say the 'sentence' is going to be is 138 00:07:24,790 --> 00:07:26,290 some 'Lorem Ipsum' stuff. 139 00:07:26,920 --> 00:07:30,370 And so it's a fairly long sentence, and we are expecting 140 00:07:30,370 --> 00:07:32,800 it to return just this word. 141 00:07:32,820 --> 00:07:36,450 So now we can take that 'sentence', apply it. 142 00:07:36,460 --> 00:07:39,850 So 'result = get_first_word', throw the 'sentence' 143 00:07:39,860 --> 00:07:49,460 in there, and we can 'self.assertEqual(result)', and 144 00:07:49,460 --> 00:07:52,800 that 'result' what we're expecting is supposed to be that first word, 145 00:07:52,800 --> 00:07:56,400 exactly that first word, not uppercase, not lowercase, just the 146 00:07:56,400 --> 00:07:58,800 first word. Now we can go ahead and run this. 147 00:07:58,800 --> 00:08:01,200 [no audio] 148 00:08:01,200 --> 00:08:02,200 Let's try this again. 149 00:08:02,200 --> 00:08:07,530 And I have run two tests in here in 0.001 seconds, and one failure, 150 00:08:07,540 --> 00:08:09,480 and that was because I didn't change that one back. 151 00:08:10,050 --> 00:08:13,570 Let's rerun this and there are no problems. 152 00:08:13,580 --> 00:08:15,280 Let's make this just a tad bigger here. 153 00:08:15,380 --> 00:08:19,100 So this is doing exactly what we thought it would do. 154 00:08:19,110 --> 00:08:23,910 What if, however, let's say this isn't 'test_first_word', this 155 00:08:23,910 --> 00:08:27,800 is going to be 'test_first_word_in_sentence()'. 156 00:08:27,860 --> 00:08:29,480 Now a unit test 157 00:08:29,490 --> 00:08:32,600 we have to try to figure out all these other different use 158 00:08:32,600 --> 00:08:38,500 cases. So let's say 'test_first_word_in_sentence_with_one_word', 159 00:08:38,500 --> 00:08:41,700 and we're only going to provide one word in here, just 160 00:08:41,700 --> 00:08:42,799 the word "Lorem". 161 00:08:42,799 --> 00:08:46,100 And what we're testing here is to see if this fails. 162 00:08:46,140 --> 00:08:49,950 So let's go ahead and run this once more. 163 00:08:51,260 --> 00:08:52,260 Cool. 164 00:08:52,530 --> 00:08:53,370 So that was okay. 165 00:08:54,380 --> 00:08:59,190 But what if, for whatever reason, I just forgot to return 166 00:08:59,200 --> 00:09:04,500 the first word, and I just returned the whole list? Again, 167 00:09:04,500 --> 00:09:05,610 just read that 'Traceback', 168 00:09:05,620 --> 00:09:09,480 and it's going to say in, file "test.py" on line 19 with this 169 00:09:09,480 --> 00:09:10,700 long function here, 170 00:09:10,700 --> 00:09:16,400 what it got was a list with 'Lorem' as the first item compared to a string. 171 00:09:16,460 --> 00:09:19,400 So either the test was wrong or the function was wrong. 172 00:09:19,580 --> 00:09:22,790 Okay, let's try one more just for funsies. 173 00:09:22,980 --> 00:09:24,810 Let's try something that's not a string. 174 00:09:24,820 --> 00:09:27,720 Let's create a new function in here, and this one is just 175 00:09:27,730 --> 00:09:30,950 going to 'return_a_list', and it's not going to take anything, 176 00:09:30,960 --> 00:09:32,710 because I want this to be a faster example. 177 00:09:32,710 --> 00:09:40,500 And all this is going to do is return a list of ['Cats', 'Dogs', Birds']. 178 00:09:40,500 --> 00:09:42,900 [no audio] 179 00:09:42,900 --> 00:09:45,400 And we can write another test in here to make sure 180 00:09:45,460 --> 00:09:47,010 that that is what it's supposed to be. 181 00:09:47,100 --> 00:09:50,500 So let's grab this and just copy that. 182 00:09:50,500 --> 00:09:53,400 [no audio] 183 00:09:53,400 --> 00:09:56,800 Go over here and paste it. 184 00:09:57,200 --> 00:10:02,400 So we're importing all three functions from 'my_program,py', 185 00:10:04,100 --> 00:10:05,200 and let's test the list. 186 00:10:05,250 --> 00:10:06,570 Let's do a test, 187 00:10:07,260 --> 00:10:10,710 return a list, always take 'self' because it's in an object, 188 00:10:10,980 --> 00:10:15,120 this object that's inheriting from 'unittest.TestCase'. 189 00:10:15,680 --> 00:10:21,930 And we want to make sure that the list, let's call it 'result'. 190 00:10:21,990 --> 00:10:23,220 I like calling it 'result', 191 00:10:23,230 --> 00:10:24,570 it doesn't have to be called 'result'. 192 00:10:24,620 --> 00:10:28,310 So just make sure 'result = return_a_list', 193 00:10:28,400 --> 00:10:32,200 and we know that that is supposed to return this, 194 00:10:32,530 --> 00:10:35,360 and we can try a different assert. 195 00:10:35,540 --> 00:10:39,750 We can do 'self.assertListEqual', 196 00:10:40,560 --> 00:10:43,600 and you can actually see VS Code is being nice here. 197 00:10:43,610 --> 00:10:46,710 It's saying the first item has to be a list, any type of 198 00:10:46,720 --> 00:10:49,320 list. The second item has to be a list, any sort of message. 199 00:10:49,440 --> 00:10:51,030 So the 'result' is the first one 200 00:10:51,040 --> 00:10:53,280 we're going to compare against the second one here. 201 00:10:53,690 --> 00:10:57,030 And if it doesn't work out, let's give it an error message. 202 00:10:57,600 --> 00:11:01,000 "THE LIST WAS WRONG". 203 00:11:01,090 --> 00:11:04,310 And let's put these on separate lines just to keep this a 204 00:11:04,310 --> 00:11:05,400 little cleaner. 205 00:11:05,400 --> 00:11:07,800 Let's go ahead and run this test. 206 00:11:07,800 --> 00:11:10,000 [no audio] 207 00:11:10,000 --> 00:11:11,200 Hey, cool. That worked. 208 00:11:11,420 --> 00:11:15,680 What if somewhere down the road, someone said, "We're gonna 209 00:11:15,680 --> 00:11:19,700 change 'Cats', to 'Kitties', and 'Dogs' 210 00:11:19,700 --> 00:11:23,500 to 'Doggos' and 'Birds' are just going to be 'Birds'. 211 00:11:23,500 --> 00:11:25,700 [no audio] 212 00:11:25,700 --> 00:11:26,600 Let's go ahead and run this. 213 00:11:26,720 --> 00:11:29,720 Now we're going to see a different type of error. 214 00:11:29,940 --> 00:11:33,540 We're seeing that this list does not match the expected list. 215 00:11:33,540 --> 00:11:34,800 So this is what we got, 216 00:11:34,840 --> 00:11:37,660 and this is what the 'unittest' was expecting. 217 00:11:37,880 --> 00:11:41,330 It even tells us 'First differing element at 0'. 218 00:11:41,510 --> 00:11:44,370 'Kitties'. Was 'Kitties' and 'Cats'. 219 00:11:44,400 --> 00:11:47,700 So I thought, 'Kitties' and 'Cats' are not the same. 220 00:11:47,700 --> 00:11:48,900 It's right. It's not the same. 221 00:11:49,060 --> 00:11:51,820 So we could go ahead and we could change that back. 222 00:11:52,290 --> 00:11:55,120 And this one is now going to complain about Doggos'. 223 00:11:55,860 --> 00:11:58,600 'First differing element is 'Doggos', and 'Dogs'. 224 00:11:58,660 --> 00:11:59,920 Okay, let's change that one back. 225 00:11:59,930 --> 00:12:05,000 That one's supposed to be 'Dogs', and voila. 226 00:12:05,200 --> 00:12:09,200 We have four 'unittest' up and running the way we expect them to. 227 00:12:09,200 --> 00:12:10,600 [no audio]