Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course

The "Testing in Go" Lesson is part of the full, Polyglot Programming: TypeScript, Go, & Rust course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen walks through testing the projector project in Go with the built-in testing package and test command. A student's question regarding the phrase "black box testing" is also covered in this segment.


Transcript from the "Testing in Go" Lesson

>> Let's keep on going, let's jump over to go testing. Let's see if I put anything, anything a little special, I think I'm not sure, look that, I even put in this error right here. Look at that, I was smart enough to catch myself, awesome. So let's go next.

And we're gonna do the exact same thing. Let's see, why did I add in go compare? Here we go, I actually added in one you could go get. Here's a nice one, go compare, you could use that or not gonna be using that. Remember go has its own testing kind of harness put into the gocode.

But it's pretty cumbersome like the rest of Go, there whole not equal nil error problem, all that a lot more boilerplate. This just makes it a lot nicer, we're just not gonna use it. So let's create another config file, or yeah, another test file called config_test for the go project.

I know that transition was a little abrupt, sorry about that. We're now in go run, right. All right, so let's go over to package, projector, create a file, config. Test.go, awesome. Of course, so now what is your package name? Well, you have two options here. And I think this is kinda like a philosophy problem.

I personally always do the name of the package underscore test, the LSP will automatically kind of let you know that that's one of your two main options here. The reason why you should do that is that it prevents you from being able to test internals. Once again, I just got done giving this whole soliloquy about this but I'm blackbox tester, I just think it's the way to go.

And some people like more of that gray box, white box kind of approach, I always feel like it's just dangerous. So for me, always do that. It's very obvious what I'm trying to test in this file because already I'm naming it the same as the file. But I can only do public interfaces.

>> Can you explain what you mean by black box testing?
>> Black box testing effectively, I believe it's named after the airplane which I think black boxes are orange. But there's just this device on airplane that gathers data when the airplane crashes and everybody is killed, nobody knows what happened.

They grab out the black box and they can effectively see what data comes out and goes, this must have happened over here. And so, it's kinda named after that where, I don't know what's going on internally. I just know what I can put into the system and what I expect to come back out of the system.

So, I don't really know, it's just a void area. You don't have to know what it is. And so white box obviously is like the opposite of black box. So you can see into the system, is generally how it's been explained at least to me and those were for my college years, we were using those terms.

So that's ten years ago, so I don't know if new suite testing terms have even better things that are more descriptive but that's how we talked about airplanes and great stuff. Orange is the new black box testing. This is true. This is an actual fact. All right, so let's write the config test.

Now again, go is a bit more verbose if you don't use a library, but we're gonna deal with it because we are champions. So all of your functions to start off with test. You'll even see that tab 9, great auto complete product will go off and say, hey, this is probably what you're trying to type.

I'm just gonna say yes on it. And let's just, hey, why not? That seems good. So let's go TestConfigPrint, right? Awesome, of course, you have to include the testing library. Effectively this is just a library that is part of the STD that they provide, you will be able to run this project via Go test, and you just provide a path or you can test everything.

And there you go, now we're ready to start testing this function. So a little bit better than I'd say the TypeScript experience in the sense that testing is inherent to TypeScripts or to go as opposed to testing is something you add on to TypeScripts. So I like that approach, I like a language that's thought about it that supports it.

So we have a lot of things we can do, but really the most useful thing is gonna be Error or Errorf. Effectively this will break your program and say, hey, this line failed right here, go check it out, and you can put it in a nice little message, fantastic.

So let's do that right now. So if you remember, GoLang duck typing just like TypeScript walks like a duck quacks like a duck, it obviously is a giraffe. And so, that is how GoLang does all these things. So we don't need to actually to create the same type as long as we have the same shape, it should all work out.

But since we're not dealing with any methods, we can just import.type, create one, and we should be good to go. So I'll do that right now, I'll go projector, it's gonna say hey, yes, we probably should be using that. And I can just simply go to a ops, right?

Cuz we need to create an ops to pass into our config. So let's create that. I assume everybody love my really great joke about a giraffe, made it up on the spot, feel pretty good about it. Let's paste that in. So let's create a simple empty object, right?

