There is a serious skills shortage in the IT industry in many countries. Business suffers because they can't innovate to stay ahead of their competition and largely that innovation equates to developing secure and safe online experiences.
As a self-taught PHP developer myself I can appreciate that unit testing may not be a priority for many developers. Certainly it wasn't for me in the beginning but fortunately I fell into it along the way.
Whether you are new to web development or have just never got around to practicing TDD now would be a good time as any.
There are three main types of unit testing and each have their place.
First off, there is a unit test. This test is where you are testing a unit of code and asserting a condition has been met. The unit of code is typically on a small part of an implementation and nothing more. If there are other parts to the system then you write a separate unit for each use case or part of the system.
Next you have feature (also known as integration) tests. Those may be used to test an API endpoint but they are useful to for ensuring your UI acts as expected. That is a form is submitted properly along with its data and the assertion will fail if there is missing data.
Last there are the mock tests. Those assertions are ensuring the different components of a system remain stable and intact. Whilst a unit test in practice will ensure a calculation is true and correct a mock test will show you where in the system there are structural faults.
As software becomes far more complex, dependent on third party services it is more important than ever before to be able to develop and test your code. Let's have a go at it.
Say you wanted to develop a dating application, where would you start? You have two sexes and you must consider compatibility for a start. Inheritance solves the problem and as it turns out there are actually three sexes, aren't there.
namespace App\Library\Human;
abstract class Human { protected $orientation;
public function __construct() {}
public function enjoys(Human $orientation) : void
{
$this->orientation=$orientation;
}
public function sameSex() : bool
{
return $this->getSex() === $this->orientation->getSex();
}
public function isCompatible(Human $human) : bool
{
if(($this->orientation->getSex() === $human->getSex()) or ($this->orientation->getSex() === 'bi'))
{
return true;
}
return false;
}
public function getSex() : string
{
return $this->sex;
}
}
All the logic is placed in the abstraction leaving the three concrete classes bare bones. Even though there are only two sexes - man and woman - we must in this day and age account for same sex preferences and individual tastes. That means the third sex is bi and the following tests clearly show the need for a third sex.
use App\Library\Human\Human;
class Man extends Human { protected $sex='male';
public function __construct() {}
}
class Woman extends Human { protected $sex='female';
public function __construct() {}
}
class Bi extends Human { protected $sex='bi';
public function __construct() {}
}
That's our system in place for the purpose of getting you started on testing PHP code.
There are several use cases identified. They are the following:
Our tests must ensure those conditions are met therefore, for the software to function as expected. The tests are below and we see green on all six. You test one unit at a time and you don't move onto the next until you have a green bar on all existing units.
Another piece of advice is not to add too much implementation to concrete classes all in one go without running your tests at each and every addition of new code. The more you run your tests the longer you'll hold onto your sanity.
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Library\Human\Bi; use App\Library\Human\Man; use App\Library\Human\Woman;
class HumanTest extends TestCase { public function setUp() : void { parent::setUp(); }
public function tearDown() : void
{
parent::tearDown();
}
/** @test */
public function same_sex() : void
{
$woman=new Woman();
$woman->enjoys(new Woman());
$this->assertTrue($woman->sameSex());
}
/** @test */
public function not_same_sex() : void
{
$man=new Man();
$man->enjoys(new Woman());
$this->assertFalse($man->sameSex());
}
/** @test */
public function man_wants_sex_with_women() : void
{
$man=new Man();
$man->enjoys(new Woman());
$this->assertTrue($man->isCompatible(new Woman()));
}
/** @test */
public function woman_wants_sex_with_women() : void
{
$woman=new Woman();
$woman->enjoys(new Woman());
$this->assertTrue($woman->isCompatible(new Woman()));
}
/** @test */
public function woman_wants_sex_with_anyone() : void
{
$woman=new Woman();
$woman->enjoys(new Bi());
$this->assertTrue($woman->isCompatible(new Man()));
$this->assertTrue($woman->isCompatible(new Woman()));
}
/** @test */
public function man_wants_sex_with_men() : void
{
$man=new Man();
$man->enjoys(new Man());
$this->assertFalse($man->isCompatible(new Woman()));
$this->assertTrue($man->sameSex());
}
}
As you see those six tests pretty much cover all cases keeping everyone happy. There are only two methods to be mocked in our system as it stands. They're below.
namespace Tests\Mock;
use Tests\TestCase;
class HumanTest extends TestCase { public function __construct() { /** * @note must call the parent constructor otherwise the tests will fail with an * exception thrown */
parent::__construct();
}
public function setUp() : void
{
parent::setUp();
}
public function tearDown() : void
{
parent::tearDown();
\Mockery::close();
}
/** @test */
public function can_any_relationship_be_created() : void
{
$mock_man=\Mockery::mock(Man::class);
$mock_woman=\Mockery::mock(Woman::class);
$mock_man
->shouldReceive('enjoys')
->once()
->with($mock_woman)
->andReturnNull();
$this->assertNull($mock_man->enjoys($mock_woman));
}
/** @test */
public function can_a_relationship_work_or_not() : void
{
$mock_man=\Mockery::mock(Man::class);
$mock_woman=\Mockery::mock(Woman::class);
$mock_man
->shouldReceive('enjoys')
->once()
->with($mock_woman)
->andReturnNull();
$this->assertNull($mock_man->enjoys($mock_woman));
$mock_bi=\Mockery::mock(Bi::class);
$mock_man
->shouldReceive('isCompatible')
->once()
->with($mock_bi)
->andReturn(false);
$this->assertFalse($mock_man->isCompatible($mock_bi));
}
}
I create a separate suite for mock tests. I suggest you do the same and you do that by adding the following into PHPUnit's XML configuration:
... <testsuite name="Mock"> <directory suffix="Test.php">./tests/Mock</directory> </testsuite> ...
Without that your mock tests won't run. I've not provided you with the integration tests however. I'll leave those as an exercise for you.
Hopefully this has helped you see TDD practices aren't that difficult nor are they tedious and time consuming. Even if you don't start with tests but you implement them later into a project that is better than no tests at all. In my experience (of not always wanting to test on personal projects) the more you do test then the happier you are to do them.
Unit testing grows on you and it makes sense to get started if you haven't already.
Content on this site is licensed under a Creative Commons Attribution 4.0 International License. You are encouraged to link to, and share but with attribution.
Copyright ©2024 Leslie Quinn.