Quick Testing Tips: One Class, One Test? – Matthias Noback

I’ve mentioned this several times without explaining: the rule that every class should have a test, or that every class method should have a test, does not make sense at all. Still, it’s a rule that many teams follow. Why? Maybe they used to have a #NoTest culture and they never want to go back to it, so they establish a rule that is easy to enforce. When reviewing you only have to check: does the class have a test? Okay, great. It’s a bad test? No problem, it is a test. I already explained why I think you need to make an effort to not just write any test, but to write good tests (see also: Testing Anything; Better Than Testing Nothing?). In this article I’d like to look closer at the numbers: one class – one test.

A Platonic concept of object-oriented programming

Some say there are in essence two types of thinking. One based on Plato, one based on Aristotle. This may not really be the case, but it’s an attractive thought. I find that in testing at least there is a similar distinction. The rule that every class should have a corresponding test class is a Platonic rule. It is similar to Plato’s ideal world, which is (roughly speaking) a universe of ideas on which our concrete reality is based. Given that a programming problem is also an idea, and that classes are ideas as well, the problems and their class-based solutions all live in that ideal world. The programmer’s job is to extract those classes from the world of ideas and turn them into code that we can run on our computers. All the classes we’ll ever need already exist, we only have to discover them. Once we have found such a class (eureka!) we know, because of our team rule, that we have to now also create a test class for it. Why? Because we are mere mortals, and in the process of bringing that perfect class from the immaterial realm into our computer, we make mistakes. The real class is less perfect than the ideal class.

Classes are arbitrary things

Why doesn’t all of this make sense? Because we don’t discover classes the way they are. We arbitrarily decide on a set of properties and methods (data and behavior) that we want to keep together. The size of a class changes over time: we extract a new class, or we extract a method, and we often do the opposite too: we let the data and behavior of one class be absorbed into another one, and so on. Why? Because we want to fix some code smells, or maybe we made a design mistake. We want to try a different design pattern, or we want to revert that decision.

If the size and shape of a class is arbitrary, how can we link the number of required tests to the number of classes?

This is a known problem for the testing school that follows the one class-one test rule. They have to decide: when I extract class B from class A, do I write new tests for class B? Maybe not, because B’s behavior is indirectly covered by the test we already have for class A. Maybe yes, because I can mock B and test A and B separately. The same reasoning goes for methods: do you test each method? What happens when you extract a new method (maybe in another class, maybe in the same class)? Do you write new tests because you have to follow the rules?

It’s clear that in practice this one class-one test rule needs a lot of re-evaluation, leading to lots of discussion in the team. Furthermore, it leads to demotivating testing practices, where you spend a lot of time changing tests, writing tests that are mock-heavy, or are otherwise too close to the implementation of the subject under test.

An Aristotelian alternative

What is the alternative? In my experience, it makes a lot more sense to follow an Aristotelian approach. Down-to-earth. What do we have in front of us? What are we working on? What kind of test does it need? Can we test this at a somewhat higher level of abstraction than this class we only accidentally use? We shouldn’t be focused on classes anyway, since they are just the way we write code as object-oriented programmers: classes are an implementation detail. What matters is the behavior of the application as a whole. What value does it provide to the user?

When we focus on the bigger picture, we can separate the essential from the accidental. If our test covers only the essential parts, we can leave all the accidental parts inside the black box. We can then freely change those parts and still have a working test, because the test didn’t focus on the details. I find this a very rewarding approach to testing. It’s not as demotivating, because you don’t spend a lot of time rewriting tests. As a bonus, these tests tell the bigger story, and help the reader understand what’s going on in the code, and for what reason. So they will serve as documentation too; future programmers won’t have to re-invent and reverse engineer all the business rules again.

Debugging PHP Applications at Web Summer Camp 2021 – Liip

With Covid-19, physical conferences stopped happening. I gave a talk at the virtual Symfony World conference in 2020. The virtual conference was an interesting experience and a well organised event. But I missed travelling to a foreign place and meeting people between talks and in the evening. Hence, I was absolutely thrilled to be invited to do a workshop at the Web Summer Camp in Croatia.