So I'm just gonna go args is empty string array. A config is gonna be just an empty string. And present working directory is just an empty string. There we go, we've done it, we've created this thing, I just kinda copied it over, so it's very obvious how I got to this point So let's just do the very simple test, we'll go config =, I believe there's gonna be an error as well if I remember our interface correctly.

We'll just go projector NewConfig pass in ops. And of course we need to pass it in as a reference. Please remember it takes in a pointer. We didn't create it this pointer, we could have easily created that as a pointer by putting it up here. Honestly, does it really matter?

Probably not. To make things a little bit nicer, I'll see if my refactoring tool actually works, getOpts, it didn't work, that just hurts. Okay, well, it sort of worked. I go in, we'll put it in here, opts, projector., there we go. Fantastic, so now I'm just gonna use this and one last thing we're gonna do is I'll just pass in arg cuz we're gonna type this a few times.

So we might as well just abstract it out a little bit, so we don't have to keep typing the same thing over and over and over again, right? So there we go. Now I can simply just do the old string that, there we go. Beautiful, right? A little abstraction in your tests are fine.

I try to keep it always to a minimum. Anytime I find myself doing an if statement in my test, I have to slow down for a second go like am I making the right decisions with my life or have I now added in something that needs to be tested that's a test?

And you never wanna be in that spot. All right, so let's do pretty much everything you'd expect it to do, right? We gotta do a little error checking. If there is an error, and we didn't expect an error, we need to do a t.rrorf and I can just simply do a, I expected to get no error, right?

Very great English everything is fantastic, everyone should understand. So unfortunately again like I said, it's just more verbose, and you can kinda see the verbosity about to happen, right? So now when I do config I'll have to do something like this, if config.Args there is a deep equals, so this is actually pretty cool, reflect.DeepEquals, right?

So I can do a deep equals on this thing to say, hey, does this array have the same shape as the one I'm providing? If you look at the deep equals it has a xy any awesome. And so I can do something along the lines of I expect my config to have, let's see, config.Args, I don't know where that went wrong, there we go.

And it should return that, awesome. So if that's not the case, then we got ourselves something wrong. All right, expected args to be an empty string array, but got, and will do little +v in there. So that way we get that nice, beautiful print, config.Args. So now that we've written a single test, let's see how this goes, right?

Let's actually see if this will be successful. So I'm gonna erase that, jump over here and go go test. Now, this is effectively the syntax to say, go find everything that has a test and run it. I can point it to something directly or I can just say run all the tests.

Hit Enter, if I've done everything correct, awesome. So I just said, hey, there's no test files inside of P2 which is a problem from yesterday, there's no test files inside of projector. There is a test file here, we've ran it, took very, very little time, awesome. You'll also notice that go did compile everything while we did that, and it ran really, really fast.

And that's because, Go again, weird syntax makes super fast compilation, and often tests and all this other extra infrastructure that you're waiting on, just go so fast, and I just think it's pretty awesome. There we go. So we have something that works, we could abstract this out to make the rest of testing pretty easy.

If you can kinda see, really all I'm gonna be testing is this string array, all I'm gonna be testing is don't throw an error and we could effectively just go through and make a pretty simple meta test. For the sake of this, we're just gonna kinda keep it simple and I'll just cut and paste it.

All right, let's go print key and I'm gonna go over here and go foo, and I should be able to see in here foo, right, awesome. We'll abstract out, there we go, run it again. We'll abstract out the next testing we'll do, and that'll be great. But for this one, I just wanna kinda keep it simple.

There we go, let's go Add key value, right? Exact same thing. Add foo, bar, awesome. I also noticed that I did not test see problem by copying and pasting code, right? If okay, you know what, I'm gonna abstract it. I can't do it anymore. It always hurts me emotionally to cut copy and paste too many things and so I'm just gonna do this testConfig.

So we'll do something simple we'll go args which is a string or an array of strings the operation you expect, right? Operation which is gonna be an op projector.Operation. There we go. I tried to hold out cuz I don't want to do too much of that. And of course, we need to do a testing.t, there we go.

