Before moving on to Codeception and PHP, we should cover the basics and start by explaining why we need testing in applications in the first place. Perhaps we could complete a project without wasting time on tests, at least this time?

Sure, you don’t need tests for everything; for example, when you want to build yet another homepage. You probably don’t need tests when your project contains static pages linked by one router.

However, you definitely do need testing when:

  • Your team uses BDD/TDD.
  • Your Git repo contains more than a couple commits.
  • You are a proper professional, working on a serious project.

You can excuse yourself by saying that you already have a dedicated test department, a group of people who conduct tests and write new ones when needed. But, can you imagine how long bug fixing will take after you add new functionality to your project?

What Does Testing Solve?

First, let’s decide what sort of problems may be solved through testing. You can’t get rid of all your errors with testing, but you can describe expected behavior in test cases. The errors might be inside your test cases. Spaghetti code remains spaghetti code even when you use test cases.

However, you can be sure that your code will be changed afterward (by fixing errors, or adding new features), so your code still will be free of errors described in the test. Besides, even well-written tests may sometimes be used in documentation because there you can see how typical scenarios unfold and check expected behavior. We can say that testing is a small but crucial investment in the future.

So what sort of tests can we employ?

Basic unit tests usually aren't enough. They need to be backed by integrational, functional, and acceptance testing.

Basic unit tests usually aren't enough. They need to be backed by integrational, functional, and acceptance testing.
  • Unit tests: Low-level tests that check small pieces of your code - your class’ methods isolated from other code.
  • Integrational testing: Integrational tests check a part of your application, they may contain several classes or methods, but should be restricted to one feature. This test should also check how different classes are interacting.
  • Functional testing: Tests specific requests to your application: browser response, database changes and so on.
  • Acceptance testing: In most cases acceptance testing means checking if the application meets all client requirements.

To clarify, let’s say we illustrate the process with something tangible, such as a building. A building is composed of small blocks that form walls. Each brick has to meet specified requirements; it has to withstand the required load, have a specific volume and shape, and so on. These are unit tests. The idea of integrational tests is to check how tightly and accurately the bricks adhere to each other, how they’re integrated into a certain element of the building. Functional tests can be likened to tests on a single wall of the building, to check whether or not the interior is protected from the elements, and whether or not it possible to see the sun through the window. Acceptance testing involves testing the entire building as a complete product: Open the door, go inside, shut the door, switch on the light, climb to the second floor and take a look at the garden outside the building.

Meet Codeception

However, this division is conditional and sometimes it is difficult to resist the temptation of mixing different kinds of tests.

Many developers use unit tests and claim that’s enough. I used to be one such developer; I found using different systems for different types of tests too difficult and time-consuming. A while ago, I decided to find something more useful than PHPUnit; I wanted to be better at testing my code, but I didn’t want to read and learn tons of documentation and look for pitfalls. That’s how I discovered Codeception. At first, I was skeptical, as we often are when it comes to something new (this project is five years old, so technically, it can’t be considered “new”), but after playing around with it for a couple of days, I concluded Codeception is a very useful and powerful system.

So how do you install Codeception? It is as simple as it gets:

$ composer require "codeception/codeception"
$ php vendor/bin/codecept bootstrap

After installing, you will find a new folder named tests in your project, and there will be some subfolders named acceptance, functional and unit. It looks like we can start writing our tests. Cool, but what’s next?

Now, try to add a standard acceptance Hello World test.

$ php vendor/bin/codecept generate:cept acceptance HelloWorld

Now, we get an acceptance test file tests/acceptance/HelloWorldCept.php, with the following content:

<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('perform actions and see result');

The default variable, named $I, is not just a letter; it is a character. What conducts the tests? The tester, obviously. This tester opens your website’s page or class, does something with it, and shows you the end-result of its actions. You will see what worked and what went wrong. That’s why this object is named $I and why it contains methods called wantTo(), see() or amOnPage().

So, let’s think like a tester would about ways of checking a page’s operability. The first approach is to open the page and search for a phrase. It proves that the page is available to visitors.

This should be easy:

<?php
$I->amOnPage('/');
$I->see('Welcome');

We can use this command to run Codeception’s tests:

$ php vendor/bin/codecept run

We see immediately that something is wrong. At first glance, it appears that the message is too long and unclear, but when we look more closely, everything becomes obvious.

