Writing Go(od) Tests
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Number of Parts | 490 | |
Author | ||
License | CC Attribution 2.0 Belgium: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor. | |
Identifiers | 10.5446/47294 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
FOSDEM 2020411 / 490
4
7
9
10
14
15
16
25
26
29
31
33
34
35
37
40
41
42
43
45
46
47
50
51
52
53
54
58
60
64
65
66
67
70
71
72
74
75
76
77
78
82
83
84
86
89
90
93
94
95
96
98
100
101
105
106
109
110
116
118
123
124
130
135
137
141
142
144
146
151
154
157
159
164
166
167
169
172
174
178
182
184
185
186
187
189
190
191
192
193
194
195
200
202
203
204
205
206
207
208
211
212
214
218
222
225
228
230
232
233
235
236
240
242
244
249
250
251
253
254
258
261
262
266
267
268
271
273
274
275
278
280
281
282
283
284
285
286
288
289
290
291
293
295
296
297
298
301
302
303
305
306
307
310
311
315
317
318
319
328
333
350
353
354
356
359
360
361
370
372
373
374
375
379
380
381
383
385
386
387
388
391
393
394
395
397
398
399
401
409
410
411
414
420
421
422
423
424
425
427
429
430
434
438
439
444
449
450
454
457
458
459
460
461
464
465
466
468
469
470
471
472
480
484
486
487
489
490
00:00
Statistical hypothesis testingStatistical hypothesis testingStatistical hypothesis testingGoodness of fitOrder (biology)Range (statistics)CASE <Informatik>Expert systemLevel (video gaming)CodeTouchscreenMultiplication signComputer animation
01:15
Drum memoryCoefficient of determinationPhysical systemHypermediaAvatar (2009 film)Power (physics)Open sourceStatistical hypothesis testingSoftware engineeringSoftware frameworkoutputOpen setCore dumpProjective planeComputer animation
02:28
1 (number)ResultantFormal languageAuthorizationMultiplication signRun time (program lifecycle phase)Open sourceSoftwareComputer programmingComputer animation
03:31
Standard deviationLibrary (computing)Formal languageStatistical hypothesis testingFormal languageObject (grammar)Error messageBitLibrary (computing)INTEGRALExecution unitStatistical hypothesis testingComputer fileCodeFunctional (mathematics)BuildingComputer programmingSystem callProcess (computing)Focus (optics)Revision controlSemiconductor memoryField (computer science)Instance (computer science)Standard deviationBinary codeCharacteristic polynomialDynamical systemType theoryReflection (mathematics)WritingCompilation albumMultiplication signoutputTask (computing)WeightGroup actionCategory of beingDependent and independent variablesDifferent (Kate Ryan album)Physical systemInstallation artDistribution (mathematics)Projective planeInheritance (object-oriented programming)Concurrency (computer science)CoroutineBasis <Mathematik>SoftwareSynchronizationOperator (mathematics)Speech synthesisCondition numberComputer architectureData managementComputer animation
08:24
Formal languageStatistical hypothesis testingMultiplication signSound effectGame controllerIterationLogical constantMultiplicationPoint (geometry)Goodness of fitConnectivity (graph theory)Dependent and independent variablesSimilarity (geometry)
09:44
CodeProjective planePermutationContent (media)UsabilityStatistical hypothesis testingMedical imagingOrder (biology)Self-organizationUnit testingNumberComplex (psychology)Database normalizationTask (computing)Functional (mathematics)MultiplicationVariable (mathematics)Context awarenessLatent heatVariety (linguistics)Point (geometry)Combinational logicBlock (periodic table)INTEGRALPhysical systemMultiplication signArithmetic meanComponent-based software engineeringComputer animation
12:24
DiagramStatistical hypothesis testing1 (number)CodeSoftware bugState observerConnectivity (graph theory)TwitterComputer animation
13:32
Beat (acoustics)CodeSlide ruleShift operatorCycle (graph theory)Software developerExecution unitStatistical hypothesis testingCartesian coordinate systemSoftware bugTest-driven developmentCASE <Informatik>Computer animation
14:36
Data storage deviceService (economics)CodeStatistical hypothesis testingGoodness of fitText editorCASE <Informatik>Line (geometry)Condition numberPay televisionProjective planeParameter (computer programming)Core dumpRegular graphFunctional (mathematics)Letterpress printingFunction (mathematics)CoroutineComplex (psychology)Covering spaceConsistencyComputer programmingInstance (computer science)CuboidContext awarenessComputer fileDirectory serviceAttribute grammarOcean currentElectronic signatureWordUnit testingINTEGRALBusiness modelBuildingParallel portComputer animation
19:04
CountingInterior (topology)Decision tree learningLocal area networkPersonal digital assistantStatistical hypothesis testingSystem callDot productEvent horizonLetterpress printingObject (grammar)Statistical hypothesis testingElectronic mailing listCodeFluidFunctional (mathematics)1 (number)Computer fileLine (geometry)Multiplication signDecision tree learningError messageContext awarenessCASE <Informatik>Business modelStatement (computer science)LengthSingle-precision floating-point formatComputer animation
21:45
Condition numberStatistical hypothesis testingStandard deviationObject (grammar)ResultantError messageFunctional (mathematics)Library (computing)Boolean algebraCodeSlide ruleBitParameter (computer programming)Pointer (computer programming)Computer animation
23:12
Hill differential equationDecision tree learningEquals signStatistical hypothesis testingDifferent (Kate Ryan album)Dot productDigital electronicsStatistical hypothesis testingEvent horizonDiscrete groupCondition numberCatastrophismAlgorithmLine (geometry)CodeDecision tree learningError messageCASE <Informatik>Nichtlineares GleichungssystemNeuroinformatikStatement (computer science)LengthMessage passingParameter (computer programming)Letterpress printingComputer animation
24:40
Decision tree learningStatistical hypothesis testingCASE <Informatik>WhiteboardSoftware developerFunctional (mathematics)Content (media)Computer fileStatistical hypothesis testingCASE <Informatik>Dot productCodeExpected valueMultiplicationNegative numberElectronic signatureGame theoryMultilaterationFormal verificationDampingRoundness (object)SubsetLine (geometry)Unit testingINTEGRALTable (information)Decision tree learningProgram slicingVariable (mathematics)outputFunction (mathematics)LogicMathematicsIterationBitInstance (computer science)CalculationComputer animation
29:21
Statistical hypothesis testingUtility softwareServer (computing)Functional (mathematics)Uniqueness quantificationConnectivity (graph theory)Multiplication signDifferent (Kate Ryan album)CodeStandard deviationLibrary (computing)Uniform resource locatorComputer animation
30:05
Error messageString (computer science)Total S.A.Statistical hypothesis testingPersonal digital assistantUniform resource locatorCodePointer (computer programming)Dependent and independent variablesStatistical hypothesis testingFunctional (mathematics)Server (computing)Process (computing)Computer simulationAuthorizationIterationInformation securityBusiness modelIP addressUniform resource locatorSlide ruleFehlererkennungCASE <Informatik>Error messageGame controllerService (economics)Parameter (computer programming)Condition numberEmailComputer wormPlastikkarteInformationTotal S.A.Type theoryElectronic signature
34:28
Context awarenessPointer (computer programming)Different (Kate Ryan album)Statistical hypothesis testingContext awarenessBoundary value problemInheritance (object-oriented programming)Computer animation
35:08
Context awarenessDecision tree learningCone penetration testHill differential equationConnectivity (graph theory)CodeMessage passingStatistical hypothesis testingFunctional (mathematics)Decision tree learningCoroutineBlock (periodic table)CASE <Informatik>CountingPay televisionProjective planeOrder (biology)Slide ruleGoodness of fitContext awarenessPrice indexObject (grammar)Type theoryLengthPoint (geometry)Multiplication signService (economics)System callFerry CorstenComputer programmingFluid staticsMathematical analysisOnline helpSurfaceParameter (computer programming)ConsistencyPlastikkarteContent (media)Concurrency (computer science)Source codeComputer animation
40:44
Condition numberCondition numberComputer programmingStatistical hypothesis testingParameter (computer programming)Complex (psychology)Vulnerability (computing)Concurrency (computer science)CoroutineComputer animation
41:28
Condition numberResolvent formalismSemiconductor memoryWeightGroup actionWechselseitiger AusschlussCodeMultiplication signComputer fileStatistical hypothesis testingLine (geometry)Computer animationSource code
42:05
Line (geometry)Functional (mathematics)Statistical hypothesis testingWechselseitiger AusschlussParallel computingPay televisionDecision tree learningMathematicsMessage passingBitMultiplication signSynchronizationLibrary (computing)Block (periodic table)Computer animation
43:05
Decision tree learningPay televisionSynchronizationDecision tree learningSynchronizationWechselseitiger AusschlussBitVariable (mathematics)Statistical hypothesis testingWechselseitige InformationStatement (computer science)Functional (mathematics)Computer animation
44:02
Point cloudFacebookOpen sourceStatistical hypothesis testingOpen sourceComputer animationLecture/Conference
Transcript: English(auto-generated)
00:05
All right, well hey guys welcome to the testing and automation dev room today. I'm going to be talking about Writing good tests writing go tests and writing good go tests probably in that order I tried to my best to cater this talk to a wide range of experience with go
00:24
So if I have experts listening out there Please just be patient with some of the introductory material And I promise we'll work our way up to some of the more complex cases and to any novices if you're new to go Don't get don't get discouraged by any unfamiliar go concepts or packages much of what I'm going to cover is
00:44
Like directly from the go doc. So if you want to reference those at the end or revisit anything, you're welcome to do so Hopefully regardless of your experience level you're all visual learners I'm gonna have a lot of code on the screen and kind of be like tutorial based For each concept
01:00
So hopefully by the time we're done here You guys will walk out with a better understanding of best practices in general testing and in going testing My name is Nikki Atiyah and I'm a work from home a dog mom who hacks on distributed systems and workflows
01:21
I'm based in Southern, California And you could find me at my handle listed on any social media You could just look for the baby Yoda avatar sipping a soup and that's me I Why am I talking about go tests? I have a long history with testing and go
01:41
So I at the power and performance team at Apple I maintained automation frameworks for iOS and watch OS performance testing and we used Ruby and Python for that And currently I'm a software engineer at senseu which is an open source and open core monitoring tool kind of like Nagios on steroids if you're familiar with either of those and for about the last three years
02:04
I've been contributing to the sense you go project which judging by the name is a rewrite in go senseu Inc the company I work for Has supported all of my efforts to come here from halfway across the world So a huge thank you to them and their support of open source
02:21
So if you're curious about what we do at senseu at all, you could check out our github right there All right, so let's start by answering the first question you might have on your mind why go Believe it or not. There's a lot more to go Lang than the cute little gopher mascot such as this Belgium styled gopher by Ashley McNamara
02:42
For starters, it's an open source language, which is pretty important designed by Google. You could find it at github.com slash go Lang So it's a comparatively young language, especially compared to the ones that more the popular ones We've today and it's been almost eight years since its initial release
03:00
to the public so As a result the original offer authors were able to address more current and relevant problems in the landscape Like at the time that they were designing it. So these include runtime efficiency high performance networking and multi-processing So as such many consider it like the hot new language and distributed programming
03:23
Including those big companies like uber twitch Netflix which all require like high concurrent performance. So it's a good language Now that we have a little go history. Let's talk about the design of the language For the most part. It's considered considered statically and structurally typed
03:42
It's type safe requiring a strict type for each object and field memory safe Allocating memory for each object and field So in these instances the compiled binary is a lot faster at runtime, which of course is a performance win But the caveat to this characteristic is that go also supports some instances of dynamic typing
04:03
so if you're used to like Ruby and doing really dirty like Things in Ruby go is capable of doing that But it's just more considered on the statically typed language side So using techniques such as reflection you can treat go objects generically at a little bit of a performance trade-off, so
04:24
generic functions If you decide to do that, they encourage code reuse and can compile their program faster But might be a little slower at runtime because of the additional dynamic processing it's doing So for the sake of testing, we'll focus more on the statically typed cases because they're very straightforward
04:42
It is a compiled language which with a very large standard library It produces tools such as go build go run and go test Therefore when you download the language itself, it's quite large. I think comparatively to other languages as well the most recent version of go is
05:00
116 megabytes on Mac OS and Linux systems but due to the enriched Standard library that they have a lot of the functions you're going to need on a day-to-day basis such as IO calls OS exec encoding networking syncing and of course our testing operations Are all contained in these built-in packages, so it limits the need for external dependencies
05:26
so speaking of containment It's self-contained. So you say go build and it's good to go So when you run go build it produces a static binary against a target operating system and architecture so the resulting binary includes any external dependencies you have with your dependency management system and
05:47
It'll run on systems that don't have the language installed. So it makes distribution of go projects super super easy And finally, it's concurrent and asynchronous with go routines programs can handle tasks simultaneously
06:02
And with channels and weight groups programs can execute tasks asynchronously So both of these properties improve performance and responsiveness for your program Regardless of the pros and cons of these characteristics as a whole They all have a different impact or implication on how you would write tests
06:23
So tests like I mentioned in Structurally typed languages are a lot more straightforward because your input and output types are already defined and You would probably have a compile time error if that was incorrect Therefore testing fixtures are a really common practice in go
06:42
They can be read in from a JSON file or compiled in your go file directly But essentially they'll provide a quick reliable and easy way to invoke test artifacts in your tests The standard library makes writing tests really really convenient The testing package is built-in and it's really huge. It's got
07:04
Functions to make assertions and they can be made without any external dependencies It also has packages for mocks and things such as like a test HTTP server, which we'll get into a little later for more of the more involved integration tests
07:22
Self-containment how this relates to testing is it helps keep tests organized Unit and integration tests are written as go files and they live directly in your code These tests of course are not built and packaged and shipped when you run go build But you can easily run the tests in a CI pipeline and the end tests
07:43
However capture more of a larger scope But go build kind of empowers you to do that a little easier in a CI pipeline So you could go build and then run and end tests on that resulting binary The concurrent and asynchronous features of a go program are some of the biggest challenges to testing in go
08:04
Because they're more prone to race conditions and blocking calls Therefore running these kinds of tests with a race detector and a timeout is typically encouraged So we'll get into some examples about how to deal with concurrent complexities and tests, but for now, I hope I've convinced you the pros
08:21
Outweigh the cons when it comes to go design and tests So whether you came in new to the language or not, I hope that background was useful Because it's gonna help us understand at greater depths You know the background of the language and help us write better tests So
08:40
Regardless of the language we choose even if you're not choosing go and you're just trying to get a good seat for the next session We still need to be able to recognize what actually makes a test good and what techniques we can use to write them Simply and concisely. So what are some properties of well-written tests? Similar to a scientific experiment a good test will only test one thing at a time
09:04
So control variables are designed to isolate the effects of a single independent variable on That it has on that outcome and just as there are control variables and like a scientific experiment like this a test should also contain Controls for each iteration. So for each thing you're stressing
09:22
You should have some constants around it Even in larger scope tests such as end-to-end testing you want to focus on Testing just the happy path or just a networking failure, for example otherwise, you could end up with multiple points of failure, which Makes it really difficult to isolate which component caused what?
09:46
While we want to make sure we're only testing one single thing at once We also want to make sure we're testing many things together and that's not a contradiction. I promise Software components can have many like entangled interdependencies
10:01
So for this reason we should exercise as many permutations as possible in our tests so each permutation of code like in this image is Going to be unique in context and order and although the permutation itself is unique It can still yield the same outcome as other tests
10:20
so for example, like having the same number of blues and reds in each column and Just because we have a redundant outcome doesn't necessarily mean that the test is bad and we're being redundant Because we're actually creating multiple success and failure scenarios Triggered by all of these different variables working together. So
10:43
The variety of the code paths that get touched by these different combinations are what makes a test really really good Are you guys familiar with Marie Kondo in Europe? Some of us? Okay So we want to be able to test a single thing at once and additionally multiple things together
11:01
But how are we supposed to do that, especially in systems that grow in complexity over time and often like per commit? Simply put we can Marie Kondo our code. She's a Netflix documentary person and she helps people like organize their garage and stuff It doesn't mean we want to erase the code that like doesn't spark joy like we still need our code
11:25
but for example if a project Resembles a messy garage that hasn't been cleaned out in years It's going to be really difficult to verify the contents and usability of anything that's in there So on the other hand if we Marie Kondo our garage, we'll probably have
11:41
Transparent containers, they're clearly labeled organized by season or activity And they're easily accessible for when we need it. So the same goes for code in that metaphor Organization and compartmentalization Will help simplify our code and therefore our tests. So our unit tests are smaller. They're testing smaller things
12:02
By dividing complex tasks and functions into smaller and more manageable pieces We can encourage some of that same code reusability and isolate those specific problem points So obviously those smaller code blocks are far easier to unit test than large like run-on Integrated code blocks that just start getting really like intertingled
12:26
Another component to writing good tests is writing failing tests This tweet says you From another talk. I'm not sure where Says you should never trust the test. You haven't seen fail I'm not sure that Joe or Gwen here were the first ones to say that
12:43
I don't know if they're gonna be the last ones to say that because I'm saying that now But we shouldn't just write tests to pass we should be making incorrect assumptions on our code So we can actually observe that failure So personally my favorite way of doing this in practice is with bug fixes So if I've narrowed down kind of to where I think the problem is I'll immediately write a test for it
13:07
Without even attempting to like fix the bug Observe that it fails ultimately reproducing the bug that's reported and then I can
13:20
Basically confirm that the code was behaving improperly before and properly once the fix is shipped So your code reviewers will thank you when your test explains exactly what's going on So that last slide is really just a funner and like longer way to say test-driven development Some might argue that it's just a vicious cycle of endless handoffs or maybe riddled with like developer bias
13:46
but in many cases At least in unit case unit tests, it will help you achieve really good code and really good coverage So in practice writing a test prior to fixing the bug or implementing a feature sounds great, especially for the smaller scope tests
14:02
But in reality for real-world Applications and larger scope tests. They're not as easily testable through this method. So don't beat yourself up over it I'd recommend using a test-driven development when possible but focusing more on shipping organized and compartmentalized code Which in the end makes it more testable
14:23
So that's kind of my overview on writing good tests and best practices I write and speak a lot about testing in general So if you want more of that be sure to check out the resources at the end But for now, let's shift into go tests specifically Before we start writing go tests, we'll kind of discuss how we would run a go test
14:42
I mentioned earlier that golang produces tools such as go build go run and go test and you guessed it Go test is meant to run our tests So it includes other features such as benchmarking and parallelization Which we won't get into in this talk, but they're great resources Into gathering and reporting some more context about your code
15:03
So to test you simply run go test from the directory where your tests are stored or you can utilize some other attributes of the tool So here's a couple use cases for how one might use the tool You can run all the tests in the current working directory You can run a single test against a fully qualified package name, which you have like the github
15:26
Path there. You can tag go files with a simple comment at the beginning of the file So if you want to just run integration tests, you could tag all of your integration files with that And that's just gonna run all of those
15:42
I Think I skipped over dash V is just gonna verbosely print any output any additional output from your go testing tool You can enable race detectors with dash race and timeouts for tests that might depend on blocking calls So if you're calling something with dash race, you're probably going to want to timeout as well
16:06
You can also get a snapshot of code coverage with dash cover I know code coverage can be a pretty controversial topic While I believe we should all be shooting for a hundred percent code cub at least on unit tests
16:20
it's really not feasible in some instances and Writing high-quality code in meaningful tests is a lot more important than just checking that box and saying like okay a hundred percent code code We're good And lastly some editors such as VS code which is what I use can even highlight the covered and uncovered lines of code if you run the tool through the editor
16:45
So it's kind of really helpful to identify if your test is actually hitting those lines Some basic syntax and project details that you'll need to know about writing go test before we get started First is the file naming convention
17:03
All shippable go code should be in a file ending in dot go just like that Well, all non-shipped test code should have the postfix underscore test dot go. So it's still a go file It just has that test When writing fixtures for your go structs that are going to be using your tests
17:21
I would actually recommend to organize those fixtures in the go file. Not the test file In the same package where that struct was defined because this is going to help limit circular dependencies that could arise when importing those fixtures in tests across packages
17:41
Secondly the test naming convention a go test is written as a regular go function like this With the caveat that it always follows the same function signature So it's prefixed with the word test with capital T and it accepts a single testing dot T Parameter which is like the core
18:01
testing package in go The first letter it should follow just like regular camel case and that's kind of goes Styling preference so the function name will identify that test routine like that test xxx and
18:20
It should be capitalized So with all of the prerequisites we discussed in mind, let's move on to writing tests and go We'll start out with very basic tests and work our way up to more complex use cases with like race conditions and all of that So patience is a virtue
18:40
for the sake of consistency I'm gonna try and use the same example throughout so this Gophers getting pretty lit on beer beer seems to be pretty popular here in Belgium, and I love beer so we'll go with that Our business model for our program can be an online beer store and subscription service
19:04
For some context I created a few structs to represent each object in our business model So a cart is a shopping cart that contains a list of cases a case is a pack of beer that Can be a specific amount such as a six-pack or a 30 rack and then a beer represents details about the specific
19:26
Beer such as its brand its name its size and fluid ounces So on and so forth so I've also written some preliminary fixture functions. I've only pictured one here There's some other ones in the code, but they're a little
19:42
Intuitive once you see them in the tests And this is just gonna allow us to rapidly initialize this struct So I don't need to take up six or seven lines of code creating this every time I want to create one of these objects Lastly the function we're gonna test first is really simple
20:00
It's called add case and it simply appends a single case of beer on to the list of cases in the shopping cart So our first test is pretty simple We initialize a new empty cart again that function wasn't pictured, but it just initializes a cart object Before we do anything else. We're gonna make sure the length is equal to zero with an if statement and
20:27
Call T dot fatal in the event that it fails So it'll print expected empty cart if for whatever reason there was beer populated in our cart to begin with. What's up?
20:45
Yes Typically, yes only used for the test But if you if you want to use that fixture in Other packages like when you're say I'm testing in another package and I want to use that fixture
21:01
I can't export that in the test in the test code itself It'll only look for the code in the go test or the go file. I'm sorry. Does that make sense? Okay, so custom error We'll create fixtures here
21:21
So we can add those fixtures into our add case function The bat blue light is my favorite beer. It's a Canadian pilsner typically found in 12 ounce cans, so we'll fill that up in the case and If it works properly our tests pass the second assertion as well because we're making that if statement on the length of the cart
21:46
So As I just demonstrated in that last slide using the go testing package alone We can explicitly mark tests as passed or failed depending on the if conditions This makes code more human readable But it can kind of create all of these like dangling conditionals that make it a little bit more tiresome
22:04
And we're going to be writing a lot of tests today So I want to introduce you to testifies assert and require packages Testify is a lightweight external dependency that acts as a testing toolkit around the go standard library testing package
22:21
So it wraps the testing object T that's your top-level parameter in all of your tests and It extends common assertions such as equal not equal Error no error nil not nil and many other conditions Every function takes that object T as the first argument and
22:43
As a result it can write those same errors out to the go tool The Assert and require functions also return booleans indicating if the assertion was successful or not So if you want to build off of each assertions and only make Assertions based on like what you just previously asserted you could do that. We're not going to do an example of that here
23:06
We're just gonna ignore and swallow the return to booleans but for future reference if you have special code paths, I would recommend that So the same test we just did it can be written in a few less lines of code simply by invoking testifies assert package So rather than making that if statement we can just assert equal the length of the cart and have
23:27
This empty or expected empty cart message. So assert can take Three to four parameters the first being T the second parameter being the expected value the third parameter being the
23:44
Actual value and then the fourth parameter being an optional like custom error message So that's what it'll print to the go test tool in the event of an error The require package is almost identical to the
24:02
Assertion or the assert package the only difference being that if you call require dot Equal and that fails. It's similar to running T dot fail now, which will short circuit the test in the event that it fails and it won't continue on So I'd recommend you kind of use your best discretion when deciding between an assert condition versus require condition
24:26
But for any like non catastrophic failure scenarios, it's probably easier to use assert So any failed test cases in some algorithm or some mathematical equation that you're computing will still be surfaced despite any failures prior
24:41
So we talked about test-driven development a little earlier now that we've gotten our feet a little bit wet with some go examples We're gonna see it in practice So let's say I want to write a function called subtotal that calculates the subtotal of the contents in my shopping cart Instead of jumping directly to the dot go file I'm gonna start with the test dot go file writing a case that I know is gonna fail
25:04
Writing it before my code is going to encourage me to adhere to the original expectations I have for the function and probably the original Function signature as well so Test subtotal similar to the add case test. I can fix your beers and cases and add them to my shopping cart
25:25
Google tells me a duvel triple hop is a very popular Belgian beer and although it's quite expensive for a pack of four I'll be worldly and try it out and throw it in our cart. So We also might be playing some drinking games. Later. We need a 30 rack of labatt blue
25:40
For $24.99, so I'm expecting the subtotal function here Which we haven't yet written to accept zero parameters, so empty parentheses and Return a single value, which is a float If this function works as I would expect it to the calculated value should be
26:03
$39.98 but let's round up to 40 for the sake of this test so we can first assert the failure and then Fix it later on So our tests assume the function signature was shown here. So this is how it should be defined So it should plug and play perfectly We're gonna do some really intense math here to calculate the subtotal
26:24
We'll iterate through each case in the cart adding the price to the subtotal and then return that value So it fails with an expected Value of 40 and passes with the corrected value of $39.98 So while TDD kind of gets us to decent coverage in this small helper function
26:45
I Like to believe we're a little bit more thorough than that and we want to test more than one instance of the calculation so Although it may have passed like what happens if our cart is empty What happens if there's a negative value for some reason more than two cases duplicate cases in our cart?
27:06
And then lastly, how can we capture all of these edge cases while simultaneously optimizing our code? Reusability So for this purpose a common practice for unit tests and go are called test tables So you can create a test table, which is basically a slice array of input and output
27:26
variables Defined directly within the test. So this struct doesn't have to exist anywhere else. It's just right here We can take the name of our test It'll accept a cart as an input value and the output value for the test case is going to be the subtotal which is that float
27:47
So using normal go syntax and logic we can actually just iterate through all of the test cases here and t.run is going to Run each function as a subset or sub test of t in a separate go routine
28:02
But that go routine will block until each test is finished Doing this allows us to reuse this line of code But if you have longer tests if it's an integration test, it's going to be nice that you have Your test tables here because you're only going to have to define your testable stuff once
28:23
So instead of it writing an entirely new test we can validate all of these multiple Edge cases as sub tests so empty values multiple values negative values They all abide by the same function signature and we're good to go I'm not sure under our business model, but circumstances would create a negative value or like a free beer
28:46
but in this case We're at least like aware of what we need to add and we probably just need like some additional validation in Either our fixture or in the struct itself So while we aim to compart compartmentalize our code we can do the same for our tests
29:05
So as a code reviewer, it's a lot more clear to identify like what your tests are actually trying to accomplish When they're broken out like this So the more complex the setup and verification the larger trade-off you get with some of this code reusability
29:23
HTTP test is another standard library package which provides utilities for HTTP testing The new server function starts and returns a new server the caller of course being responsible for stopping and cleaning up that server It gets created with a unique URL
29:42
So it's important that the component that you're testing has a way to pass that variable in So we can make our tests against the test server rather than a real server The function also accepts a HTTP handler that gets invoked each time the URL is called So we can basically mock out the server and have it return different
30:05
status codes So for example, let's think about how our business model might interact with like an external or arbitrary HTTP API So say we have a third-party service to process payments. We can simply just do an HTTP post such as this
30:21
to send the payload there It could include other things such as like the total amount that's due any credit card information if it's secure And whatever else the payment server needs And this function as is is actually really difficult to test because pay.me is hard-coded directly
30:41
Within the function itself. So we have no control over what the server is going to do. So we wouldn't want to do this and Then it would just leave us really prone to like intermittent service errors and we wouldn't be able to actually like change the outcomes of the service so
31:01
Passing the server address as an additional parameter is much more suitable for this test So the payment server here, it could still be a constant, but we're just going to pass it at the highest scope in the function So this will allow us when we're testing a process payment to Pass the URL of the test server up here
31:22
And we can control the custom handlers that are going to respond So as such we can simulate things such as auth errors, server errors, and of course like a successful payment processing So if we have more code down here that like is dependent on the response of HTTP post
31:40
We can exercise those code paths as well The handler func type that you see up here is defined in the HTTP package And it basically is an adapter that's going to allow us to use ordinary functions that we write as HTTP handlers So if it abides by the appropriate signature here with a response writer and an HTTP request
32:04
We'll be able to pass that in as the handler The signature, it looks maybe a little weird if you've never seen this before But it's the same signature that if I was writing my own go server I would run the function serve HTTP, and it just mimics that as a real server
32:25
So in each test case we'll write a custom function here in these two different handlers One for exercising a 200 status code So we'll do that with the write header function, and we'll pass the status okay, and then w.write lets us pass a response body
32:45
Through the test server, and then here for our internal service error We'll just write that header, and it won't have a response body Kind of similar to how a server might act in the wild In each subtest we'll start by initializing a new HTTP test server right here
33:03
This handler func is from that test case so those little custom handlers we built Immediately we want to make sure that we defer TS.close which means at the end of all of our tests or at the end of each iteration It's going to close the test server
33:20
So it's ready to create a new one So for both of the test case that we defined in the last slide the expected errors And the expected body are asserted as well So it should match up But it actually doesn't and it's kind of a caveat to how the HTTP post method works
33:42
Does anyone know why our test might fail? So HTTP post actually doesn't return If it's like a 500 response code it still returns a nil error because it was actually successful in processing the post command so
34:02
What we actually need is Some error checking or some checking around the response status code so if anything greater than a 400 We want our actual function to return that error So we can have this custom payment server error and give us what the status code is so The previous test would fail under I'm sorry would pass under these conditions now and remember never trust the test you haven't seen fail
34:29
So that caveat kind of reminds me of another gotcha that I've come across in the go world and it has to do with contexts Specifically how it relates to text testing so the context package in go is
34:42
Responsible for carrying like deadlines and cancellation signals across API boundaries and processes It's extremely powerful But equally as important to understand so you want to initialize a parent context And you can call one of these two methods to do that They actually both initialize a non nil empty context the main difference being
35:06
Context dot background is typically done in The main function initialization and tests whereas to do kind of acts More as like a to-do like comment with the Idea that you will eventually like replace it in the future
35:23
So maybe at the time that you're writing the function you don't have a context That's actually being passed down through a higher scoped function So you'll put it to do there or you're unsure of which context is appropriate to use But the intention is that we're gonna replace that so it doesn't really make sense to use it to do in tests
35:43
Because we have no intention on replacing it and the test is already at the highest scope so If you ever notice inconsistent uses of to-do and background and tests that you're reviewing in the future Save someone from getting nerd sniped and direct them to background While they're programmatically the same if you use them correctly you can actually use like static analysis tools
36:07
And it'll help validate that you're using and passing around context through your components Correctly so it'll help surface problems early on So now that we know what context to use and when we can start creating functions
36:21
Which require them and test them out so context should always be the first parameter in a go function so in this start subscription timer It's the first and only parameter here I've implemented a subscription timer which basically will listen for a context Cancellation signal if it's done. We'll just return from this function if not on every
36:46
interval of this ticker it will send a cart Along the message channel, so you see it doesn't return here after so each Each time that timer or that ticker expires. It'll keep sending those messages on the channel
37:04
So as a beer subscription service will have multiple orders to fulfill concurrently So we'll likely have to call start subscription timer through a go routine for each active subscription When the order is ready to be processed We just send it on that message chan, and we'll have another component of our code actually responsible for receiving those messages
37:25
So how do we test channels? To test this function will create two unique carts there at the top For The sake of this test it makes sense to have a short Interval duration such as one second or even like a higher granularity like millisecond duration
37:45
But that's how we set it through the subscription itself The timer is designed to run continuously, and it doesn't return so like I said we need to run this as a go routine in the test if we don't it's just gonna sit there and block and likely timeout
38:02
So Our function is sending values along the channel But we need to verify that the correct message is actually being received on that message Chan So this channel syntax here is just going to allow us to Just like pop that Message out of the channel and then we'll do a type assertion
38:23
To make sure that it's actually a cart object and assert that it's equal to this card up here So I've done this twice because I want to make sure that we can test consecutive Items and that the contents of this item is unique to the one that we defined earlier
38:42
Now that we've verified our program can send values on a channel we want to work on a concurrent component That's designed to receive them. So we have this start order handler function It's again designed to continuously wait and handle any Messages that are received up here. So that same syntax that we use in our test
39:03
For each cart that it receives on the message channel, it's gonna attempt to place the order in this arbitrary function That's basically just going to iterate something in our order handler So if it fails, we'll log but we'll continue on and stay in here
39:23
If there's any type assertion failure, we'll log that as well The only time we're actually going to exit this program or this go routine is if the channel itself is closed Which we can detect up there To test this asynchronous behavior We can simply start the order handler in a go routine and send a few objects on the message Chan
39:45
So we start the function here assert that the length is equal before we even start sending anything and then We send a couple messages on the message Chan You can see that this is a case object not a cart object, but we are
40:00
guarding against Wrongful like type Assertions in our code so we know that there's only two carts that are processed and the length is equal to two So this message isn't actually received Well, the message is received, but it doesn't count in our length
40:21
So at this point we have two asynchronous and concurrent components of our project our subscription timer And our order handler They're both basically small enough to fit in a single slide which is kind of a good indicator that They're broken down and compartmentalized which again we said was really good So breaking things out is just going to make our lives easier in the future
40:45
In a nutshell we have a working program with pseudocode that demonstrates some of those basics for writing go tests But we have yet to polish up on one of the harder concepts in go which is race conditions and race detection
41:01
Introducing that complexity kind of increases vulnerabilities such as race conditions whenever we're using concurrency such as go routines and Channels I'm gonna try and speed up a little bit again. We can use the dash race argument to detect race conditions
41:20
Where Yeah, we'll just skip over that So we already have our tests written And I actually have a race condition in one of them already It was somewhat intentional. It's fine But there's a couple techniques that we can use to resolve unsafe memory access Such as blocking with weight groups blocking with channels or turning a channel or using a mutex
41:46
Some of these solutions are going to require me to refactor code and I don't really have time for that So we'll look at the stack trace and try to identify why it's failing Basically, this is just what it looks like. It's saying on line 123 and line 138 in my go file in my go test
42:04
That's what is Problematic and comparing these two lines you can see that I'm attempting to read from the subscription cart in the main function and then in the test I'm trying to
42:20
Set the subscription cart and mutate it and write to it So that's really unsafe, especially when you have multiple concurrent processes that could access this So in this case, we're going to want to use a mutex to protect against us So the sync package I wish I had a little bit more time to dive into the synchronous
42:41
Synchronization that it can offer but it's basically intended for use by like low-level library routines Mutex stands for like mutual exclusion lock. So basically you can lock a value and unlock a value and locking it will Block any like reads and writes to that value until it's unlocked
43:05
So we only need to make a couple more changes to make that test pass First we'll create we'll make sure the cart and interval variables are not exported So we can't access them outside of our package We'll add a mu a mutual
43:23
mutual exclusion lock and then anytime we want to We'll create those getters and setters and anytime we want to either Get the value of the cart or set the value of the cart We will lock it set it get it and then unlock it. So that we'll just do that with the first statement here
43:40
Sorry, I know we went over that a little bit fast, but lo and behold Synchronization protects our concurrent functions and the race detector Okay's our tests with just those simple changes such as a mutex so sync is just one of the many packages that We went over today and it's really really important so I would definitely suggest going over that a little bit
44:04
Sorry, I'm rambling on but I really appreciate you guys listening to me at nerd out on testing and go Lang All of the examples you saw are up on my github, which is Nikki X dev and Then a special thanks to all of these folks that contribute to open source with their gopher con or their gopher illustrations
44:24
So, thank you guys, I don't know if you have time for tests, but come find me out after