Polyglot Programming: TypeScript, Go, & Rust

Projector CLI App Testing in Go



Polyglot Programming: TypeScript, Go, & Rust

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

The "Projector CLI App 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 demonstrates creating a series of tests for the Go Projector CLI application. The tests check for the correct present working directory, restrictions on setting and deleting data, and the ability to retrieve data from various directory levels.


Transcript from the "Projector CLI App Testing in Go" Lesson

>> All right, so now, of course, what's the vegetables in this case? Well, it's testing, let's do some testing. We're gonna do something very, very similar. We'll do projector_test.go, and we're gonna do the whole package projector_test. Remember, we don't want to test private stuff, we just wanna test the public stuff, make sure it all works out.

And we already know ahead of time that we're gonna do something very, very similar that we did in JavaScript, which is to create a easy way to create a projector and an easy way to create the data, right? Just make that part pretty fast. So let's do that now.

Let's go func getData. And this thing's just gonna return a data, right? And we'll do one of those, and we have to go projector.Data, cuz remember, we don't exist in the same file, correct? And it's not an ampersand sign. It's that, because it's a pointer. There we go, importing your file, we're looking good.

We're gonna be doing testing. So to catch everyone back up, I'm gonna jump over to my previous project, the TypeScript one, and go to projector right here, and look at what I do right here. I create a projector. I create the data and then I can create the projector itself, makes it really simple, because then my testing logic looks like this.

That's pretty nice, right? I like that, that's kind of what I want to see, so we'll do the same thing. All right, we're gonna jump in here and we're gonna create a piece of data. So let's go like this, let's return a projector data. And inside of here, we need a projector, because any JSON encoded field must be exported.

That's just a requirement, that's why we can just directly manipulate it right here. So we need to do, of course, that good old fashioned map[string]map[string][string], right? Very classic, I almost named my kid that, in fact, it's such a standard operation here. So the nice part about this is that Go, I think, does a really great job for being a strict kinda type language, being able to handle these things, cuz watch this.

I can go like this, that, and then I can just do this. And the reason why that works out is it's able to infer that this thing is supposed to be a map of [string]string. So I could explicitly do [string]string and then do that, but it's gonna say, hey, we already know, we already know what you're talking about, you don't need to do that.

I'm also surprised it doesn't just had error on you because that just seems like a more Go type way of doing things. So let's do the exact same thing. In fact, we're gonna do such the exact same thing. I'm actually gonna hop over to my previous project here and just make sure that all the data is effectively one to one, because I don't want to mess any of that up.

Awesome, so I'm gonna hop back. Delete that and paste it in. There we go. We have the same data, and look how awesome that was also. I was able to just directly copy and paste it. So syntax, not too much different, I can really get behind that. I like anything that has ease of creation, cuz I feel like, especially when it comes to testing, it's just such a pain if you don't have this.

All right, so let's create the old getProjector. It's gonna return a projector back out. And of course, we need to provide a present working directory, which will be a string, cuz remember, just like our previous one, what did we do? Well, we went down here and we just inlined a config, cuz during these operations, we only need a present working directory.

We don't care about the args, we don't care about the operation, we don't care about the present working directory, or we do care about that. We don't care about the config. So we could create effectively the identical thing. In fact, I could even yank this entire thing, bring it in here, paste it in, and just do a slight modification, and it's going to work.

Projector dot that, awesome, and then let's do a projector.Config is this. And so, of course, Args is gonna be an empty string array, so string that. Operation, of course, is just gonna be projector.print. Present working directory, of course, can be present working directory, pwd, and config can pretty much be that.

And so what do we have left? Just capitalizing these things. And now, the data, the last part, if you remember from our previous one, we also allowed parsing in the data. So we'll just do that here, we'll go data is, of course a data, a projector.Data, right? And there we go, too many arguments in this projector.

Interesting, must have messed that up, because I don't have little right here. And so that means this data is data, right? I just realized I was trying to new it up an object. You can't really new this up like an object, right, because it's actually just a config.

