Category: Yii2

Today’s Objective(s)

Get acceptance test to pull url without having to hardcode (ie. use a function instead of ‘index.php?r=site/index’)

Write an acceptance test for functionality that uses a session.

Research best practices for acceptance/functional testing in Yii

Introduction

In yesterday’s acceptance test I ended up using the code:

$I->amOnPage('index.php?r=say/index');

Now even though it works, it’s a bit iffy to me. The reason for that is, what happens when I want to use pretty urls (ie. localhost/index.php?r=say/index becomes localhost/say)? I might end up having to rewrite my test, which in the current scenario would just mean changing one line of code. But if I had a complete website with over 50 acceptance tests, I wouldn’t want to manually go to each of those tests and change the page url…. I’m lazy like that

The solution

There is a Class BasePage that we can extend to simulate the page we want to display and we specify our route as a variable for use in an inherited static function. Let’s get cracking.

And voila! The acceptance tests works again, without having to hard code the URL. Delving further into the static function openBy for BasePage has shown that it could be replaced with the following:

$I->amOnPage(Yii::$app->getUrlManager()->createUrl(['say/index']));

But that doesn’t really read as well, and another benefit to the new page class that we have, we can define other functions to improve inserting data into forms and other items we might want to simplify.

Conclusion

This was a rather piss poor post, didn’t really have my head in it…. But we did cover creating a faux-page to use for better implementing complicated functions into our acceptance test. So progress has at least been made!

Today’s Objective(s)

Write a new acceptance test

Research best practices for acceptance/functional testing in Yii

Introduction

So today we write a new acceptance test. Nothing complicated, I just want to create a basic acceptance test to confirm a page shows the text ‘Hello’ and has a text field. And text inserted into the text field will be appended to the ‘Hello’ string.

Set the submit button to have an id of “say-button” (Since that is the button/link being clicked on in the acceptance test). And we append the string from SayModel to our ‘Hello’ string if there is a string to append.

Now you can see what I mean about ugly outputs But it works, and this does seem to be a very easy method to confirm that pages do what they are supposed to.

I do find myself a bit troubled over complicated scenarios, but think that’s just me overthinking it all. So believe I’ve already thought of my next acceptance test

Conclusion

This form of acceptance testing is excellent in my opinion, the test itself is written in such a way that it’s simple to understand at a glance and could become very powerful with the right implementation.

Upcoming Objective(s)

Get acceptance test to pull url without having to hardcode (ie. use a function instead of ‘index.php?r=site/index’)

Today’s Objective(s)

Research best practices for Unit testing in Yii

Research executing tests and viewing results from a browser

Research best practices for acceptance/functional testing in Yii

Introduction

I moved the objectives around for today (2 is now 1 and vice versa), this is because I’m not quite sure I want to worry much about executing my tests from a browser. I also think there are already implementations of this and doing it myself would be like reinventing the wheel, so I’ll modify it a bit for tomorrow to something a bit more useful.

On to the topic at hand! Best practices for Unit testing in Yii… now there seems to be varying opinions on unit testing, so I’ll make this piece my own opinion on the matter and the practices I’ll be setting out for myself to achieve.

What to Unit Test?

For Yii, I believe the only item that really requires unit testing is the models used. Yii has a fat model … model of the MVC architecture, meaning that the prime focus is to have the majority of logic inside of the model. To quote the guide:

In summary, models

may contain attributes to represent business data;

may contain validation rules to ensure the data validity and integrity;

may contain methods implementing business logic;

should NOT directly access request, session, or any other environmental data. These data should be injected by controllers into models;

should avoid embedding HTML or other presentational code – this is better done in views;

That makes models the only place to do proper unit testing from what I can tell, and then in the unit tests we mock the injections from the controller (Like user info/requests). And this in turn makes for very clean and easy to read Controllers

What is the best process to implement Unit Tests

MY opinion on this is create the Unit test before the actual code and then do the code. This is both an excellent form of development, since it both gives the developer pure clarity of what needs to be done, and means that further down the line the unit test can be used to ensure that nothing has broken over the course of further development (And it’s there to be used, instead of needing to make it and struggling to remember what the code is really supposed to do).

