DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Testing Patterns: Testcase Class per Feature

Practical PHP Testing Patterns: Testcase Class per Feature

Giorgio Sironi user avatar by
Giorgio Sironi
·
Apr. 18, 11 · Interview
Like (0)
Save
Tweet
Share
909 Views

Join the DZone community and get the full member experience.

Join For Free

There are cases when you want to break the parallelism between production code classes and test cases. Let's start from some of these scenarios.

You may have too much things to test in a single class, which presents many methods in its public Api. Or you may want to get a better picture of what a group of classes does.

Then break down the giant Testcase Class derived from the Testcase Class per Class pattern in two or more test cases.

The criterion for dividing the Test Methods is their relatedness to different features. By feature, a generic term, I mean a set of operations used together, but we'll see more examples of this division. The result of breaking up a large test case is lots of smaller and more understandable tests, and a better picture of the responsibilities of the SUT.

This 600-line class smells...

I bet you have said that too many features can be a smell of a God class. And indeed you're right; but if the test is a functional or end-to-end one is quite handy to divide Testcases by feature: you're testing the whole application and you cannot just divide end-to-end tests by the class they act on. They act on a whole copy of the system.
The same goes for certain all-encompassing classes such as Facades. This pattern comes handy for tests that do not have a natural single production code class equivalent.

Implementation

Transitioning from Testcase per Class to Testcase per Feature should not be really difficult as test methods should already be independent. If you have common code shared between test cases, you can use a superclass.

Since you have now different test case classes, choose a good convention for naming. You should nevertheless name them with a 'Test' suffix and keep the -Test.php file name, so that PHPUnit automatically sees them.

A common way of refactoring towards this pattern is to duplicate the class. Then, renaming takes place along with the deletion of everything that is responsibility of another class (Test Methods of other features) and that is not used in the current one (unrelated Test Utility Methods).

Variations

Again, remember these are examples of a class not tested in isolation, but more of tests required for an application. So many features to test does not imply a God class, only a system which does many things, which is normal when you're paid to develop it (although many of your tests should be at the lowest possible level, which is often the unit one.)

  • a Testcase Class per Method is used when methods are really complex to call, with many parameters. Different methods are addressed by different classes.
  • a Testcase Class per User Story is used for high level tests which address the various acceptance criteria for a story; in the xUnit patterns book it is said that this division prevents commit conflicts as long as working on user stories is not simultaneous.

The ultimate criteria is to keep cohesive Testcase Classes: if you have many Test Methods and they can be logically divided into groups, you can break the Testcase into pieces. A rule of thumb for measuring cohesion is checking how many methods use a Testcase field: when fields are referencedonly in a small subset of methods, those methods become candidates for extraction.

Example

The code sample of today is related to a small e-commerce Facade, which is tested at the acceptance level. Since the Facade hides the underlying system, it has a lot of features to test: you can consider these tests as functional over the whole object graph referred internally by the Facade.

Here's one of the two tests:

<?php
require_once 'Facade.php';

class FacadeBuyingTest extends PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        /**
         * For simplicity I instance the object with new, but in reality
         * it would be an object graph created by a Factory or a DI
         * container.
         */
        $this->facade = new Facade();
        // other setup code: database of the users for example
    }

    public function testUserCanBuyAnItem()
    {
        $userId = 42;
        $productId = 1000000;
        $this->facade->buy($userId, $productId);
    }

    public function testUserCanPayAnItem()
    {
        $userId = 42;
        $transactionId = 200000000;
        $this->facade->payFor($userId, $transactionId);
    }
}

And here is the other:

<?php
require_once 'Facade.php';

class FacadeSearchTest extends PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        $this->facade = new Facade();
        // other setup code: product database for example
    }

    public function testUserCanSearchForProducts()
    {
        $products = $this->facade->search('hard disks');
        $this->assertTrue(count($products) > 0);
        // ... more assertions
    }

    public function testUserCanSearchForProductsAndExcludeUnwantedOnes()
    {
        $products = $this->facade->search('hard disks -ssd');
        $this->assertTrue(count($products) == 1);
        // ... more assertions
    }
}

Sometimes you can't split a class, but you can always split its tests.

PHP Testing

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Testing Repository Adapters With Hexagonal Architecture
  • Understanding and Solving the AWS Lambda Cold Start Problem
  • Building the Next-Generation Data Lakehouse: 10X Performance
  • Steel Threads Are a Technique That Will Make You a Better Engineer

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: