1 00:00:00,480 --> 00:00:03,480 foreign 2 00:00:10,099 --> 00:00:15,680 it's my pleasure to introduce Peter from 3 00:00:13,440 --> 00:00:19,380 the Australian energy Market operator 4 00:00:15,680 --> 00:00:23,779 with 6qa tall QA tools that'll make your 5 00:00:19,380 --> 00:00:23,779 python code better take it away Peter 6 00:00:24,720 --> 00:00:27,619 thank you and welcome 7 00:00:27,720 --> 00:00:31,980 who's this talk for 8 00:00:29,880 --> 00:00:34,739 the Amo team in this case study right 9 00:00:31,980 --> 00:00:36,960 critical Enterprise code that executes 10 00:00:34,739 --> 00:00:41,340 on a lock lockdown serves in the cool 11 00:00:36,960 --> 00:00:43,620 room we are very very conservative 12 00:00:41,340 --> 00:00:47,040 I'll be advocating you spend about 45 13 00:00:43,620 --> 00:00:49,500 percent of your Dev Budget on code QA 14 00:00:47,040 --> 00:00:51,420 this may not be for you but we'll 15 00:00:49,500 --> 00:00:54,539 proceed anyway 16 00:00:51,420 --> 00:00:56,340 before I start I have a question and the 17 00:00:54,539 --> 00:00:59,399 question is a question 18 00:00:56,340 --> 00:01:01,680 what is the hardest question for a coder 19 00:00:59,399 --> 00:01:03,899 to answer 20 00:01:01,680 --> 00:01:05,880 give some thought to that although 21 00:01:03,899 --> 00:01:09,860 scroll through the next few slides and 22 00:01:05,880 --> 00:01:09,860 we'll talk about that more in a moment 23 00:01:10,080 --> 00:01:14,280 first of all can you spot the bug in 24 00:01:12,180 --> 00:01:16,020 this code 25 00:01:14,280 --> 00:01:18,299 probably not a fair question because you 26 00:01:16,020 --> 00:01:21,540 haven't seen the context but you can see 27 00:01:18,299 --> 00:01:25,100 that it opens a text file it 28 00:01:21,540 --> 00:01:28,500 iterates over it it pulls out gosh 29 00:01:25,100 --> 00:01:30,540 column number six it adds the value to a 30 00:01:28,500 --> 00:01:32,820 list it sorts it and it prints it out 31 00:01:30,540 --> 00:01:36,240 what's the whole point of that well 32 00:01:32,820 --> 00:01:38,939 here's the underlying data this is a 33 00:01:36,240 --> 00:01:40,920 typical Market data that we deal with at 34 00:01:38,939 --> 00:01:44,640 work it's available on the public 35 00:01:40,920 --> 00:01:47,460 internet and it shows the generation in 36 00:01:44,640 --> 00:01:50,040 megawatts against each generator in the 37 00:01:47,460 --> 00:01:51,899 nem at a point in time the point in time 38 00:01:50,040 --> 00:01:55,259 we do this every five minutes 39 00:01:51,899 --> 00:01:58,380 The Columns of Interest are f 40 00:01:55,259 --> 00:02:01,200 a value we call do it or dispatch unit 41 00:01:58,380 --> 00:02:05,579 ID we call a generator A dispatch unit 42 00:02:01,200 --> 00:02:07,560 and column G scada value which is the 43 00:02:05,579 --> 00:02:09,239 megawatts of that unit is producing at 44 00:02:07,560 --> 00:02:11,520 that point in time 45 00:02:09,239 --> 00:02:14,160 you can often reverse engineer where the 46 00:02:11,520 --> 00:02:16,400 unit is in the country by looking at the 47 00:02:14,160 --> 00:02:16,400 name 48 00:02:16,459 --> 00:02:22,379 c-a-p-t-l underscore WF for example on 49 00:02:19,800 --> 00:02:25,200 row whatever it is six is Capitol Hill 50 00:02:22,379 --> 00:02:27,300 Wind Farm outside Canberra 51 00:02:25,200 --> 00:02:30,200 eyeballing the data you can see the 52 00:02:27,300 --> 00:02:33,360 lowest value is around about negative 53 00:02:30,200 --> 00:02:35,819 0.02 and the highest value is around 54 00:02:33,360 --> 00:02:37,440 about 620 55 00:02:35,819 --> 00:02:39,599 back to our code 56 00:02:37,440 --> 00:02:42,000 it opens the file 57 00:02:39,599 --> 00:02:44,400 it iterates that it pulls out column six 58 00:02:42,000 --> 00:02:46,019 it adds it to It List It sorts it and it 59 00:02:44,400 --> 00:02:47,879 prints out what we think going to be the 60 00:02:46,019 --> 00:02:49,500 first value and the last value the 61 00:02:47,879 --> 00:02:52,620 minimum megawatts and the maximum 62 00:02:49,500 --> 00:02:54,739 megawatts and we get this 63 00:02:52,620 --> 00:02:58,379 minus 64 00:02:54,739 --> 00:03:00,120 0.0015 and 97. 65 00:02:58,379 --> 00:03:02,580 the answer's wrong 66 00:03:00,120 --> 00:03:04,379 so where's the bug 67 00:03:02,580 --> 00:03:06,120 this is perhaps the worst sort of bug 68 00:03:04,379 --> 00:03:07,739 you can get because it executes it 69 00:03:06,120 --> 00:03:09,900 produces a number but the number's 70 00:03:07,739 --> 00:03:12,420 absolute nonsense 71 00:03:09,900 --> 00:03:16,819 so what we need is a crutch to lean on 72 00:03:12,420 --> 00:03:16,819 to help us not make this error 73 00:03:17,840 --> 00:03:24,780 let's get a bit smart now we'll 74 00:03:20,519 --> 00:03:26,640 introduce a class structure to help us 75 00:03:24,780 --> 00:03:28,620 class structure to lean on or a 76 00:03:26,640 --> 00:03:31,920 different coding style to help us 77 00:03:28,620 --> 00:03:34,739 avoid that error down the track and of 78 00:03:31,920 --> 00:03:37,140 course the error was that we're adding 79 00:03:34,739 --> 00:03:39,060 our numeric values as a string to a list 80 00:03:37,140 --> 00:03:41,099 and sorting is a string and strings are 81 00:03:39,060 --> 00:03:43,019 numbers sort differently so we're 82 00:03:41,099 --> 00:03:44,879 getting the maximum and minimum value 83 00:03:43,019 --> 00:03:45,900 based on the fact that it's a string not 84 00:03:44,879 --> 00:03:48,299 a number 85 00:03:45,900 --> 00:03:49,860 let's inject some code that forces it to 86 00:03:48,299 --> 00:03:53,700 be a number 87 00:03:49,860 --> 00:03:57,060 so I've created a class line eight in 88 00:03:53,700 --> 00:04:00,599 the code called genval it has two 89 00:03:57,060 --> 00:04:03,420 attributes do it and scada value do the 90 00:04:00,599 --> 00:04:07,440 string scalar value is a number it has a 91 00:04:03,420 --> 00:04:09,360 Dunder LT method or a magic python 92 00:04:07,440 --> 00:04:12,659 method that will help us sort this class 93 00:04:09,360 --> 00:04:16,440 where it compares the two scada values 94 00:04:12,659 --> 00:04:19,400 it does a generation line 21 creates an 95 00:04:16,440 --> 00:04:23,699 instance of the class line 22 96 00:04:19,400 --> 00:04:25,320 sets the do it line 23 sets the value it 97 00:04:23,699 --> 00:04:27,060 sets it as a decimal so we're getting 98 00:04:25,320 --> 00:04:31,259 smarter now 99 00:04:27,060 --> 00:04:33,600 it does its sort and it prints the 100 00:04:31,259 --> 00:04:35,759 maximum and minimum value 101 00:04:33,600 --> 00:04:39,259 but it's still wrong 102 00:04:35,759 --> 00:04:43,199 like can you spot the error in this code 103 00:04:39,259 --> 00:04:45,419 again probably not a fair question but 104 00:04:43,199 --> 00:04:47,759 where we work scada value is a value 105 00:04:45,419 --> 00:04:50,220 that's used all over the organization in 106 00:04:47,759 --> 00:04:51,540 many systems and it's it's a word that 107 00:04:50,220 --> 00:04:53,759 we hold in our heads and we probably 108 00:04:51,540 --> 00:04:56,340 translate it without even thinking about 109 00:04:53,759 --> 00:04:59,400 it from one form to the other so line 11 110 00:04:56,340 --> 00:05:01,979 is scada value line 111 00:04:59,400 --> 00:05:04,259 23 where assigning it is scada 112 00:05:01,979 --> 00:05:05,880 underscore value and of course beautiful 113 00:05:04,259 --> 00:05:08,400 infinitely 114 00:05:05,880 --> 00:05:10,139 elastic permissive python will just 115 00:05:08,400 --> 00:05:12,780 swallow that and will create scada 116 00:05:10,139 --> 00:05:16,199 underscore value as an attribute on our 117 00:05:12,780 --> 00:05:18,120 class without complaining 118 00:05:16,199 --> 00:05:20,720 so we need a crutch to prevent us from 119 00:05:18,120 --> 00:05:20,720 that error 120 00:05:21,120 --> 00:05:26,460 more code 121 00:05:23,460 --> 00:05:28,320 Can you spot the area in this code 122 00:05:26,460 --> 00:05:30,900 again these are three areas that I have 123 00:05:28,320 --> 00:05:34,020 made a hundred times and there areas my 124 00:05:30,900 --> 00:05:36,360 team has made a hundred times 125 00:05:34,020 --> 00:05:38,400 the error in this one is a result of 126 00:05:36,360 --> 00:05:42,060 copying and pasting the headers from a 127 00:05:38,400 --> 00:05:44,160 CSV into my code entering some Carriage 128 00:05:42,060 --> 00:05:47,280 returns and then reformatting it so I'm 129 00:05:44,160 --> 00:05:49,860 mapping CSV columns into object 130 00:05:47,280 --> 00:05:52,139 attributes 131 00:05:49,860 --> 00:05:56,400 and invertently left a trailing comma on 132 00:05:52,139 --> 00:05:58,680 line 23. now beautiful permissive python 133 00:05:56,400 --> 00:06:00,960 will just swallow that except it will 134 00:05:58,680 --> 00:06:04,259 set our scada value to be a tuple with 135 00:06:00,960 --> 00:06:05,940 one value in it rather than the actual 136 00:06:04,259 --> 00:06:07,919 value itself 137 00:06:05,940 --> 00:06:10,080 I've changed the context of the code 138 00:06:07,919 --> 00:06:11,880 slightly it's now not looking for the 139 00:06:10,080 --> 00:06:15,300 maximum minimum values but it's trying 140 00:06:11,880 --> 00:06:16,080 to sum the generation available at that 141 00:06:15,300 --> 00:06:19,020 um 142 00:06:16,080 --> 00:06:21,960 uh time period and of course this code 143 00:06:19,020 --> 00:06:25,319 will actually blow up on line 28 because 144 00:06:21,960 --> 00:06:26,819 it's trying to add a tuple to a number 145 00:06:25,319 --> 00:06:29,580 this is 146 00:06:26,819 --> 00:06:31,139 this is a less worse error than those 147 00:06:29,580 --> 00:06:34,800 previous ones because our code won't 148 00:06:31,139 --> 00:06:37,680 execute that's a good thing 149 00:06:34,800 --> 00:06:40,259 anyway we've had three hard to spot bugs 150 00:06:37,680 --> 00:06:42,240 in Trivial code 151 00:06:40,259 --> 00:06:44,880 these bugs would never have occurred in 152 00:06:42,240 --> 00:06:47,940 a compiled language like Pascal which is 153 00:06:44,880 --> 00:06:50,280 my background or C or C sharp as the 154 00:06:47,940 --> 00:06:54,000 compiler would have caught them 155 00:06:50,280 --> 00:06:56,160 what happens when the body of code grows 156 00:06:54,000 --> 00:06:59,880 what happens when the number of code is 157 00:06:56,160 --> 00:07:01,560 on your team grows from one to n and 158 00:06:59,880 --> 00:07:04,100 what happens if you're simply having a 159 00:07:01,560 --> 00:07:04,100 bad day 160 00:07:05,759 --> 00:07:09,660 back to the question at the start of the 161 00:07:07,620 --> 00:07:12,240 talk 162 00:07:09,660 --> 00:07:14,220 what is the hardest question for a coder 163 00:07:12,240 --> 00:07:16,080 to answer 164 00:07:14,220 --> 00:07:17,400 in my experience 165 00:07:16,080 --> 00:07:19,440 it's this 166 00:07:17,400 --> 00:07:21,539 are you done 167 00:07:19,440 --> 00:07:23,639 the answer always comes with a caveat 168 00:07:21,539 --> 00:07:25,620 yes I'm done but I haven't finished the 169 00:07:23,639 --> 00:07:28,199 unit test yes I'm done but I have some 170 00:07:25,620 --> 00:07:30,419 documentation to write yes I'm done but 171 00:07:28,199 --> 00:07:32,759 oh my goodness I have to refactor this 172 00:07:30,419 --> 00:07:37,560 module over to the side 173 00:07:32,759 --> 00:07:40,080 what we want is an objective unambiguous 174 00:07:37,560 --> 00:07:41,699 definition of done 175 00:07:40,080 --> 00:07:43,860 something that we all agree to see 176 00:07:41,699 --> 00:07:44,639 something is all your team members agree 177 00:07:43,860 --> 00:07:48,620 to 178 00:07:44,639 --> 00:07:48,620 to allow this question to be answered 179 00:07:49,979 --> 00:07:53,940 about these six and they're going to 180 00:07:52,259 --> 00:07:54,720 help us answer that question are you 181 00:07:53,940 --> 00:07:57,360 done 182 00:07:54,720 --> 00:07:59,759 the tools that I saw black Pile in my 183 00:07:57,360 --> 00:08:01,620 pipe Pi test and coverage we're going to 184 00:07:59,759 --> 00:08:04,259 look at each one and look at the changes 185 00:08:01,620 --> 00:08:05,639 those tools will make to the code as you 186 00:08:04,259 --> 00:08:07,800 execute them 187 00:08:05,639 --> 00:08:09,539 I thought as the first one and it's the 188 00:08:07,800 --> 00:08:12,479 simplest this is the blurb of the 189 00:08:09,539 --> 00:08:14,639 eyesort website I saw it as a python 190 00:08:12,479 --> 00:08:16,500 utility or library to sort Imports 191 00:08:14,639 --> 00:08:18,539 alphabetically and automatically 192 00:08:16,500 --> 00:08:21,900 separate them into sections by type 193 00:08:18,539 --> 00:08:24,479 that's all a bit ho-hum you can 194 00:08:21,900 --> 00:08:26,099 implement the same behavior as I sort 195 00:08:24,479 --> 00:08:29,220 will give you with Control Alt something 196 00:08:26,099 --> 00:08:30,720 other in your IDE 197 00:08:29,220 --> 00:08:33,779 um 198 00:08:30,720 --> 00:08:35,399 it will sort your imported or get rid of 199 00:08:33,779 --> 00:08:37,979 the ones that aren't used it will 200 00:08:35,399 --> 00:08:39,779 eliminate git noise that's the main 201 00:08:37,979 --> 00:08:41,580 reason for use this you're not going to 202 00:08:39,779 --> 00:08:44,039 get any git conflicts in your import 203 00:08:41,580 --> 00:08:47,360 section when you've used this install 204 00:08:44,039 --> 00:08:47,360 use it and move on 205 00:08:47,640 --> 00:08:50,820 black takes over your life a little bit 206 00:08:49,740 --> 00:08:53,000 more 207 00:08:50,820 --> 00:08:56,399 black is the uncompromising 208 00:08:53,000 --> 00:08:58,680 uncompromising python code formator by 209 00:08:56,399 --> 00:09:01,440 using it you agree to see control over 210 00:08:58,680 --> 00:09:04,320 the minutia of hand formatting in return 211 00:09:01,440 --> 00:09:06,480 black gives you speed certainty and 212 00:09:04,320 --> 00:09:08,880 freedom from get noise that git thing 213 00:09:06,480 --> 00:09:10,980 again black will save you time and 214 00:09:08,880 --> 00:09:14,300 mental energy freeing you for more 215 00:09:10,980 --> 00:09:16,500 important matters 216 00:09:14,300 --> 00:09:20,040 let's have a look at some code before 217 00:09:16,500 --> 00:09:21,660 and after we execute black 218 00:09:20,040 --> 00:09:24,959 so our little fragment of code that 219 00:09:21,660 --> 00:09:26,640 parses that text file with the generator 220 00:09:24,959 --> 00:09:28,920 class has been run through black and 221 00:09:26,640 --> 00:09:30,660 it's done two things to it it's injected 222 00:09:28,920 --> 00:09:33,839 a blank line ahead of the class 223 00:09:30,660 --> 00:09:37,320 definition that's pep eight thing it's a 224 00:09:33,839 --> 00:09:39,959 bit ho-hum but it will solve get noise 225 00:09:37,320 --> 00:09:42,899 and it has reformatted the dunder string 226 00:09:39,959 --> 00:09:45,839 method that I've added to my class I 227 00:09:42,899 --> 00:09:48,000 started off with a long uh 228 00:09:45,839 --> 00:09:50,339 long line of code that spanned I don't 229 00:09:48,000 --> 00:09:51,779 know 80 90 characters and black has 230 00:09:50,339 --> 00:09:54,060 wrapped it for me and it's wrapped it 231 00:09:51,779 --> 00:09:56,700 for me in a consistent way 232 00:09:54,060 --> 00:09:58,140 a little bit ho-hum perhaps but again it 233 00:09:56,700 --> 00:10:00,899 will get rid of git noise and it will 234 00:09:58,140 --> 00:10:03,899 stop arguments over formatting Styles 235 00:10:00,899 --> 00:10:06,180 within your team it also allows you as 236 00:10:03,899 --> 00:10:07,980 your coding to just type any old 237 00:10:06,180 --> 00:10:10,320 nonsense run it get the thing working 238 00:10:07,980 --> 00:10:12,779 and then hit black and it's formatted 239 00:10:10,320 --> 00:10:14,040 for you so it actually it does save a 240 00:10:12,779 --> 00:10:16,320 lot of time because you don't have to 241 00:10:14,040 --> 00:10:18,360 stress over do I enter a carriage return 242 00:10:16,320 --> 00:10:20,540 here or should I be continuing on the 243 00:10:18,360 --> 00:10:20,540 line 244 00:10:20,700 --> 00:10:25,440 again black a little bit ho-hum install 245 00:10:22,860 --> 00:10:27,180 it use it move on 246 00:10:25,440 --> 00:10:28,500 piling is starting to get a bit more 247 00:10:27,180 --> 00:10:31,880 interesting 248 00:10:28,500 --> 00:10:35,160 piling has a is a python static code 249 00:10:31,880 --> 00:10:37,740 analysis tool which looks for form looks 250 00:10:35,160 --> 00:10:40,080 for programming errors it helps enforce 251 00:10:37,740 --> 00:10:43,380 coding standards it sniffs for code 252 00:10:40,080 --> 00:10:46,220 smells and it offers simple refactoring 253 00:10:43,380 --> 00:10:46,220 suggestions 254 00:10:46,320 --> 00:10:51,480 now a good ide will give you isort black 255 00:10:49,260 --> 00:10:55,260 and pilint support 256 00:10:51,480 --> 00:10:57,420 this is part charm and you can see line 257 00:10:55,260 --> 00:11:00,779 six there's a squiggly yellow line under 258 00:10:57,420 --> 00:11:04,220 my class definition if I hover the mouse 259 00:11:00,779 --> 00:11:07,260 it tells me pep 8. 260 00:11:04,220 --> 00:11:10,260 e302 expected two blank lines one found 261 00:11:07,260 --> 00:11:12,480 well that's easy to fix and it tells you 262 00:11:10,260 --> 00:11:14,640 another one class names should use camel 263 00:11:12,480 --> 00:11:16,920 case convention okay that's a little bit 264 00:11:14,640 --> 00:11:20,640 harder to fix you've got to control or 265 00:11:16,920 --> 00:11:22,620 whatever the rename is in pycharm and 266 00:11:20,640 --> 00:11:24,860 change that class name all through your 267 00:11:22,620 --> 00:11:24,860 code 268 00:11:25,459 --> 00:11:30,060 pilint will run in the IDE as I just 269 00:11:28,079 --> 00:11:33,420 showed you but also run as a batch 270 00:11:30,060 --> 00:11:35,339 script outside the IDE and we do both on 271 00:11:33,420 --> 00:11:37,980 our project pilot will give an error 272 00:11:35,339 --> 00:11:40,200 message like this it's the file name 273 00:11:37,980 --> 00:11:42,899 followed by the line number and the 274 00:11:40,200 --> 00:11:45,000 column number you can copy that to the 275 00:11:42,899 --> 00:11:46,800 clipboard and control alt something or 276 00:11:45,000 --> 00:11:49,019 other in pycharm and it'll take you to 277 00:11:46,800 --> 00:11:53,940 the exact location it gives you a 278 00:11:49,019 --> 00:11:56,279 numeric warning a message ID it gives 279 00:11:53,940 --> 00:11:58,440 you a human readable message description 280 00:11:56,279 --> 00:12:01,140 and then it gives you a text-based 281 00:11:58,440 --> 00:12:03,779 message ID also you can use that 282 00:12:01,140 --> 00:12:06,000 text-based message ID to suppress this 283 00:12:03,779 --> 00:12:08,660 form of Warning by inserting a comment 284 00:12:06,000 --> 00:12:08,660 into your code 285 00:12:10,019 --> 00:12:16,019 messages from our sample code 286 00:12:12,899 --> 00:12:19,920 the first one trailing new lines to bit 287 00:12:16,019 --> 00:12:23,160 ho-hum the second one disallowed 288 00:12:19,920 --> 00:12:25,740 trailing comma Tuple that's interesting 289 00:12:23,160 --> 00:12:28,019 that's a real bug that pilant has 290 00:12:25,740 --> 00:12:29,700 spotted for us that that absolutely 291 00:12:28,019 --> 00:12:32,160 needs to be fixed 292 00:12:29,700 --> 00:12:35,279 uh the next one redefining a built-in 293 00:12:32,160 --> 00:12:37,079 sum okay so the word sum is a python 294 00:12:35,279 --> 00:12:39,180 Library thing and I have a variable that 295 00:12:37,079 --> 00:12:42,180 clashes with that so that should be tied 296 00:12:39,180 --> 00:12:44,760 it up constant name input underscore 297 00:12:42,180 --> 00:12:48,240 file doesn't conform to uppercase naming 298 00:12:44,760 --> 00:12:51,180 style ho-hum but let's fix it missing 299 00:12:48,240 --> 00:12:54,860 doc string we have a rule on our project 300 00:12:51,180 --> 00:12:57,000 that all classes must have documentation 301 00:12:54,860 --> 00:12:58,800 pilot wants a higher level of 302 00:12:57,000 --> 00:13:01,800 documentation than that but we've wound 303 00:12:58,800 --> 00:13:04,200 that back to only requiring 304 00:13:01,800 --> 00:13:05,459 documentation for classes and there's a 305 00:13:04,200 --> 00:13:06,600 couple of other ones that are getting 306 00:13:05,459 --> 00:13:07,339 into the 307 00:13:06,600 --> 00:13:10,139 um 308 00:13:07,339 --> 00:13:12,180 uh semantic thing rather than hardcore 309 00:13:10,139 --> 00:13:14,459 bugs but we have a policy of tidying 310 00:13:12,180 --> 00:13:16,980 them up anyway because that noise could 311 00:13:14,459 --> 00:13:20,839 be hiding something like our trailing 312 00:13:16,980 --> 00:13:20,839 comma that actually matters 313 00:13:21,899 --> 00:13:27,240 progress between runs with this message 314 00:13:24,540 --> 00:13:30,740 here your code has been rated 10 out of 315 00:13:27,240 --> 00:13:30,740 10. previous run 316 00:13:31,700 --> 00:13:38,339 99.98 out of 10. it kind of gamifies the 317 00:13:35,579 --> 00:13:41,040 quality process so our team members 318 00:13:38,339 --> 00:13:45,540 collectively get this wonderful surge of 319 00:13:41,040 --> 00:13:48,540 happy hormones as our as our progress 320 00:13:45,540 --> 00:13:51,000 tracks put towards 10 out of 10 or 100 321 00:13:48,540 --> 00:13:52,980 percent 322 00:13:51,000 --> 00:13:53,940 here's a fragment of code before and 323 00:13:52,980 --> 00:13:55,800 after 324 00:13:53,940 --> 00:13:57,300 pilot 325 00:13:55,800 --> 00:14:00,180 before and after the human intervention 326 00:13:57,300 --> 00:14:02,399 to correct pylent errors again bit 327 00:14:00,180 --> 00:14:05,399 ho-hum I've changed the name of the 328 00:14:02,399 --> 00:14:08,040 input file variable to conform with pep 329 00:14:05,399 --> 00:14:11,100 8 all uppercase I've changed my class 330 00:14:08,040 --> 00:14:14,720 name to Pascal case and I've inserted a 331 00:14:11,100 --> 00:14:14,720 comma that comma a comment 332 00:14:15,120 --> 00:14:19,320 pilint allows you to write custom 333 00:14:17,100 --> 00:14:21,959 linters and we do this quite a bit on 334 00:14:19,320 --> 00:14:24,660 our project too one of our staffing 335 00:14:21,959 --> 00:14:26,820 patterns is that we rotate our power 336 00:14:24,660 --> 00:14:29,339 system engineer graduates through the 337 00:14:26,820 --> 00:14:31,860 team who all come with coding skills but 338 00:14:29,339 --> 00:14:35,579 not necessarily Enterprise type coding 339 00:14:31,860 --> 00:14:37,500 skills and during peer reviews or peer 340 00:14:35,579 --> 00:14:39,720 reviews of the code we've found 341 00:14:37,500 --> 00:14:41,639 ourselves over and over again explaining 342 00:14:39,720 --> 00:14:44,339 look please don't build up a file name 343 00:14:41,639 --> 00:14:46,680 like that use Python's pathlib or please 344 00:14:44,339 --> 00:14:49,019 don't create a python class like that 345 00:14:46,680 --> 00:14:52,079 use a data class or please don't 346 00:14:49,019 --> 00:14:55,860 concatenate strings like that use F 347 00:14:52,079 --> 00:14:58,800 string so we have a series of custom 348 00:14:55,860 --> 00:15:01,380 linters that pylin executes that 349 00:14:58,800 --> 00:15:04,019 supports the new coder towards our 350 00:15:01,380 --> 00:15:06,180 coding standards and relieves time 351 00:15:04,019 --> 00:15:08,160 during the peer review process for 352 00:15:06,180 --> 00:15:10,699 matters of architectural significance 353 00:15:08,160 --> 00:15:15,079 rather than just 354 00:15:10,699 --> 00:15:15,079 how did you concatenate a string 355 00:15:15,600 --> 00:15:19,500 after running the customer letters a 356 00:15:17,940 --> 00:15:22,860 code looks like this 357 00:15:19,500 --> 00:15:25,220 the input file now has become a python 358 00:15:22,860 --> 00:15:28,500 path object 359 00:15:25,220 --> 00:15:30,839 because it's a python path object we can 360 00:15:28,500 --> 00:15:34,680 open it as a file and iterate it using a 361 00:15:30,839 --> 00:15:37,320 slightly different tersa syntax as shown 362 00:15:34,680 --> 00:15:39,600 on line 26. 363 00:15:37,320 --> 00:15:42,600 and we've created an instance of our 364 00:15:39,600 --> 00:15:44,880 class using the new format where we're 365 00:15:42,600 --> 00:15:49,380 passing important data into the 366 00:15:44,880 --> 00:15:49,380 Constructor on line 30. 367 00:15:50,820 --> 00:15:56,279 my pie is where things start to get more 368 00:15:53,339 --> 00:15:58,320 interesting still my pie is a static 369 00:15:56,279 --> 00:16:00,600 type Checker your note your code with 370 00:15:58,320 --> 00:16:02,300 typings and my Pi will check for bugs 371 00:16:00,600 --> 00:16:05,220 caused by type 372 00:16:02,300 --> 00:16:08,579 incompatibility here are some typical my 373 00:16:05,220 --> 00:16:10,399 Pi messages function is missing a type 374 00:16:08,579 --> 00:16:15,120 annotation 375 00:16:10,399 --> 00:16:17,820 or incompatible type assignment 376 00:16:15,120 --> 00:16:19,740 some code before and after we've run it 377 00:16:17,820 --> 00:16:21,360 through mypine manually made the 378 00:16:19,740 --> 00:16:24,480 necessary changes 379 00:16:21,360 --> 00:16:28,139 our Dundas LT method has got some 380 00:16:24,480 --> 00:16:30,180 typings now so the other attribute or 381 00:16:28,139 --> 00:16:33,600 the other argument that's passed in must 382 00:16:30,180 --> 00:16:34,800 be of type gen and it must return a 383 00:16:33,600 --> 00:16:37,380 Boolean 384 00:16:34,800 --> 00:16:40,500 while we're here we've created a generic 385 00:16:37,380 --> 00:16:43,079 based list of our generators so we can 386 00:16:40,500 --> 00:16:46,639 pass this we can use this list in our 387 00:16:43,079 --> 00:16:49,320 code without having to um 388 00:16:46,639 --> 00:16:51,839 type the list 389 00:16:49,320 --> 00:16:54,120 differently all through our all through 390 00:16:51,839 --> 00:16:55,980 our coder that grows and we've created 391 00:16:54,120 --> 00:16:59,360 an instance of our list of the correct 392 00:16:55,980 --> 00:16:59,360 type down the bottom 393 00:16:59,899 --> 00:17:03,779 Pi test is a unit test framework for 394 00:17:02,759 --> 00:17:06,179 python 395 00:17:03,779 --> 00:17:08,520 High test framework makes it easy to 396 00:17:06,179 --> 00:17:10,559 write small tests while scaling to 397 00:17:08,520 --> 00:17:15,120 support unit integration regression 398 00:17:10,559 --> 00:17:17,459 testing of a full program Suite 399 00:17:15,120 --> 00:17:19,079 now to run Pi test against your code 400 00:17:17,459 --> 00:17:22,079 you're probably going to have to change 401 00:17:19,079 --> 00:17:24,179 the structure of your code this is where 402 00:17:22,079 --> 00:17:25,319 the tools really start to take over your 403 00:17:24,179 --> 00:17:28,400 life 404 00:17:25,319 --> 00:17:30,660 code was initially a large script 405 00:17:28,400 --> 00:17:33,419 execution would start at the top it 406 00:17:30,660 --> 00:17:35,039 would work through to the bottom and the 407 00:17:33,419 --> 00:17:36,539 lines at the bottom would be the entry 408 00:17:35,039 --> 00:17:40,160 point and would call the code 409 00:17:36,539 --> 00:17:43,380 procedurally one line after the other 410 00:17:40,160 --> 00:17:45,179 that is very hard to unit test code in 411 00:17:43,380 --> 00:17:46,919 that style because you don't have a 412 00:17:45,179 --> 00:17:50,280 thing that you can grasp hold of and 413 00:17:46,919 --> 00:17:52,440 pass around you can't create an instance 414 00:17:50,280 --> 00:17:55,799 of it in your unit tests and poke it 415 00:17:52,440 --> 00:17:59,160 with the testing patterns 416 00:17:55,799 --> 00:18:01,320 so we break our code from one file into 417 00:17:59,160 --> 00:18:04,740 four 418 00:18:01,320 --> 00:18:06,600 we start with a an entry point file you 419 00:18:04,740 --> 00:18:09,000 know called done dismayne in this case 420 00:18:06,600 --> 00:18:12,720 we have a file that contains the unit 421 00:18:09,000 --> 00:18:15,120 tests test underscore gen in this case 422 00:18:12,720 --> 00:18:18,000 we have a file that contains our data 423 00:18:15,120 --> 00:18:20,340 test underscore bomb a naming standard 424 00:18:18,000 --> 00:18:22,200 we use on our project bomb means 425 00:18:20,340 --> 00:18:23,880 business object model 426 00:18:22,200 --> 00:18:25,140 and then we have a file that contains 427 00:18:23,880 --> 00:18:27,539 the behavior 428 00:18:25,140 --> 00:18:29,340 gen to total 429 00:18:27,539 --> 00:18:32,039 so the pattern we use is that we 430 00:18:29,340 --> 00:18:35,960 separate data from Behavior nouns from 431 00:18:32,039 --> 00:18:35,960 verbs in our code 432 00:18:37,039 --> 00:18:42,780 our gen bomb file contains the code that 433 00:18:40,740 --> 00:18:44,780 we created earlier on in our big 434 00:18:42,780 --> 00:18:47,880 procedural script 435 00:18:44,780 --> 00:18:52,160 definition of our gen class and the 436 00:18:47,880 --> 00:18:55,260 definition of our gen list 437 00:18:52,160 --> 00:18:57,660 our Behavior has been wrapped up into a 438 00:18:55,260 --> 00:19:00,600 class called gen to total 439 00:18:57,660 --> 00:19:03,240 it has a it has a entry point called 440 00:19:00,600 --> 00:19:05,039 execute again the standard we use across 441 00:19:03,240 --> 00:19:07,440 our team if you have a class that 442 00:19:05,039 --> 00:19:09,419 contains Behavior it has a entry point 443 00:19:07,440 --> 00:19:12,299 called execute 444 00:19:09,419 --> 00:19:14,640 execute takes a single argument passed 445 00:19:12,299 --> 00:19:17,400 to the input file and it returns a 446 00:19:14,640 --> 00:19:20,520 single value of death of type decimal 447 00:19:17,400 --> 00:19:23,880 which is the sum of all the generations 448 00:19:20,520 --> 00:19:26,160 for that trading period as execute grows 449 00:19:23,880 --> 00:19:28,620 you can break it down into a number of 450 00:19:26,160 --> 00:19:30,960 methods that are private to the Gen to 451 00:19:28,620 --> 00:19:33,919 Total class you've got to do what you've 452 00:19:30,960 --> 00:19:33,919 got to do for readability 453 00:19:35,880 --> 00:19:41,400 anything remarkable here if name equals 454 00:19:38,280 --> 00:19:43,320 main create a reference to the file that 455 00:19:41,400 --> 00:19:45,780 we want to read in reality you might 456 00:19:43,320 --> 00:19:48,140 want to parameterize that somehow create 457 00:19:45,780 --> 00:19:51,179 an instance of our gender total class 458 00:19:48,140 --> 00:19:52,880 execute our gender total class grab the 459 00:19:51,179 --> 00:19:56,340 value print the value to the screen 460 00:19:52,880 --> 00:19:59,520 nothing remarkable there 461 00:19:56,340 --> 00:20:00,919 our test code looks like this using the 462 00:19:59,520 --> 00:20:03,600 pi test 463 00:20:00,919 --> 00:20:07,820 standard we have a container class 464 00:20:03,600 --> 00:20:11,340 called test gen read Pi test requires 465 00:20:07,820 --> 00:20:15,059 the first part of a class that's part of 466 00:20:11,340 --> 00:20:17,160 a test to be Capital t-e-s-t it has a 467 00:20:15,059 --> 00:20:19,260 method or a bunch of methods on it that 468 00:20:17,160 --> 00:20:22,440 can execute our test code Pi test 469 00:20:19,260 --> 00:20:25,919 requires test underscore whatever as 470 00:20:22,440 --> 00:20:31,220 part of its naming style line 21 we're 471 00:20:25,919 --> 00:20:34,380 creating an input file with known data 472 00:20:31,220 --> 00:20:36,960 that variable content contains three 473 00:20:34,380 --> 00:20:39,360 lines of text that sets up our input 474 00:20:36,960 --> 00:20:41,100 file with values that we have control 475 00:20:39,360 --> 00:20:45,179 over 476 00:20:41,100 --> 00:20:47,940 line 20 contains a pi test 477 00:20:45,179 --> 00:20:51,179 fixture called temp path 478 00:20:47,940 --> 00:20:53,700 temppath is a path to a temporary folder 479 00:20:51,179 --> 00:20:55,260 that Pi test has created for us and Pi 480 00:20:53,700 --> 00:20:57,660 test will take responsibility for 481 00:20:55,260 --> 00:20:59,700 cleaning up 482 00:20:57,660 --> 00:21:02,400 we create an instance of our gen to 483 00:20:59,700 --> 00:21:04,980 Total class and line 25 we execute and 484 00:21:02,400 --> 00:21:07,200 capture the value and then on line 28 we 485 00:21:04,980 --> 00:21:10,039 compare the value against an expected 486 00:21:07,200 --> 00:21:12,480 answer 487 00:21:10,039 --> 00:21:14,520 and our test passes 488 00:21:12,480 --> 00:21:17,940 and of course we have many many tests 489 00:21:14,520 --> 00:21:22,080 like this in a production body of code 490 00:21:17,940 --> 00:21:24,600 again to execute to work in this way we 491 00:21:22,080 --> 00:21:26,280 must refactor our code and just a 492 00:21:24,600 --> 00:21:28,679 reminder this the pattern is we have an 493 00:21:26,280 --> 00:21:31,940 entry point we have tests we have data 494 00:21:28,679 --> 00:21:31,940 and we have Behavior 495 00:21:33,179 --> 00:21:37,500 it is the last tool in the suite 496 00:21:35,460 --> 00:21:40,500 coverage is a tool for measuring code 497 00:21:37,500 --> 00:21:42,780 that is actually executed in Python 498 00:21:40,500 --> 00:21:45,120 coverage monitors the program while it 499 00:21:42,780 --> 00:21:46,620 executes noting which parts of the code 500 00:21:45,120 --> 00:21:49,559 have been run 501 00:21:46,620 --> 00:21:53,299 it produces a report listing code that 502 00:21:49,559 --> 00:21:53,299 could have been run but wasn't 503 00:21:53,460 --> 00:21:58,080 coverage report on our frag on our test 504 00:21:55,679 --> 00:22:00,059 code looks like this 505 00:21:58,080 --> 00:22:03,200 you can see we've got coverage of 93 506 00:22:00,059 --> 00:22:07,580 percent and we're missing lines 8 to 11 507 00:22:03,200 --> 00:22:07,580 of Dundas Maine 508 00:22:08,220 --> 00:22:13,679 how are we going to hit those lines 509 00:22:10,740 --> 00:22:15,900 things start getting a little trickier 510 00:22:13,679 --> 00:22:18,059 the code at the top shows the entry 511 00:22:15,900 --> 00:22:20,340 point as we coded it 512 00:22:18,059 --> 00:22:23,280 early on 513 00:22:20,340 --> 00:22:26,820 if name equals main creating reference 514 00:22:23,280 --> 00:22:29,820 to the file an instance of the reader 515 00:22:26,820 --> 00:22:31,740 executed print the answer 516 00:22:29,820 --> 00:22:34,460 we need a way of grabbing hold of that 517 00:22:31,740 --> 00:22:37,380 code in pi test and poking it 518 00:22:34,460 --> 00:22:39,780 so to achieve that we've wrapped it all 519 00:22:37,380 --> 00:22:43,200 in a method called Main 520 00:22:39,780 --> 00:22:45,480 same code is just contained in a in a 521 00:22:43,200 --> 00:22:48,080 function called Main and the entry point 522 00:22:45,480 --> 00:22:51,960 calls that function on line 14. 523 00:22:48,080 --> 00:22:53,340 this allows us to write a unit test like 524 00:22:51,960 --> 00:22:56,640 this 525 00:22:53,340 --> 00:23:01,620 we use Python's import lib 526 00:22:56,640 --> 00:23:03,780 module to import our Dundas main file as 527 00:23:01,620 --> 00:23:06,900 if it was a module 528 00:23:03,780 --> 00:23:09,020 we then use Python's mock library to 529 00:23:06,900 --> 00:23:12,299 change the name of the doneness name 530 00:23:09,020 --> 00:23:14,700 attribute on that file to Main 531 00:23:12,299 --> 00:23:18,840 we then hit the main 532 00:23:14,700 --> 00:23:20,880 method the main function in that file 533 00:23:18,840 --> 00:23:23,280 a pi test has another fixture called 534 00:23:20,880 --> 00:23:25,220 capsis which will capture anything 535 00:23:23,280 --> 00:23:29,700 that's printed to the screen 536 00:23:25,220 --> 00:23:31,919 so line 35 we're reading the contents of 537 00:23:29,700 --> 00:23:35,039 what was printed to the screen 538 00:23:31,919 --> 00:23:36,720 it gives you two outputs what was 539 00:23:35,039 --> 00:23:38,700 printed to stand it out and what was 540 00:23:36,720 --> 00:23:40,380 printed to standard error 541 00:23:38,700 --> 00:23:44,400 we checked the contents of standard 542 00:23:40,380 --> 00:23:47,120 error against our expected value and our 543 00:23:44,400 --> 00:23:47,120 test passes 544 00:23:47,760 --> 00:23:52,380 which now reports 100 percent 545 00:23:50,280 --> 00:23:53,520 coverage 546 00:23:52,380 --> 00:23:55,460 now 547 00:23:53,520 --> 00:23:59,280 there'll be a question within your team 548 00:23:55,460 --> 00:24:01,260 what level of coverage is sufficient 549 00:23:59,280 --> 00:24:03,539 when we started this 550 00:24:01,260 --> 00:24:06,179 we had our unit test and we had almost 551 00:24:03,539 --> 00:24:08,940 prod quality code we had a coverage of 552 00:24:06,179 --> 00:24:10,799 around about 95 percent 553 00:24:08,940 --> 00:24:13,500 we all pulled our efforts and spent a 554 00:24:10,799 --> 00:24:15,299 day and we got it up to 98 percent 555 00:24:13,500 --> 00:24:17,039 and then we did some soul searching how 556 00:24:15,299 --> 00:24:20,400 far do we want to take this because 557 00:24:17,039 --> 00:24:22,620 we're spending our employers money and 558 00:24:20,400 --> 00:24:25,559 to reach that extra two percent we're 559 00:24:22,620 --> 00:24:27,120 starting to spend serious money 560 00:24:25,559 --> 00:24:29,700 but 561 00:24:27,120 --> 00:24:31,799 well and so we decided to give it a go 562 00:24:29,700 --> 00:24:33,059 so we time boxed it we said by the end 563 00:24:31,799 --> 00:24:34,980 of the week if we don't have 100 564 00:24:33,059 --> 00:24:37,380 coverage we're going to back off and 565 00:24:34,980 --> 00:24:40,559 lower our quality standards 566 00:24:37,380 --> 00:24:42,120 we achieved 100 coverage there were some 567 00:24:40,559 --> 00:24:44,220 patterns we had to nut through 568 00:24:42,120 --> 00:24:45,480 particularly that how to hit an entry 569 00:24:44,220 --> 00:24:48,240 point 570 00:24:45,480 --> 00:24:50,820 but this has become our quality metric 571 00:24:48,240 --> 00:24:55,220 from that day forward code doesn't go to 572 00:24:50,820 --> 00:24:55,220 production unless it has 100 coverage 573 00:24:55,320 --> 00:25:00,720 do I have to do this really is often 574 00:24:58,320 --> 00:25:03,480 asked by the new members of our team 575 00:25:00,720 --> 00:25:07,080 this is seriously expensive 576 00:25:03,480 --> 00:25:09,419 and the answer is absolutely yes you 577 00:25:07,080 --> 00:25:11,940 have to do this for our code base 578 00:25:09,419 --> 00:25:16,919 for many reasons but one is that your 579 00:25:11,940 --> 00:25:21,059 code will last a long long time 580 00:25:16,919 --> 00:25:23,539 December 17 1999 is the birthday of the 581 00:25:21,059 --> 00:25:25,980 system we support 582 00:25:23,539 --> 00:25:30,419 23 years old and the code that was 583 00:25:25,980 --> 00:25:33,000 written prior to December 17 1999 Pascal 584 00:25:30,419 --> 00:25:36,240 code is still in production and is still 585 00:25:33,000 --> 00:25:38,700 producing value for the organization so 586 00:25:36,240 --> 00:25:43,020 we write our python code as if it's 587 00:25:38,700 --> 00:25:47,480 going to last 25 years or more 588 00:25:43,020 --> 00:25:50,760 what's the lifetime spend on QA 589 00:25:47,480 --> 00:25:54,419 aimo's amp code base the project I work 590 00:25:50,760 --> 00:25:55,679 on now has 71 000 lines of production 591 00:25:54,419 --> 00:25:59,520 source 592 00:25:55,679 --> 00:26:02,580 it has 52 000 lines of unit tests that's 593 00:25:59,520 --> 00:26:05,179 around about 40 of our code is unit 594 00:26:02,580 --> 00:26:05,179 tests 595 00:26:05,520 --> 00:26:09,840 back to this question the hardest 596 00:26:07,500 --> 00:26:12,960 question for a coda to answer 597 00:26:09,840 --> 00:26:16,080 are you done we now have 598 00:26:12,960 --> 00:26:17,880 an unambiguous objective way of 599 00:26:16,080 --> 00:26:21,299 answering that question 600 00:26:17,880 --> 00:26:25,679 I thought it's clean it's black is clean 601 00:26:21,299 --> 00:26:28,200 is clean my pie is clean you have unit 602 00:26:25,679 --> 00:26:30,360 tests and coverage is at a hundred 603 00:26:28,200 --> 00:26:32,820 percent 604 00:26:30,360 --> 00:26:35,220 we can talk about 605 00:26:32,820 --> 00:26:37,620 the cost of peer review user acceptance 606 00:26:35,220 --> 00:26:39,840 testing load testing cyber testing 607 00:26:37,620 --> 00:26:41,400 integration testing usability testing 608 00:26:39,840 --> 00:26:44,940 and all those other things you do before 609 00:26:41,400 --> 00:26:46,799 you're truly done later but this is a 610 00:26:44,940 --> 00:26:49,400 tool for the coder 611 00:26:46,799 --> 00:26:49,400 thank you 612 00:26:54,260 --> 00:26:59,779 thanks Peter we have some time for 613 00:26:57,000 --> 00:26:59,779 questions yes 614 00:27:00,539 --> 00:27:03,799 did anyone have a question 615 00:27:08,760 --> 00:27:14,220 um do you uh 616 00:27:11,159 --> 00:27:16,980 maintain the same level of checking for 617 00:27:14,220 --> 00:27:19,980 products and related issues how do we 618 00:27:16,980 --> 00:27:21,779 how do you fix those in case 619 00:27:19,980 --> 00:27:23,340 say that again if you could in case 620 00:27:21,779 --> 00:27:26,220 there is a products and related issue 621 00:27:23,340 --> 00:27:28,460 and you need to push a chance rather 622 00:27:26,220 --> 00:27:28,460 quick 623 00:27:28,500 --> 00:27:33,720 uh yes we do 624 00:27:30,299 --> 00:27:36,000 um we have a git row repo of course we 625 00:27:33,720 --> 00:27:38,580 use git flow so we have a series of 626 00:27:36,000 --> 00:27:40,440 branches we have feature branches hot 627 00:27:38,580 --> 00:27:43,500 fixes going on 628 00:27:40,440 --> 00:27:46,620 release branches if we have a prod type 629 00:27:43,500 --> 00:27:48,620 quality will create a hotfix 630 00:27:46,620 --> 00:27:51,600 we'll implement the fix 631 00:27:48,620 --> 00:27:54,299 this is absolutely a quality metric that 632 00:27:51,600 --> 00:27:56,039 everything that goes to prod has to 633 00:27:54,299 --> 00:27:58,080 has to go through 634 00:27:56,039 --> 00:27:59,760 um you think it's an overhead and it's a 635 00:27:58,080 --> 00:28:02,039 burden and it's going to cost more than 636 00:27:59,760 --> 00:28:04,200 it saves but because we have this 637 00:28:02,039 --> 00:28:07,220 we're able to implement that prod fix 638 00:28:04,200 --> 00:28:07,220 very quickly 639 00:28:09,100 --> 00:28:12,240 [Music] 640 00:28:13,500 --> 00:28:18,679 looks like we've hit our definition of 641 00:28:15,120 --> 00:28:18,679 done no questions I think so 642 00:28:23,400 --> 00:28:27,659 so much Peter it was a lovely talk thank 643 00:28:25,559 --> 00:28:30,740 you 644 00:28:27,659 --> 00:28:30,740 thank you very much