Of course keeping to this practice proves insanely difficult as I find myself constantly just going straight to the coding and only remembering the actual unit test 2-3 functions later, but I’m making a very dedicated effort to change that habit. Believe it takes a lot of trial and error to find the sweet spot where creating the unit test and doing the code becomes more useful. By this I mean that if the unit tests are too basic it could lead to multiple breaks in development to write another unit test, or the unit test could be too intricate and the resulting code could require multiple debugging sessions to get at why the unit test isn’t passing as expected.

But once that sweet spot is found, I believe it could be glorious! Saving a lot of pain and effort further down the line, so I will gladly trip and fall a few times now to allow myself to sprint uninterrupted in the future

And as with all testing, shouldn’t forget to include negative cases. I’ve seen it a lot where the “Happy day” cases are focused on and “Rainy day” cases end up causing floods (heh).

Conclusion

Unit tests are awesome and I believe a little misunderstood. The general feel I’ve gotten about Unit tests over the years has been bad… although this might have been from my lack of understanding them. But after what I’ve learnt so far, they can mean a whole lot less trouble for just a few simple lines of ‘extra’ code.

Well worth it in my opinion

Upcoming Objective(s)

Write a new acceptance test

Research best practices for acceptance/functional testing in Yii

Ps. You will notice that I’ve now completely removed the part of the objective for running/viewing tests in the browser. Will revisit the idea after covering acceptance and functional tests. Also number 2 on the above list had a spelling mistake (pracices)… shocking

Although I’ve come to realise that these are only for acceptance/functional tests, which are scheduled for learning further down the road. So I think for today I’ll focus on stubbing.

What is a stub?

A stub is basically a mock of a class that you would like to test in which you fake certain functions and make them return specific values to help with unit testing another function (At least this is how I’m understanding it at the moment). So for today I’m just going to override the functions we’ve already made and make them return values of my choosing and just test that those values are returned.