There we go, my bad on that one. And of course, yep, just standard. Is it that? We can't just new this up, what do we do wrong here? Well, look at what we did wrong. This is a classic Go problem, right? You'll want to be able to new these things up, but if you don't have access to these privates, you can't do that.

So we have one of two ways in which we can solve this. I can either solve this by making these things public. But if you really don't wanna do that, another simple way is just simply, just createProjector, right? And this thing can take in a config, and it can take in a data, and it can return back out a projector.

So much like our little default one down here, we can also have one that takes in a data, which means our default one could actually use this exact same thing. So let me go, oopsies, I'll actually changed files. So our default one could actually just use this method if we really wanted to.

And so that just will simply return a projector, which needs to be this thing with config: config and data: data, and always do that. And so now, we have this nice clean thing. So the thing we built over in our test file, we just can't do, right? We're not able to access these variables, and we kinda wanna keep it that way.

We really wanna try to keep this a little bit more clean. And so instead of doing a projector, we'll just do CreateProjector. And we don't need an ampersand sign. And so from here, we can just pass in a config, right? And then we can just pass in a data.

And so now, we just used a method to do it instead, right? So that feels pretty good. Hopefully everyone kinda followed along with that transformation. I wanted everyone to kinda feel some of the pain when it comes to creating these things. If you goof up on the parameters, how can you get around it?

And I do think it's probably best to keep those private if we were to build this properly or in a larger system, cuz we don't really want people accessing those, right? It's just not how our program was intended to be designed. So there we go. So now, it's just time to do our actual testing.

So luckily, our LSP is gonna help us here. I'm gonna start it off with the word test. And it's gonna be like, yeah, don't worry, I know what you want, you want this type of method, awesome. So we need to start testing our projector. So we're gonna do a little bit less testing just because it's so very verbose in Go.

We could create more meta methods if we wanted to, but again, using a library to help you, there's plenty of different libraries you can try out, lots of cool assertion libraries out there. So we're just gonna go like this, TestGetValue. We'll just do a get value test, we'll do a set value test, and a remove value test.

We'll just kinda keep it pretty light just for the sake of time, speed, and momentum. So let's start off by creating a projector. So projector equals, did I call getProjector? I think I did. And here, all we have to provide on this one, of course, is a string and the projector data.

So let's go data and go getData. Awesome, and so now all we have to do is do our present working directory of /foo/bar, cuz that's kind of what we did, and then pass in our data. And now, we actually have a projector, right? So now, we can do the test itself.

So we could abstract this out pretty easily, but we're just not going to. So we're gonna go like this, value, ok:= proj.GetValue, and let's pass in foo. So we should expect bar, which means we should be able to like this. If not okay, we can t.Error(expected to find value\ foo).

There's actually a way to quote these, I know there's an operation that do a quick quoting, I just don't know it off the top of my head. If value does not equal bar 3, well, we have the exact same thing, right? Expected to find bar 3 but received %v, which is gonna just be value, right?

Beautiful, right, we did exactly what we wanted to do. Now, again, we could probably just turn this into a method, make it a little bit easier, but we're gonna keep the testing somewhat light here. Let's do one more. I can already feel myself wanting to abstract it, I can't even help it.

I just have this just constant desire to abstract things, and I try to fight it, and I usually don't make it that long. So let's just abstract it. Let's just do it, we gotta do it. So let's go like this. We'll just call it a test, that's gonna take in a t testing, T, I know I say we're not gonna do it, and then look at me now, we're doing it.

We're gonna have a projector, we can call proj, which is gonna be a projector. Awesome, and then we need to grab out the key that we expect, right? We have a key we want, we have a value we expect out of it, and so let's do that. We won't build in the negative test case into here, cuz then it gets super confusing if we also consider, okay, then all of a sudden, you're just building such a complex function, we're not gonna do that.

