I've spent close to 20 years doing web development, and in that time I haven't really thought much about testing. I mean, I've written my share of unit tests and mocks and all that, but I just went with the patterns set by other people on a project, and my tests passed, so I moved on.
Recently, I started work at Pocket as an engineer on our backend team. And one of my first big projects is migrating our admin tools to a dedicated server AND (more my initiative) adopt Symfony and start porting/rewriting our tools to be more unified in both the front- and backend.
Part of that work involved setting up Codecept. I initially tried to leverage what we've done in our main product code, but in the process I did a lot of hacking to include my tests into the bigger set of tests, and furious googling only got me half of what I needed to know.
So I took a breath and started from scratch. This is what I've learned.
Before I get into Codecept specifics, I want to talk about some basic testing concepts if it's not familiar to you. (Skip ahead, or read and correct me.)
Automated testing can generally be grouped into the following categories:
Unit Testing: this generally tests specific functions, either through comparing some expected output, or through generating and verifying code execution through test doubles (see below), or a combination of the two. Tests should be isolated and not depend on external dependencies (e.g. no live database or filesystem), hence unit
- Take-away: you want to test that
x * x, but you don't want to test if
save_to_table('some_table', square(x))successfully updates the database.
- Take-away: you want to test that
Integration Testing (and Functional Testing): this takes a step back from unit testing. You might still be comparing return results and using test doubles, but you're also using live systems (e.g. a real database or REST endpoint), and the code execution will span a wide range of functions and services (e.g. using a test Symfony framework instance to trigger a request and make sure the expected controller is executed). The goal here is to make sure all those isolated pieces are working nice together.
- Functional testing is a subset of integration testing and is focused more on verifying output of a given system based on the input provided -- you don't need to know what happens along the way, just that you get the results you expected.
Acceptance Testing: the nearly final stage of testing. These build on everything you've done in unit and integration/functional testing and makes sure that the output matches consumer expectation:
- Is a header showing?
- Are the links correct?
- Is the sign-in form working?
Test doubles are objects and functions that get swapped in for real things. Let's say you have a unit test where the function you're testing involves making a REST call, and you want to verify behavior for successes and failures. Instead of making an actual REST call (which should be in the integration test phase), you can pass in a simulated HTTP client that only responds to the behaviors you tell it go (e.g.
when get('/url') is called, return this specific response). TestDouble describes specific types of test doubles.
Tests can be organized into suites. You'll have your common unit, functional, and acceptance suites, but you might also have tests that run only on production after a deployment takes place. Suites are a great way to have control over what tests get executed and when.
In your PHP project, make sure you're using Composer (LINK HERE), because I haven't setup Codecept any other way. Then do:
composer require "codeception/codeception":"^2.2" --dev
This will make Codecept available for the development environment. Next, you may want to add your project's
./vendor/bin directory to
$PATH. This will make
codecept available as a script.
In your root project directory, type
codecept bootstrap. This will generate the following:
codecept.yml tests/ tests/_data/ tests/_output/ tests/_support/ tests/acceptance/ tests/acceptance.suite.yml tests/functional/ tests/functional.suite.yml tests/unit/ tests/unit.suite.yml
codecept run to make sure that these empty tests suites successfully load and run.
Note that every suite has a
$suite.suite.yml file and
That's it! I don't want this intro to be a huge info dump. In Part 2 I'll start getting into specifics.