در حال بارگذاری ...

Project and Composer Setup0:00

Alright, let's dive right in to the basics of unit testing. I have an empty directory here for our project, and we'll split these into examples. So example 1, and we'll open this in my editor. Now if you're working on a Laravel app, the Composer autoloading and the PHP unit dependencies are ready to go. But otherwise, you'll have to wire that up manually. And we'll do that together, this lesson alone. So our source files will go here, and then our test directory will go here. Okay, next, let's set up Composer. composer init, and I'm just going to accept the defaults.

Okay, next, let's set up Composer. composer init, and I'm just going to accept the defaults. There we go. Okay, so really quick, we're going to set up autoloading, and we always use PSR-4 autoloading. And we'll say, my app namespace, that's what I'll call it, will go in the src directory. Next, I'm going to do another one here, but only for development. In this case, our test namespace will go into the tests directory. And then finally, the only remaining step is to pull in PHPUnit. We can do that with composer require. There we go.

Writing First PHPUnit Test1:03

We can do that with Composer, require. There we go. We're all set to go. So we're going to begin with a test. For this example, we're going to work with parsing tags. So I'm going to call it tagParser, and then we always add test to the end of the class name. Now, think of a test as a description, or an example of what the thing you're working with does. So we're working with the tagParser here.

with does. So we're working with the tagParser here. Let's provide an example of what it does, how it should work. In this case, let's say you have tags for a blog, and you keep it really simple. As part of the form, you add a comma-separated list, personal, money, family, things like that. If you put that into a form input, behind the scenes, we want to turn that into an array, and then maybe it gets thrown in the database. So how about it parses a comma-separated list of tags. That's an example of what it does.

So how about it parses a comma-separated list of tags. That's an example of what it does. So let's try it out. We're going to create some class. It doesn't exist yet, and that's okay. This is a technique known as test-driven development. It's a fancy term that just translates to writing your tests before your main code. So imagine you have a class called TagParser, and imagine you call some method parse on it. And imagine you give it a string or a comma-separated list, personal, money, family.

it. And imagine you give it a string or a comma-separated list, personal, money, family. If you run that code, what do I expect it to return? Well, I expect it to return an array, personal, money, family. This is an example of how it should work. It doesn't work right now, but this is how it should work. So now how do we tell phpUnit this should match this? They should be the same. Well, I want some kind of assertSame method, but I don't see it here yet, even though I've pulled in phpUnit.

Running Tests and Fixing Failures2:57

Well, I want some kind of assertSame method, but I don't see it here yet, even though I've pulled in phpunit. And that's because we're not extending from phpunit's TestCase class. Let's do that now. All of your phpunit tests should extend this class. So now notice if we run this again, we get all sorts of assertions. assertArrayHasKey, assertCount, assertContains, assertEquals, assertSame, which is what we want in this case. Don't let this be overwhelming, as with everything. I've been coding for many years, and I probably haven't used 80% of these assertions.

Don't let this be overwhelming, as with everything. I've been coding for many years, and I probably haven't used 80% of these assertions. A small handful will take you a really long way. But it's good to know we have them. So in our case, I want the expected to be the same as the result. Okay, so now let's give it a run. I'll bring up the terminal, and we can say vendor/phpunit, and then we provide a path to our test directory. Okay, now it's going to run all of the tests within that folder. But it says it couldn't find any tests, but we know we have some right here.

Okay, now it's going to run all of the tests within that folder. But it says it couldn't find any tests, but we know we have some right here. So here's an important thing to remember. We need to signal to phpunit which method is a test, and which method is maybe a helper, like for creating a User or something like that. So we do that by prefixing the string test. Now phpunit will correctly identify this as a test it should execute. So if we run it again, now it found the test, and it tried to execute it. But of course it fails. But real quick, notice it's all white, and a little difficult to read.

But of course it fails. But real quick, notice it's all white, and a little difficult to read. Let's run it again, and I will add the --colors flag. Okay, that helps a little bit. So there was one test found, and that test failed, because there is no class called TagParser. That's our first step. So in my src directory, we'll add a new class called TagParser. All right, we run the test again. But it still fails. But notice it's trying to load a test TagParser class, but we have an app TagParser.

But it still fails. But notice it's trying to load a test TagParser class, but we have an app TagParser. That's because we haven't updated our namespace. So let's do that now. We bring it in up there, and we run it again. Okay, so now it found the test, and then it found the TagParser, but then it errored out. You tried to call an undefined method parse. That's the next step. So notice it's almost like a game, where you keep following the steps. Change some code, run the test again, and then move on to the next step.

So notice it's almost like a game, where you keep following the steps. Change some code, run the test again, and then move on to the next step. Now it found the method, and it called it, but null was returned, of course. And it's saying, hey, you expected this array here, but null was returned. So your test has failed. So why don't we do this? I'm going to come back and tweak this a little bit, just to make it a little simpler. And then we'll expand. If you only gave it a single tag, that should still return an array with one item. Okay, so that's still basically the same thing, but we will work off of that.

And that's okay. The goal is to get into the mindset of writing the least amount of code to make the test pass. And only when you introduce new requirements and new tests, do you change that code, and you make it more complex. Kind of a general mindset to get into. But yeah, if nothing else, you have your first passing test. And on top of that, you follow TDD to get to green. And this is it. This is a basic unit test.

Unit vs End-to-End Tests6:57

And this is it. This is a basic unit test. A unit test focuses on one unit of your application. Notice that nowhere here did I say, open a browser and visit this page, and then fill out the form and type in this string for the tags. And when you submit the form, I should expect to see that. That is known as an end-to-end test. You're testing the full spectrum of the application. A unit test, on the other hand, is often more isolated and targeted on a single class or maybe a small collection of classes.