Whops! Something went wrong. That's the whole point of testing. Check out the message, identify the error, and learn from your mistakes.

Whops! Something went wrong. That's the whole point of testing. Check out the message, identify the error, and learn from your mistakes.

We had one test, Acceptance, and it detected an error:

Acceptance Tests (1)
Perform actions and see result (HelloWorldCept)                                                             Error

----------
1) Failed to perform actions and see result in HelloWorldCept (tests/acceptance/HelloWorldCept .php)
[GuzzleHttp\Exception\ConnectException] cURL error 6: Could not resolve host: localhost (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) 

This is the culprit: localhost is not available.

And here are the scenario steps of our test:

 1. $I->amOnPage("/")

Ok, let’s open tests/acceptance.suite.yml and change url: http://localhost/to something that is actually available. In my case, it is my local test host, url: https://local.codeception-article.com/

Run the test again and this is what you should end up with:

Acceptance Tests (1) ---------------------------------------------------------------------------------------
Perform actions and   result (HelloWorldCept)                                                             Ok

Hooray! Our first successful test!

Of course, amOnPage() is not the only testing method available, we merely singled it out for our example. All Codeception test methods can be divided into the following groups:

  • Interaction with page: fillField(), selectOption(), submitForm(), click()
  • Assertions. see(), dontSee(), seeElement(), seeInCurrentUrl(), seeCheckboxIsChecked(), seeInField(), seeLink(). To all these methods you can add a suffix and use it when you need a method that will not interrupt the test scenario when something cannot be found.
  • Cookie methods: setCookie(), grabCookie(), seeCookie()
  • Comment and description of test scenarios: amGoingTo(), wantTo(), expect(). Use these methods to get well commented and described tests, which will help you remember the goals of the test.

So, if we were to test the password-reset email page, we could do it this way:

<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('Test forgotten password functionality');
$I->amOnPage('/forgotten')
$I->see('Enter email');
$I->fillField('email', 'incorrect@email.com');
$I->click('Continue');
$I->expect('Reset password link not sent for incorrect email');
$I->see('Email is incorrect, try again');
$I->amGoingTo('Fill correct email and get link');
$I->see('Enter email');
$I->fillField('email', 'correct@email.com');
$I->click('Continue');
$I->expect('Reset password link sent for correct email');
$I->see('Please check your email for next instructions');

It looks like this should do it, but what if there are some Ajax-loaded parts on the page? Can we test a page like that? The answer is that Codeception uses PhpBrowser, based on Symfony BrowserKit and Guzzle, by default. It is simple, fast and you need only curl to use it.

You could also use Selenium and test pages with real browsers. Yes, it will be slower, but you will be able to test JavaScript, as well.

First, you need to install the Selenium driver, change acceptance.suite.yml and rebuild the AcceptanceTester class. After this, you can use methods wait() and waitForElement(). And, more interestingly, you will be able to save your time and resources by using methods saveSessionSnapshot() and loadSessionSnapshot(). This method allows you to store the session state and start new tests in earlier sessions. This is useful in some situations, for instance, in the test authorization process.

So, we end up with a simple, yet powerful, ability to test many functions.

Functional Testing

OK, time to move on to functional testing.

$ php vendor/bin/codecept generate:cept functional HelloWorld

And this is what we get:

<?php
$I = new FunctionalTester($scenario);
$I->amOnPage('/');
$I->see('Welcome');

Wait, what?

No, it is not a mistake. Functional tests should be written in the same way as integrational tests. The difference is that functional tests are interacting directly with your application. That means you don’t need a webserver to run functional test, and you have more capacity for testing different parts of your application.

It does mean that support for all frameworks is lacking, but the list of supported frameworks is extensive: Symfony, Silex, Phalcon, Yii, Zend Framework, Lumen, Laravel. This should be sufficient for most cases and most developers. Please consult Codeception’s module documentation to get a list of available functions and then just switch it on in functional.suite.yml.

Codeception supports major frameworks: Symfony, Silex, Phalcon, Yii, Zend Framework, Lumen, Laravel.

Codeception supports major frameworks: Symfony, Silex, Phalcon, Yii, Zend Framework, Lumen, Laravel.

Before we proceed to unit testing, allow me to make a small digression. As you may have noticed, we created our tests with key cept:

$ php vendor/bin/codecept generate: cept acceptance HelloWorld