So as long as we do that, we should be able to effectively copy and paste all this code. So I'm just gonna go like this, delete all that, put it right here, fantastic. Take that paste it, I don't know why did that and make sure that the config.Operation equals the operation that I expected which is operation.

Operation expected was this but got that fantastic, right? Config.Operation and operation, right? Wrong way. You definitely don't wanna do that, that will lead to a lot of confusion. Awesome, right? So now I should just go to go testConfig and pass in t an empty string array, and of course print, right?

Projector print. Awesome, that's what I wanted to do. Erase this, Foo, let's just erase that for a second. Let's make sure everything's going great, awesome. We got a fail. What is our fail? No test file, no test files. Wait, missing this, of course, classic really. Really just a classic right there.

It's somewhere in here, it's right there, there we go. That's what I was missing, had a colon. And now we have a fail test 32 expected 0, but got 0, so that does feel a little emotionally difficult to handle right now. And I think the main problem was is that I was doing an equal sign there.

And so you don't wanna do that. When you're testing, you don't want to be so positive and then throw an error, it just feels wrong to do. For those that didn't just catch up because they're looking at their code, it was double equal. So I was actually testing for what I wanted to see and then throwing an error.

All right, fantastic, TestConfig, TestConfig. YAP, that Y-A-P, I always say, yap but if you're wondering what I mean, Y stands for Yank, A stands for Around, P stands for Paragraph. YAP, I just copied that. Now I can just paste with P, fantastic. YAP but you can also DAP it, you can DAP on the haters, delete around paragraph, there we go, fp do a little AddKeyValue.

Yes, my wife is exhausted of me at some times. All right bar, FP Add awesome. Once, I've done everything correctly, we should be able to see it, but we did not see it. So what just happened there? I think we have ourselves potentially an actual issue, right? Because I would've expected to see this, but you know what we didn't do.

We have this. Uh-oh, we might have ourselves a little bit of an interesting problem going on right here, is that I didn't do the args throw out here. So, we're actually testing nothing. Again, danger of doing meta type programming. There we go, everything's good. We rerun it, now we have a new one expected args to be an empty string but dot foo bar line 28.

All right, so we don't expect it to be an empty string. Expected args to be % plus v but got that, there we go. All right, so let's see what we did, let's see what we got went wrong here. We expected add foo bar but got foo bar.

No, so part of the problems of abstracting you always find these things in, right? So now we are gonna have to do something even more annoying, args, there we go. Now we have the expected and there we go we can do that. Shockingly, I did effectively plan for this to be part of the course because I did wanna show really the reason why you should be using Go copy is that you end up doing this kind of stuff all the time in Go test if you don't use an additional library.

Strongly recommend using an additional library, but I just feel like it's really good to see kinda what the language itself offers before you go off and do that. So I'd expect the same thing, awesome, expect the same thing here. All right, there we go. And of course remove that.

Awesome, so everything should now be fantastic. We're good to go. We've just created our own slightly verbose testing library here fa, do the exact same thing remove key, rm, oopsies, dt that, there we go. And of course that means we should just have foo, right? And it should be an ad.

So long as everyone followed along, we all have effectively the same code. I could have in lined all this. Which one's better? Is this better to have? Well, one thing that kind of stinks with these libraries that don't give you a full stack trace right away. When we had an error, you kind of had to deduce where did this failure come from?

Now, if you're doing something that's checking multiple times, you're just simply gonna get this single line failure. But it could have been checked three times in your function. So now you're off to, well, I guess I'm gonna print to find which one's the actual failing one. If you can't figure out why.

If you can't figure out from this, right? I just happen to be able to figure that out cuz we only call it one, so it's pretty easy to figure that out. So always kind of a danger, one nice part about Jess is that Jess gives you that full kinda stack trace so you know exactly where and how these things went wrong.

Overall though, pretty good experience, right? Happy to have this, I'm very happy to have a language that supports it. It's fast. I don't have to wait a long time, and it kind of drives you to make your stuff really fast. All right, let's move on to rust and do some beautiful rust testing.

Let's see, I'm on look at this one, did I do this? No, nice look in my other version, I only did two tests and I didn't obstruct anything. Okay fantastic.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now