A unit test, on the other hand, is often more isolated and targeted on a single class or maybe a small collection of classes. And again, just think of that term, unit. We are testing a specific unit of our application, not the whole thing. We're not hitting APIs. We're not on a network. We're not doing any of that. We're just focusing on one class. Anyways, let's keep going just for a few more minutes. This test is no longer quite right.

Okay, let's run our test again. Ah, it fails. But real quick, if we scroll up, notice that little period here. So a period followed by an F. When phpUnit displays a period, think of that as, yep, we're good to go. That passed. An F, of course, means a failure. So what we see here is there were two tests, but one of them failed. And this is that failure. But sometimes, you're only interested in this test, and you don't want to run your full

Filtering Test Runs8:48

And this is that failure. But sometimes, you're only interested in this test, and you don't want to run your full suite. Here's a little tip. You could copy the class name itself, or a single method name, and then run your test again, but add this filter option, or flag. Filter it down to only the tests that match this. So now, we only have a single test that fails. Okay, next notice the minus and positive, or minus and plus. The minus is what we expected.

Okay, next notice the minus and positive, or minus and plus. The minus is what we expected. I expected an array. The positive is what we got. We got an array with one item that had this full string, and that's because, of course, we didn't split it up. All right, we have our next step. Let's come back here and see if we can make it work. What if we used php's explode function? The delimiter will be a comma and a space, and then our string will be tags.

What if we used php's explode function? The delimiter will be a comma and a space, and then our string will be tags. That should return an array. So we give it a run, and it passes. But remember, be careful, only that single test passed. So if you want to test everything again, let's remove the filter. And now both of those pass. Perfect. What else? Well, maybe, hmm, notice that we have a comma and a space, but maybe we could do another

Improving Parsing with Regex10:31

So that's not ideal. When you submit the form, you would think, well, this should work, and this should work as well. But it wouldn't. All right, let's see if we can fix this. I don't think I can use explode anymore, because I really want to say this space here that's highlighted, look for that, or don't. It's optional. So maybe I should switch to a regular expression. I can use php's preg_split function for that.

So maybe I should switch to a regular expression. I can use php's preg_split function for that. We need to give it a pattern and the subject. The pattern is going to be, well, here's what we had before, a comma and a space, and here's the list of tags. But yeah, it's still going to fail. Exact same thing. But now I'm going to add a question mark here. This is a regular expression symbol that says the previous character or set is optional. So now what we're saying is look for a comma and then a space, but that space is optional.

Now multiple assertions in a test are okay. There's no hard rules here, but as a general rule, you will often see recommendations to only have a single assertion per test, and this is mostly meant as a guide to keep you from doing things like this, where you just keep duplicating over and over for the various assertions and expectations you have. What ends up happening is this becomes a big pain because let's say you have an error here. For some reason, this assertion fails. It gets very confusing. And when you start debugging, you'll end up doing things like this, like, okay, let's just comment all of that out, comment all of this, and let's just focus on this layer.

So I'm going to bring this back to what we had originally. And I don't think this is too bad, but yeah, if you want, add another test where you would say commas are optional. And now I could grab all of that, instantiate our class, and I think we're all set. Let's just remove the spaces. Did I do that earlier? I hope I did. But either way, we're at green. Perfect. So now, a simple enough example, but let's say you also want to accept a pipe.

Perfect. So now, a simple enough example, but let's say you also want to accept a pipe. Okay. Well, and this is very common, you'll often see pipe separators. For example, if you work with Laravel, you can separate your validation rules by a pipe. So we'll say, how about this? It parses a not comma, but a pipe-separated list of tags. All right? Same thing. Given I have a tag parser, and given I call a parse method where I split everything with

Same thing. Given I have a tag parser, and given I call a parse method where I split everything with a pipe, I expect this result. Okay. Let's copy that method, run our test again, but filter it down to only that method. Oh, it fails. Okay, let's make it pass. We come on back, and now it sounds like the comma could be a pipe. So with regular expressions, we can use character classes. And that's where you surround it with a bracket, with brackets, and then I can provide a list.

So with regular expressions, we can use character classes. And that's where you surround it with a bracket, with brackets, and then I can provide a list of characters that are acceptable. So think of this as saying, I expect one of either a comma or a pipe. Then I expect a space or not. That space is optional. So let's give it a run. Oh, it still fails. I thought it would work. But I expected personal, money, family, and I actually got personal and a space.

And then maybe we'll say, we're going to map over this array, and we'll give it tags here. And for each tag, remember, in one case, it was personal and a space. Let's just trim it. Okay, so that might be a long-form way of solving this problem. But I think it will work, and it does. Now one thing we might do is switch this to short functions. So if you're on a supported php version, you could say array_map function tag, and then just return the trimmed version of that tag. So that would be an option as well. We could give it a run, and it still passes.

So that would be an option as well. We could give it a run, and it still passes. And just to be clear, I'm on PHP 7.4. That at least turns it into a one-liner. So anyways, that works, but our tests are passing, so maybe we can play with this a little more. Do I really have to map over this array just to trim each of the items? It sounds like we're just saying, look for a space or not, and then a pipe, and then look for another space or not. So we have the back end there of a space or not, but we don't have one at the beginning.

Test spaces are optional. We already assert that that will work. So this might be a good use case for more than one assertion. So again, notice there's no hard and fast rules. There's general guidelines. Write as few assertions as possible. But if it makes sense to have more than one, go ahead. That's totally fine. And I'm going to do that here. If I try a comma-separated list or a pipe-separated list with no spaces, this should all work.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