This is not the only way of creating tests. There are also cest tests. The difference is that you can structure multiple related scenarios in one class:

$ php vendor/bin/codecept generate:cest acceptance HelloWorld
<?php
class HelloWorldCest
{
    public function _before(AcceptanceTester $I)
    {
         $I->amOnPage('/forgotten')
    }

    public function _after(AcceptanceTester $I)
    {
    }

    // tests
    public function testEmailField(AcceptanceTester $I)
    {
	$I->see('Enter email');

    }
    public function testIncorrectEmail(AcceptanceTester $I)
    {
	$I->fillField('email', 'incorrect@email.com');
	$I->click('Continue');
	$I->see('Email is incorrect, try again');
    }
    public function testCorrectEmail(AcceptanceTester $I)
    {
	$I->fillField('email', 'correct@email.com');
	$I->click('Continue');
	$I->see('Please check your email for next instructions');
    }
}

In this example, methods _before() and _after() are run before and after each test. An instance of AcceptanceTester class is passed to each test, so you can use it in the same way as in cest tests. This style of tests can be useful in certain situations, so it is worth keeping in mind.

Unit Testing

Time for some unit testing.

Codeception is based on PHPUnit, so you can use tests written for PHPUnit. To add new PHPUnit tests, use the following approach:

$ php vendor/bin/codecept generate:phpunit unit HelloWorld

Or just inherit your tests on \PHPUnit_Framework_TestCase.

But if you want something more, you should try Codeception’s unit tests:

$ php vendor/bin/codecept generate:test unit HelloWorld
<?php

class HelloWorldTest extends \Codeception\TestCase\Test
{
    /**
     * @var \UnitTester
     */
    protected $tester;

    protected function _before()
    {
    }

    protected function _after()
    {
    }

    // tests
    public function testUserSave()
    {
	$user = User::find(1);
	$user->setEmail('correct@email.com');
	$user->save();
	$user = User::find(1);
	$this->assertEquals('correct@email.com', $user->getEmail());
    }
}

Nothing unusual for now. Methods _before() and _after() are setUp() and tearDown() analogues and will run before and after each test.

The main advantage of this test is in its ability to extend your testing process by including modules that can be switched on in unit.suite.yml:

  • Access to memcache and databases to track changes (MySQL, SQLite, PostgreSQL, MongoDB are supported)
  • Testing of REST/SOAP applications
  • Queues

Each module has its own features, so it is best to check the documentation and collect the necessary information for each module before proceeding to actual tests.

Plus, you can use the Codeception/Specify package (which needs to be added to composer.json) and write a description like so:

<?php 
class HelloWorldTest extends \Codeception\TestCase\Test
{
    use \Codeception\Specify;
    private $user;
    protected function _before()
   {
	$this->user = User::find(1);
   }
    public function testUserEmailSave()
    {
	$this->specify("email can be stored", function() {
            	$this->user->setEmail('correct@email.com');
		$this->user->save();
		$user = User::find(1);
            	$this->assertEquals('correct@email.com', $user->getEmail());
        	});
    }
}

PHP code inside these closure functions is isolated, so changes inside will not affect the rest of your code. Descriptions will help you to make the test more readable and make it easier to identify failed tests.

As an optional extra, you can use package Codeception\Verify for BDD-like syntax:

<?php
public function testUserEmailSave()
{
	verify($map->getEmail())->equals('correct@email.com');
}

And of course you can use stubs:

<?php
public function testUserEmailSave()
{
        $user = Stub::make('User', ['getEmail' => 'correct@email.com']);
        $this->assertEquals('correct@email.com', $user->getEmail());
}

Verdict: Codeception Saves Time And Effort

So what should you expect from Codeception? Who’s it for? Are there any caveats?

Codeception can be employed by developers with vastly different PHP profficiency levels and teams of all sizes.

Codeception can be employed by developers with vastly different PHP profficiency levels and teams of all sizes.

In my opinion, this testing framework is suitable for all sorts of different teams: Large and small, beginners and battle-hardened PHP professionals, those who are using a popular framework, and those who aren’t using any framework.

In any case, it all boils down to this: Codeception is ready for prime time.

It is a mature and well-documented framework that may easily be extended by numerous modules. Codeception is modern, but based on the time-tested PHPUnit, which should reassure developers who don’t want to experiment too much.