With the research I’ve done so far, I’ve found out that Codeception’s Stub class (A wrapper over PHPUnit mocking framework) doesn’t play well with Yii (Here are the issues on github #5210 and #1694), which is a shame. Based on the documentation and reference it could make things rather simple. But I must carry on with what works, and I’m sure using the basic PHPUnit mocks won’t be that bad.

And the test runs through successfully. This is very useful… although my initial thought that it would just override the specified functions and all other functions would still be usable was incorrect. The only functions that can be called from the mocked object are those specified AND any attempt to call other functions throws errors, which is extremely useful!

Now if your Class is reliant on another Class or object to complete it’s run through, you could mock that Class and specify exactly what should be returned from the function call. Allowing you to be able to predict precisely what your Class’s functions should return and you can specify that functions in the mocked object have to be called, further documentation on that can be found here: PHPUnit documentation on test-doubles.

Conclusion

A rather productive evening, on top of getting a basic understanding of PHPUnit’s mocking framework, I learned that my assumptions can be wrong and when wrong the truth can be even more useful

Today’s Objective(s)

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Research executing tests and viewing results from a browser

Research best practices for Unit testing in Yii

Research best pracices for acceptance/functional testing in Yii

Introduction

As fate would have it, I was in the completely wrong ball park with my stumbles on Thursday. The definition of the expect function I’ve been using in testModelBoolean isn’t defined in Codeception\Codeception, but rather in Codeception\Verify and for today’s post I’ll describe how I got about to figuring that out and list the assertions that can be done using the result of expect.

The how

Spent the greater portion of today working on another project, which I’ve actually restarted completely to incorporate some of the things I’ve learnt (It started out as a simple project, but has grown to something that could become buggy without proper testing and the layout was turning chaotic).

So I’ve been practicing writing Unit tests of my own for this project, in which I used a combination of basic asserts from PHPUnit and using the expect function. I used the following functions with success:

Of course the first one is a bit of a blunder, since I was using it to check the string but it’s a check that the type is string. I found that out when I tried “->contains(2)”, the test failed because of a type difference.

But I digress, after those successes I developed the theory that it was basically dropping the “assert” and lowering the case of the first letter (assertEquals), so I tried out “->internalType(‘array’)”. This gave a fatal error telling me that I had made a call to an undefined method “Codeception\Verify::internalType()”. Voila! There was the actual class staring me in the face.

Conclusion

I know I promised to make a list of the assertions that can be done, but will have to post that tomorrow… or rather later today, which is why I’m cutting this short. It’s supposed to be daily, and I’d like to keep it as close to that as possible

So we now know where the expect function comes from and with that can find out what exactly can be checked using it. I’ll get into that tomorrow/today.

Upcoming Objective(s)

List the assertions that can be done using the result of expect

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Today’s Objective(s)

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Research executing tests and viewing results from a browser

Research best practices for Unit testing in Yii

Research best pracices for acceptance/functional testing in Yii

Introduction

I’m really hoping that setting up debugging for PHP via CLI is easy since today is Friday and I would like to finish up early… let’s get straight into it.

At the moment I’m currently using PHP Storm (Using the 30 day trial to see if it’s something I would like to purchase, so far I’m on the fence since Netbeans does all that I currently need just as well as PHP Storm. But I’m going to try and expand my horizons and attempt to use everything PHP Storm has to offer before I make that decision), so this post will center mostly around using PHP Storm, although I think there will be some base PHP setup required as well.

Links

Links I find during the course of this post will be put in this section with a rating on usefulness:

The second answer shows promise, PHP Storm is stopping on the first line of executing codeception when I run “codecept run unit”. And it stops at my breakpoint! (I was about to say that it wasn’t stopping at my breakpoint, but I wasn’t running it from the tests folder )

Conclusion

Well that was easy enough, I now know how to debug a script that is being used in a console terminal. Although I don’t know how exactly, but I’m going to chalk this up as a win (Already started this an hour ago, fortunately hit that link just after starting to write this post to keep me going at it). Think I will have to delve deeper into xdebug to know more of the intricacies involved, but that’s out of scope for this topic.

Upcoming Objective(s)

Learn to debug console PHP application

Delve deeper into the “expect” function used in testModelBoolean()

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Today’s Objective(s)

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Research executing tests and viewing results from a browser

Research best practices for Unit testing in Yii

Research best pracices for acceptance/functional testing in Yii

Introduction

Today’s objective is to get a better understanding of the “expect” function from yesterday. The major reason I decided on this, because I found something interesting while I was going through it last night. Let me break it down.

Declared functions for expect

The following code is all coming from the Codeception github page.

Yesterday we found that the expect function is inherited and the UnitTester class used in Yii extends Codeception\Actor. That Actor class itself doesn’t have a definition for expect but it uses 2 traits Comment and Friend, looking into the Comment trait I found this:

So this expect accepts 1 argument $prediction and then calls the comment function while prepending “I expect ” to the argument, the result of which is returned. Just as a quick refresher let me show the code that we’re trying to dig into:

expect('argument is true, result should be false', $model->bool(true))
->false();

As you can see, in this function call we’re passing 2 arguments and then running the function false off of the returned object. This blows my mind and nags at me that I’ve found the wrong function definition. What I would really like to do in this case is debug and step into the function call… but my knowledge of debugging only covers web functions and not console functions, believe I’ll add it to the objectives list.

Anyway, I thought I’d dig deeper into this declaration to see where it goes. Above I also included the definition of the comment function, the result of which is being returned by our expect function. The comment function then calls the comment function of the class’s ($this) scenario object and returns the class itself. So let me find the definition of the scenario object and it’s comment function:

Here we have the declaration of $scenario and find out that it it’s class is \Codeception\Scenario with that knowledge I’m off to find the definition of comment. I think I’ll do this a lot faster if I just do it all in a code box with comments on lines (//)

I wanted to find the declaration of the runStep function in the test variable of the Scenario class, but found that the class for test (\Codeception\TestCase) is an extension of PHPUnit_Framework_TestCase and implements PHPUnit_Framework_SelfDescribing.

I still want to go further into this, but I’ve run out of time. And I feel that this method is taking too long, I’ll rearrange the objectives for tomorrow and approach this the day after next with a proper debug stepping session.

Conclusion

About the only major thing learned today was that browsing through a GitHub repository online while hunting down what a function actually does, is a bit of a time sink. Hopefully improve on this in the future.

Upcoming Objective(s)

Learn to debug console PHP application

Delve deeper into the “expect” function used in testModelBoolean()

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Research executing tests and viewing results from a browser

Research best practices for Unit testing in Yii

Research best pracices for acceptance/functional testing in Yii

Ps. I know I’m playing it rather fast and loose with the definition of ‘object’/’class’ for a good portion of this, but I’m more interested in understanding the code behind it than being pedantic… that being said it does bug me that I’ve done it anyway.

Today’s Objective(s)

Explore and understand the basic functions of basic test methods (Understand what is going on in testModelBoolean() function to be precise).

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Introduction

So today’s highest priority item is exploring the test methods, ie. how does the TestCase Class tick exactly. Not many actions involved in this, so let’s delve into a few of the key functions that I’m not quite understanding in this code:

class MySimpleModelTest extends TestCase
{
use Specify;
public function testModelBoolean()
{
$model = new MySimpleModel();
$this->specify('model bool function should return a boolean opposite to boolean passed', function () use ($model) {
expect('argument is true, result should be false', $model->bool(true))->false();
expect('argument is false, result should be true', $model->bool(false))->true();
});
}
}

$this->specify

Now, from my very brief overviews of documentation in Codeception I can tell you that this function is best use to describe the test that you are running (Which is a good thing and believe I’ll keep this around), but I’d like to get a grip on the callback part of this function.

I’m guessing from reading the code above that the function’s basic parameters are a string to describe the test being performed, and an anonymous function that does the actual test… what’s confusing me out of this is why it needs to “use ($model)” in the callback function definition and I believe this is a lack in basic PHP education on my part, which I will now read up on. [time passes]

From reading this documentation on anonymous functions (under example #3) I can see that defining an anonymous function with the ‘use’ “language construct” (Learnt a new term there) allows the function to use the variable as it’s own and as it stands in the Parent scope (Our function ‘testModelBoolean’). I have a few further questions on this matter, but that’s out of scope of this article.

expect

This function looks like a rather interesting assertion function to me:

expect('argument is true, result should be false', $model->bool(true))->false();

The definition I’m getting from reading that code is that it accepts a string to describe what it’s expecting and then the function that is to be tested. It then returns some form of Class or Interface that can then be used to check the returned value…. let me try and find out an actual definition of this. [time passes]

So after a bit of searching… trying to google “expect” is a bit of a waste of time, I looked into the code that I have and found that expect is being inherited from \Codeception\Actor and went off to find that the function is declared in the Comment trait that the Actor class uses and the declaration seems to only accept one argument which is a prediction… I’ve found some more interesting stuff in there, but think I’ll leave this here for now and continue tomorrow

Conclusion

Not a lot to add tonight, but I have learnt a basic of PHP which is useful (heh) and believe I’m about to be jumping down a rabbit hole while trying to understand this ‘expect’ function which should be a lot of fun!

Upcoming Objective(s)

Explore and understand the basic functions of basic test methods (Understand what is going on in testModelBoolean() function to be precise).

Delve deeper into the “expect” function used in testModelBoolean()

Try to use the entire $I->wantTo(‘load all values’); style of Codeception

Research executing tests and viewing results from a browser

Research best practices for Unit testing in Yii

Research best pracices for acceptance/functional testing in Yii

(I added the questions I had from Day 3 so that they wouldn’t get lost)

And the test completes without issue. I have a sneaking suspicion that there is a better way to do those tests, but this gets the job done and I’ll keep improving as I go.

Conclusion

With unit tests passing I must admit that I quite like this process of development. It might add a bit more time, but it’s an excellent method of documenting methods and once the work has been done it can be used to keep piece of mind when changes have been made.

Upcoming Objective(s)

Update testModelArray() unit test to confirm that returned object is an array with correct values

Explore and understand the basic functions of basic test methods (Understand what is going on in testModelBoolean() function to be precise).

Try to use the entire $I->wantTo(‘load all values’); style of Codeception