So let's just grab this, delete it out of here, bring it up to test, and we're gonna go key. For those that are wondering how did I just do that sweet text editing move, well, first off we do cover it in the VIM Front End Masters course. But what I just did there is my cursor is on the same line as the string, I hit a little c, meaning change, a little a, for around, and then double quotes.

So deleted the double quotes and everything inside of it, and then put me into insert mode, and now I can just type freely. Man, just great times, right? All right, so let's do that, I know that no one really actually cares, I care, okay? I care deeply. Let's call this thing v.

There we go. So if it's not okay, we expected to find value, it's a little percent sign. I know there is something that does that. Let's do that, awesome. And then of course, if value does not equal v, then we have another problem. We expected to find this, but we received that.

So we expected to find value, but we received this. There we go, pretty simple little test function right there. Let's do that, and so now we can go like this, test(t, proj, "foo", "bar 3". Beautiful, people. And so let's do a quick little test. Ooh, Go make Rust, knows my heart.

It just knows I wanna be there. We are not gonna be there. My goodness, what has happened? Well, my friends, we did not get this first try. Okay, if you have a water bottle next to you, please pour one out. I'm sorry, we didn't quite get it. So there we go.

We actually didn't put the f in there, it could not format the string. It was just expecting an error. I know, sad day, worst way to go out really. That's like forgetting to shut the door all the way and start driving just to realize one of your doors is slightly ajar, it's just painful.

Let's just verify that we can fall back, like our previous one. So I'll bring this more up into the middle, cuz honestly, it's pretty uncomfortable probably for people looking down at the very bottom of the screen. So let's do, I believe, it's fem, and I believe it is is_great, Frontend Masters is great.

There it is, look at that, it is great. And let's just do one more test to make sure that all works out. Boom, my goodness, but at least the logic was done first try. All right, so let's just do a quick set value, TestSetValue. Now, we're gonna do this, we're gonna grab this beautiful, wait, don't do that.

We're gonna grab this beautiful piece of code or we're gonna grab this beautiful projector. We're gonna test to make sure foo is bar 3, right? We'll do a little proj.SetValue, "foo", "bar 4". I'm not naming it and apparently adding semicolons. Man, Reddit is gonna be just so upset if I leave those semicolons in there, we don't want that.

So there we go. Look at that, that's all we really need to do. Did we do this correctly? I'm hoping that we have this all correct. Let's do one more set value that I think is gonna be pretty clever. Let's do a fem, is_super_great. We're supposed to be able to override values at this level, but not override values that are above us.

That's what we did in TypeScript. If we jump back over to TypeScript, you'll see that our set value does the exact same thing. They did better than great. I should have been better than that. All right, so is_super_great, slight deviation in code, but this should work out just fine.

I should go to grab that and go fem and is_super_great. Go in here, and did we get it, my goodness. That is so much first trying happening right now. We're gonna pretend that one little not first try didn't happen. Just to really triple check this and make sure I'm not crazy here and caused any sort of problems.

Let's go into here, use the same data, this is mutated data object right now. And let's take that and do one quick test, and make sure that fem just contains is_great, cuz that's what it originally contained at the root. Just making sure we didn't somehow overwrite something silly.

We know we didn't, but, hey, just in case someone new comes along, goes, this is a bug, no, it's an intended feature. I'd go right here and go RemoveValue, exact same thing, foo, bar. We can test it for its existence, we don't actually really need to do that.

Why test the test? But we're gonna do it, it feels good, it feels right, just is what it is. All right, so we're gonna do that and we're gonna reduce this down to two. Fantastic, that should work out. Then we'll do our other very nice test, which we're gonna try to delete fem, which should actually delete the fem at this level, cuz it's this thing, no, never mind, I was thinking we still have the same object from this.

There is no fem at this level, so it should delete nothing, which means that if I test it for Frontend Masters, I should see is_great, right? Everyone's on board, that seems about right. So let's give this a nice good testing My goodness. It's actually really hard to be this good at programming.

[LAUGH] Just what happens.

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