Web Summer Camp Croatia is a unique event. There are no regular presentations, only hands-on workshops. You see fewer different topics than at a typical conference, but get to deep dive in those instead. The participants who had time to stay an additional day were invited on a boat trip on the beautiful Mediterranean sea.

Debugging PHP Applications

In my debuggin strategies workshop, I used PHP for the examples, but most of the content is applicable to any programming language. Using lots of small exercises, I let the participants try various debugging strategies to locate the bugs I had hidden in the application. Among other things, we did exercises to

  • Understand exception messages and stack traces to locate a problem
  • Use logs to identify where the problem is
  • Use unit tests and a test coverage report to identify an issue
  • Use git bisect to find the commit that introduced a regression
  • Demonstration of step-by-step debugging with the PhpStorm IDE

I also showed the clean code principles as a way to avoid errors from being made. I presented defensive programming and other methods to improve error reporting when something does go wrong.

For the showcase examples, I used a simplistic Symfony application that imports CSV files into a database and shows a query result from that database on the home page. The application is super simple, so that the participants understand what is going on and can focus on the debugging technique of the exercise. Each exercise is a branch in that repository with some change to introduce the bug.

Hire Me 😉

If you are interested in providing a hands-on workshop for your team, please get in touch with me. Meeting on-site will bring the best value, as the workshop is highly interactive.

Quick Testing Tips: Write Unit Tests Like Scenarios – Matthias Noback

I’m a big fan of the BDD Books by Gáspár Nagy and Seb Rose, and I’ve read a lot about writing and improving scenarios, like Specification by Example by Gojko Adzic and Writing Great Specifications by Kamil Nicieja. I can recommend reading anything from Liz Keogh as well. Trying to apply their suggestions in my development work, I realized: specifications benefit from good writing. Writing benefits from good thinking. And so does design. Better writing, thinking, designing: this will make us do a better job at programming. Any effort put into these activities has a positive impact on the other areas, even on the code itself.

Unit tests vs automated scenarios

For instance, when you write a test in your favorite test runner (like PHPUnit), you’ll write code. You’ll focus on technology, and on implementation details (methods, classes, argument types, etc.):

$config = Mockery::mock(Config::class);
$config->shouldReceive('get') ->with('reroute_sms_to_email') ->andReturn('developers@org.nl'); $fallbackMailer = Mockery::mock(Mailer::class);
$fallbackMailer->shouldReceive('send') ->andReturnUsing(function (Mail $mail) { self::assertEquals('The message', $mail->plainTextBody()); self::assertEquals('SMS for 0612345678', $mail->subject()); }); $smsSender = new SmsSender($config, $fallbackMailer);
$smsSender->send('0612345678', 'The message');

It takes a lot of reading and interpreting before you even understand what’s going on here. When you write a scenario first, you can shift your focus to a higher abstraction level. It’ll be easier to introduce words from the business domain as well:

Given the system has been configured to reroute all SMS messages to the email address developers@org.nl
When the system sends an SMS
Then the SMS message will be sent as an email to developers@org.nl instead

When automating the scenario steps it will be natural to copy the words from the scenario into the code, establishing the holy grail of Domain-Driven Design – a Ubiquitous Language; without too much effort. And it’s definitely easier to understand, because you’re describing in simple words what you’re doing or are planning to do.

Most of the projects I’ve seen don’t use scenarios like this. They either write technology-focused scenarios, like this (or the equivalent using Browserkit, WebTestCase, etc.):

Given I am on "/welcome"
When I click "Submit"
Then I should see "Hi"

Or they don’t specify anything, but just test everything using PHPUnit.

Writing scenario-style unit tests