It performs well, which means it’s fast and doesn’t require too much time and effort. Better yet, it is relatively easy to master, and abundant documentation should assist a hassle-free learning process.

Codeception is also easy to install and configure, yet it boasts a lot of advanced options. While most users won’t need all (or indeed, most) of them, it all depends on what you intend to do with it. You can start with the basics, and the extra features will come in handy sooner or later.

About the author

Vasily Koval, Ukraine
member since September 4, 2015
Vasily is a web developer with over than 9 years of extensive experience developing, optimizing, and supporting web applications. His main goal is to find solutions that meet all clients' requests with a focus on timely delivery. He also believes that accuracy and honesty are the keys to successful cooperation. [click to continue...]
Hiring? Meet the Top 10 Freelance PHP Developers for Hire in December 2016

Comments

Yannick Warnier
Hi Vasily, In the introduction to your article, it says "... Codeception, a mature and well-documented testing framework designed to outperform PHPUnit and Behat.". Could you specify in what sense it is meant to outperform Behat? In the total coverage allowed? In simplicity? Could you give details about how you believe it is a better choice than Behat?
Nermin Hajdarbegovic
Hi Yannick, We are not saying it outperforms either (that would be subjective), we just mentioned it in the intro, as one of Codeception's selling points - this is their claim, not ours. Their dev team states it was inspired by PHPUnit and Behat, and that their ultimate design goal was to create a system with more features, to complement or replace them in certain scenarios. So, it was designed with that goal in mind, in their own words. Whether or not they met the goal is up for debate, and it's for each user to decide. An in-depth comparison would be beyond the scope of this article, as this is merely an introduction to Codeception, and does not deal with the pros and cons of other solutions... (Behat was mentioned just once, in our mailer, and it's not featured in the article itself).
Yannick Warnier
Hi Nermin, No offense taken here. Given the invitation e-mail and my current use of Behat, I'm interested to know if Codeception has serious reasons to "outperform" Behat, or if it's just a marketing argument. If the difference between Behat and Codeception is not relevant, then it's not worth my time either, and even less if we have to change the syntax of all our tests. This doesn't diminish the merit of having written a very nice article, but it would be nice to know if the declarations are the opinion of the author or just a copy paste of a sentence from another website. I'm just saying...
Nermin Hajdarbegovic
Yep, I understand, but that simply wasn't the topic. Like I said, the article is not a review or direct comparison, it's merely an introduction to Codeception, so making such conclusions would be unprofessional on our part (as it's not a review, such "conclusions" would be mere assumptions). We mentioned it for context, as one of their design goals. If a company says it designed X to do Y and compete with Z, that's relevant - that's not just c/p marketing, that's context. Their design goal gives us an idea of what they were trying to accomplish. It doesn't imply their solution is superior to Z in any way, it merely explains the general idea behind their solution, that's all. What about doing a full review and comparison? Well, I can't make any promises, we'd need to do a bit of research and find an author, or preferably a couple of authors, well versed in both... If we decide such an article would be worth the effort, I'll ask you to pitch in... And believe me, I can be very persuasive... ;)
Anton Belonovich
I wonder why almost none of articles that introducing some technology includes advanced examples or comparsion to similar technologies? There are always "hello worlds", that I could find myself on techology's official site. This is a testing framework. Why not include test of web page with something real, that web developers are familiar with? A simple checkout form, for example. And put the same test written in another test framework to see the difference and advantages/disadvantages. Or take a piece of code and webpage and provide test examples of all kinds of tests: functional, integration, acceptance, unit. Just something beyound hello worlds. This is a website for top talented developers, isn't it? :)
davertMik
OK, Codeception author is here. Let me feature few selling points: Codeception is a *testing framework* so it aims to simplify testing process. It doesn't force you to follow BDD or so on. Main goal of it is to combine and share testing experience of various developers. That's why we have Modules. If developers do Symfony testing they include Symfony + Doctrine module which contain actions for functional testing in this environment. If someone sees that Symfony + Doctrine testing can be improved, they add more methods into it, send us a Pull Request and we accept it. That's how we collected over 20 modules for working with Frameworks, Databases, WebDriver, etc. So Codeception's main goal is not just to provide a test runner but provide configurable set of actions, and assertions that will fit to 90% cases. I find that REST API testing is done much better than in other competitors. I hope you are not writing REST tests as Gherkin scenarios, don't you? Codeception can execute all set of tests in one runner, and is very flexible in configuration.
Anton Belonovich
Well, I think you right for this case. It is not supposed to be a complete guide, just a "Jumpstart". What I am complaining about, is that there are already too much of "starting guide" articles and you can always find them in first pages of Google. Especially for somewhat popular technologies. When I subscribed to articles at Toptal, I expected that they will be more detailed and explanatory. And I previously saw this kind of articles here, like: https://www.toptal.com/python/python-design-patterns But this is only my naive expectation :)
Nermin Hajdarbegovic
Yeah, I see where you're coming from Anton, but believe me, it's always a fine balancing act. If we focused solely on articles like Andrei's Python design patterns, that would be great for experienced users like yourself, but not for everyone (btw. I enjoyed working with Andrei on that particular piece, he's a great guy). Striking the right balance gets trickier when we talk about specific solutions with plenty of alternatives, because there's a fair amount of subjectivity in such articles. Even when we do compare two different products or platforms, we try to keep the comparison brief and to the point, and as neutral as possible. Hence the "Hello World" bit - if we used real-life code for each demonstration, and if we compared every alternative solution, our articles would be much harder to digest. Personally, I don't mind reading or writing long articles, but trust me, a lot of people do. In fact, you're in the minority here ;) - we hear the exact opposite from most users: they want shorter posts and think we're overdoing it when we publish a 5,000 word article. Their argument is usually simple - why use 6,000 words to say something you could say in 3,000? Of course, an in-depth article would also provide more information, but when you double the word count, that doesn't necessarily mean you doubled the amount of useful information contained in the article. The same goes for code: elaborate examples and comparisons don't always work well. Sure, they give advanced readers a detailed overview, but as far as most people are concerned, they don't make much of a difference, which is why the industry still relies on good old "Hello World" for basic code examples. :) That's why we have to do our best to offer a wide variety of articles: different subjects, different skill levels, different niches... So, we publish everything from lifestyle posts to various technical resources, and everything in between. We need all of them, and chances are we will need even more (both in terms of volume and variety). Since I don't mind huge, in-depth articles, I'll keep your comments in mind.
davertMik
Are you passionate in reading lots of screens of source code? If not, you won't be happy with such article. A detailed description of doing testing the same complex application in Codeception/Behat/PHPUnit/Cucumber/etc involves much more writings and codings. It can probably be a post series or complete online course. But I think the idea of such article is to encourage this framework to try. No one but you can decide does it fits you or not. There are lots of different ways and patterns to use it.
davertMik
My personal philosophy is the test on a level you can but know pros and cons of each testing levels. Sure, it is better to have unit tests, but if you don't, try integration and functional. Just as you do. Codeception can work on each test level. Happy testing! )
Yannick Warnier
OK, great, that's better (in terms of specifying in what aspect it is different). Thanks! We don't have REST tests yet... in fact we only have about 80 tests now for a very large application. It's a very long process to create and then maintain the whole test base. We are working with Behat for integration and functional testing after a long (and relatively useless in our case) period of trying to make unit tests useful. Integration and functional tests are much better suited to our needs because functions/methods (covered by unit tests) don't change that often in our case, while the features we provide and the web interface do change regularly. Thank for the short description. Not sure when we'll have time to give Codeception a try, but we'll try to schedule that soon. Good luck.
Anton Belonovich
Thanks, Nermin, I've totally got your point of view. If people want such articles, you should definitely write them. And thank you, inspired by your words I suddenly realized, that when I will write about any technology, it will be an opposite to "Hello world" :)
Nermin Hajdarbegovic
Well, we don't always use "Hello World" - you pointed out a good example yourself in one of your previous posts ;) However, we use it frequently, and we probably will keep doing so, namely in intro and tutorial posts. For more elaborate, in-depth articles, we don't use "Hello World" and to the best of my knowledge, we never did.
Hubert Jason
Thank you for this article, it's always good to have a refresh on this principles. I'll be interested to hear your opinion on a "testing"
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering and design posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
You can edit your subscription preferences here.
Trending articles
Relevant technologies
About the author
Vasily Koval
PHP Developer
Vasily is a web developer with over than 9 years of extensive experience developing, optimizing, and supporting web applications. His main goal is to find solutions that meet all clients' requests with a focus on timely delivery. He also believes that accuracy and honesty are the keys to successful cooperation.