Although it may seem like having any kind of test is already better than having no tests at all, if you’re making an effort to test your code, I think your test deserves to be of a high quality. When aiming high, it’ll be smart to take advantage of the vast knowledge base from the scenario-writing community. As an example, I’ve been trying to import a number of style rules for scenarios into PHPUnit tests. The result is that those tests now become more useful for the (future) reader. They describe what’s going on, instead of just showing which methods will be called, what data will be passed, and what the result of that is. You can use simple tricks like:

  1. Givens should be in the past tense
  2. Whens should be in the present tense
  3. Thens should be in the future tense (often using “should” or “will”)

But what if you don’t want to use Behat or another tool that supports Gherkin (the formalized language for these scenarios)? The cool thing is, you can use “scenario language” in any test, also in unit tests. The trick is to just use comments. This is the unit test above rewritten with this approach:

// Given the system has been configured to reroute all SMS messages to the email address developers@org.nl
$config = Mockery::mock(Config::class);
$config->shouldReceive('get') ->with('reroute_sms_to_email') ->andReturn('developers@org.nl'); // When the system sends an SMS
$fallbackMailer = Mockery::spy(Mailer::class);
$smsSender = new SmsSender($config, $fallbackMailer);
$smsSender->send('0612345678', 'The message'); // Then the SMS message will be sent as an email to developers@org.nl instead
$fallbackMailer->shouldHaveReceived('send') ->with(function (Mail $mail) { self::assertEquals('The message', $mail->plainTextBody()); self

Truncated by Planet PHP, read more at the original (another 3134 bytes)

PHP 8.1.0 RC 2 available for testing – PHP: Hypertext Preprocessor

The PHP team is pleased to announce the release of PHP 8.1.0, RC 2. This is the second release candidate, continuing the PHP 8.1 release cycle, the rough outline of which is specified in the PHP Wiki. For source downloads of PHP 8.1.0, RC 2 please visit the download page. Please carefully test this version and report any issues found in the bug reporting system. Please DO NOT use this version in production, it is an early test version. For more information on the new features and other changes, you can read the NEWS file or the UPGRADING file for a complete list of upgrading notes. These files can also be found in the release archive. The next release will be the third release candidate (RC 3), planned for 30 September 2021. The signatures for the release can be found in the manifest or on the QA site. Thank you for helping us make PHP better.

Where do types come from? – Matthias Noback

In essence, everything is a string.

Well, you can always go one layer deeper and find out what a string really is, but for web apps I work on, both input data and output data are strings. The input is an HTTP request, which is a plain-text message that gets passed to the web server, the PHP server, the framework, and finally a user-land controller. The output is an HTTP response, which is also a plain-text message that gets passed to the client. If my app needs the database to load or store some data, that data too is in its initial form a string. It needs to be deserialized into objects to do something and later be serialized into strings so we can store the results.

We create objects from strings, and turn them back into strings because the protocols we use require strings (e.g. HTTP, SQL, AMQP, and so on). These protocols are only used near the edges of the application, where data comes in from and gets sent to external systems. In the core of the application there should be no need to serialize/deserialize data. There we should only have to deal with objects. That will be great, because objects can provide guarantees regarding the data they keep, so they are safer to use than strings. They also have an explicitly defined API, so they are much easier to use.

Of course many developers know this. They’ll use Value Objects to wrap strings, enforcing data consistency and ease of use. And not just strings, because we have several other primitive types at our disposal that support different use cases like doing math.

The problem is, how do we safely go from a string to an integer. To complicate things, most string data gets to us in the form of an associative array (i.e. map) of key/value pairs, both of which are a string. For instance when we get a record from our database abstraction library, it will be an array. If we want to use that data we can access it by its key, but we have to ensure it’s there. The next step is to ensure it’s of the correct type, and optionally cast it to the correct type:

/** @var array $record */ $title = $record['title'];
$numberOfComments = (int)$record['numberOfComments'];

From the type signature if $record, it’s not clear that we may expect keys title and numberOfComments to exist. Even if they exist we can’t be sure that their values are of the expected type. When working with arrays you always have to check if the key exists before accessing it, or you may get a PHP Notice (and hopefully to the error that it really is, but most frameworks nowadays do this for you). We can use the so-called null coalescing operator (??) to overcome the problem of undefined keys:

/** @var array $record */ $title = $record['title'] ?? '';
$numberOfComments = (int)($record['numberOfComments'] ?? 0);

This works if the key is undefined, but it will also revert to the default value of they key did exist but the value was null. We lose an important piece of information, namely that the requested key is undefined. In most cases this is a programming mistake, e.g. we forgot to add the column to the SQL SELECT statement. When using ?? it’s a lot harder to discover this problem because it “swallows” the problem.

Instead we should explicitly assert that the key exists:

/** @var array $record */ if (!array_key_exists($record, 'title')) { throw new LogicException('Expected array $record to have a key "title"');
}
$title = $record['title']; if (!array_key_exists($record, 'numberOfComments')) { throw new LogicException('Expected array $record to have a key "numberOfComments"');
}
$numberOfComments = (int)$record['numberOfComments'];

Of course, this quickly becomes annoying. So we introduce a helper function for this, e.g.

/** @var array $record */ self::assertKeyExists($record, 'title');
$title = $record['title']; self::assertKeyExists($record, 'numberOfComments');
$numberOfComments = (int)($record['numberOfComments'] ?? 0);

The helper function throws that same exception if the key is undefined.

But couldn’t we just define the expected shape of $record, thereby fixing the issue? E.g.

/** @var array{title: string, numberOfComments: string} $record */

Not really, because it isn’t this method that defines the structure of $record. It’s the result of fetching a result set from the database, and that doesn’t give us any guarantees about the shape of the array, or the types of the values. There’s a slightly better type we can use though:

/** @var array<string,string|nu

Truncated by Planet PHP, read more at the original (another 4337 bytes)

Quick Testing Tips: Testing Anything; Better Than Testing Nothing? – Matthias Noback

“Yes, I know. Our tests aren’t perfect, but it’s better to test anything than to test nothing at all, right?”

Let’s look into that for a bit. We’ll try the “Fowler Heuristic” first:

One of my favourite (of the many) things I learned from consulting with Martin Fowler is that he would often ask “Compared to what?”

  • Agile helps you ship faster!
  • Compared to what?

[…]

Often there is no baseline.

Daniel Terhorst-North

The baseline here is: no tests. The statement is that having some kind of test is better than having no tests. Without any context, this is evidently true. Since we know that tests are good, and we want tests for our code, but we have no tests yet, adding some kind of test gets us at least one step closer to the end goal.

However, without context, most statements can’t be judged for their value. So let’s add some context. My guess is that most development teams want tests for their code for two reasons:

  1. They want to have the tests form a kind of safety net, for when they’re making changes to that horrible legacy code.
  2. They want to understand why something was implemented the way it is by reading the tests.

To judge the value of any test over no test we should find out if those few tests that developers write in a #NoTest code base are actually helpful to achieve 1 and 2.

UI Tests

Another common minimal approach to testing is to go The Framework Way. Whatever the framework describes in their “Testing” chapter, the team will do. Mostly this results in tests that focus around performing web UI interactions and checking what ends up being in the database afterwards. These tests also break for all kinds of unrelated reasons, which makes them annoying to maintain. They are usually quite slow. This reduces the value of the safety net they provide. Furthermore, in most cases they still don’t document the “why”. They show a number of steps and what’s supposed to happen, but they don’t explain why that happens in this case. There is often no clear connection between the start and the end of the test.

Based on my experience with different teams and different projects, this leads me to think that it’s definitely not better to write any test than no test. If you don’t know what types of test you should write for each area of your application, you’ll end up with an unmaintainable test suite, and demotivating team standards like “every class should have a test”. If anything, you’ll get people to dislike writing tests. At that point, your principle that “any test is better than no test” has reached the opposite of the intended effect.

Good Tests

Instead of writing just any test, focus on writing good tests. Work on tests together, treat them as specifications (which makes it easier to include the “why”, something we programmers often forget). While doing so, make sure that writing tests is Fun, Easy, and Effective. The FEE for this is that you have to invest in:

  • Test tools that help you run specific tests really quickly (Can you right-click a specific test method and run it in PhpStorm? You should!)
  • Tests that give quick results (How long does it take to run the relevant tests before committing? If it’s more than a few seconds, fix it!)
  • It should be really cheap to add more test cases to show how the code behaves when different data is being provided (How much do you have to copy/paste when you want to create a new test class, or a new test method? Make sure it’s just a few lines).

This approach, I claim, is Effective; when the test suite no longer works against you, it’ll become a trusted safety net instead of an annoying maintenance burden. More tests will be created, and they will be of a better quality than just one-class-one-test or make-request-then-look-in-DB tests.

Test Debt

I’m sure it takes a lot of effort, but just like everybody understands Technical Debt for production code and invests in improving its design, you know you have Test Debt; both in your own experience as a developer and in your project. So, go go go!

Ep#361 – Interview with Dana Luther – Voices of the ElePHPant

Listen as host Khayrattee Wasseem talks with Dana Luther about her two talks for Longhorn PHP happening in Oct 2021 – docker secrets & exakat using docker.

She explains how she participated in the “School the World” movement to help children in poor countries. She also talks about her passion for singing.

This episode is sponsored by
RingCentral Developers

The post Ep#361 – Interview with Dana Luther appeared first on Voices of the ElePHPant.

Xdebug Update: August 2021 – Derick Rethans

Xdebug Update: August 2021

In this monthly update I explain what happened with Xdebug development in this past month. These will be published on the first Tuesday after the 5th of each month.

Patreon and GitHub supporters will get it earlier, around the first of each month.

You can become a patron or support me through GitHub Sponsors. I am currently 58% towards my $2,000 per month goal. If you are leading a team or company, then it is also possible to support Xdebug through a subscription.

In August, I worked on Xdebug for about 50 hours, with funding being around 25 hours, which is only half.

Xdebug Videos

I have published two more videos on how to use Xdebug on my YouTube channel.

These are part of a series to explain how to use Xdebug:

  • Xdebug 3 Profiling: 3. Analysing Data, where I show how to use KCacheGrind to read and analyse profiling files to find bottlenecks in code.

  • Xdebug 3: Activation and Triggers, where I explain how to activate Xdebug’s myriad of features with different methods, including cookies, GET/POST parameters, environment variables, and with a browser extension.

I will create more videos in the upcoming month, and if you would like to suggest a topic for a 5 to 10 minute long video, feel free to request them through this Google Form.

PHP on the road to the 8.1.0 release – Remi Collet

Version 8.1.0 Release Candidate 1 is released. It’s now enter the stabilisation phase for the developers, and the test phase for the users.

RPM are available in the remi-php81 repository for Fedora  33 and Enterprise Linux  7 (RHEL, CentOS), or in the php:remi-8.1 stream, and as Software Collection in the remi-safe repository (or remi for Fedora)

 

emblem-important-4-24.pngThe repository provides development versions which are not suitable for production usage.

Also read: PHP 8.1 as Software Collection

emblem-notice-24.pngInstallation : read the Repository configuration and choose installation mode.

Replacement of default PHP by version 8.1 installation, module way (simplest way on Fedora and EL-8):

dnf module reset php
dnf module install php:remi-8.1
dnf update

Replacement of default PHP by version 8.1 installation, repository way (simplest way on EL-7):

yum-config-manager --enable remi-php81
yum update php\*

Parallel installation of version 8.1 as Software Collection (recommended for tests):

yum install php81

emblem-important-2-24.pngTo be noticed :

  • EL8 rpm are build using RHEL-8.4
  • EL7 rpm are build using RHEL-7.9
  • lot of extensions are also available, see the PHP extension RPM status page and PHP version 8.1 tracker
  • follow the comments on this page for update until final version
  • should be proposed for Fedora 36

emblem-notice-24.pngInformation, read:

Base packages (php)

Software Collections (php81)