Kategorien
Coding

Adventures with PHPUnit, geckodriver and selenium

In my current pet project, I try to not only focus on the features itself, but also in the quality of the software and the development environment. Since I programmed code myself, the professional software development world has evolved quite a lot, and new approaches and tools have emerged and are de facto standards nowadays. While I know them as an observer, I barely used them myself. That I want to change.

Historically, my project’s setup already included a selenium test file, using geckodriver and Facebook’s php-webdriver library. However, the test code itself was a plain php file, executing a series of webdriver instructions and returning failures when some string comparisons did not yield the desired results:

<?php

namespace Facebook\WebDriver;

use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;

$host = 'http://localhost:4444';

$driver = RemoteWebDriver::create($host);

$driver->get('https://example.com/foo.php');

$driver->findElement(WebDriverBy::id('title'))
       ->sendKeys('Acceptance test title'); 

...

if (strcmp($driver->findElement(WebDriverBy::id('title'))->getAttribute('value'), 'Acceptance test title')!=0) {
    echo "Just created task cannot be accessed!";
    $driver->quit();
    exit(1);
}

$driver->quit();

This code worked, and helped me to assure that my product didn’t break (to some degree) from a user perspective. However, it was ugly to maintain, didn’t produce any helpful test report and just didn’t feel right.

Failed Approach

After some research I found out about phpunit-selenium, a project combining the powers of phpunit, I guess the dominating unit testing framework in the php space with selenium. Interesting. However, from there I went down a slippery road to trial-and-error-land. I should have probably get cautious, when the only documentation I found was referring to phpunit 3.7 (its now at 9.2), and even the github project was only referring to phpunit 8.x versions. With some back-and-forth, I could identify a version which fitted together with phpunit in composer repos and sucessfully deployed:

{
    "require-dev": {
        "phpunit/phpunit": "9.2",
        "phpunit/phpunit-selenium": "dev-master"
    },
}

Apart from that, I had to install the selenium server, as so far I was directly connecting to the geckodriver. This led to the worst part of issues: For some reasons in 95% of cases, the test cases didnt execute properly, and it was somehow related to firefox crashes. I tried so many things:

  • Made sure both firefox and geckodriver had most recent versions (they had), same for selenium server.
  • Executed everything as non-root.
  • Tweaked permissions.
  • Increased log-levels of geckodriver, selenium server and even firefox (the latter didnt even create crash dumps).
  • I even started to investigate if my server’s ubuntu has some limiting settings in systemd cgroups, somehow limiting the number of parallel threads and memory. After all, its a browser we are launching here.

All of the above didnt really change anything. It was really frustrating, and the whole chain was quite long to debug in my spare time. After approx. 10 hours of frustrated tests, I gave up and made up my mind.

Solution

Luckily, I found another approach, based on the same tooling I had initially already working, but in a cleaner way. How does this look like? I simply combined phpunit and webdriver calls without any third party addition. Code says more than thousand words:

<?php declare(strict_types=1);

namespace Facebook\WebDriver;

use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Exception\UnknownErrorException;

final class AcceptanceTest extends TestCase
{
    protected static $driver;

    public static function setUpBeforeClass(): void 
    {
        $host = 'http://localhost:4445';

        $capabilities = DesiredCapabilities::firefox();
        $capabilities->setCapability('moz:firefoxOptions', ['args' => ['-headless']]);
        $capabilities->setPlatform(WebDriverPlatform::LINUX);

        self::$driver = RemoteWebDriver::create($host, $capabilities);
    }
    
    public static function tearDownAfterClass(): void
    {
        self::$driver->quit();
    }

    public function testTaskIsSuccessfullyCreatedFromContent()
    {
        self::$driver->findElement(WebDriverBy::id('title')) 
             ->sendKeys('Acceptance test title'); 

...

        $this->assertEquals('Acceptance test title', self::$driver->findElement(WebDriverBy::id('title'))->getAttribute('value'));
    }
}

This gives me the full power of phpunit, with fixtures, asserts, reports and a nice console output:

PHPUnit 9.2.0 by Sebastian Bergmann and contributors.

..                                             2 / 2 (100%)

Time: 00:14.702, Memory: 6.00 MB

OK (2 tests, 3 assertions)
Kategorien
Book

Thoughts on „Clean Code“ from Robert C. Martin

I recently ordered some books from my project’s book recommendation list. This list is maintained by a few colleagues and contains many classics, but also some more hidden gems. Clean Code probably had to be on that list, and its not my first encounter. I skipped through the book already a few occasions before, but never really had the time and motivation to really digest its content. This time, this was different.

This is not a book review, but rather some personal notes and thoughts I gathered while reading it.

I particularly liked the first chapter, because it gathers quite some good statements and arguments why clean code really matters. Of course, everyone who ever development reasonably sized software themselves, know about the pros of clean code (as per whatever definition/guideline/philosophy). However, in daily close combat with management, customers and fellow developers, its not always easy to have the perfect argumentation at hand. So I always like to learn about new arguments objectively supporting subjective opinions.

The section „The Grand Redesign in the Sky“ describes very picturesque who the „its such a mess, lets do it from scratch“ often tends to (not) pan out in reality. That fits nicely to earlier thoughts and reads of mine regarding featuritis and the competition of legacy code maintainers and the new tiger team starting from scratch. Martin’s anecdote about such a competation taking more than 10 years was really eye-opening.

The next section talks about the attitude of developers needed to defend clean code:

Attitude

[…] Why does good code rot so quickly into bad code? We have lots of explanations for it. We complain that the requirements changed in ways that thwart the original design. We bemoan the schedules that were too tight to do things right. We blather about stupid managers and intolerant customers and useless marketing types and telephone sanitizers. But the fault, dear Dilbert, is not in our stars, but in ourselves. We are unprofessional.

This may be a bitter pill to swallow. How could this mess be our fault? What about the requirements? What about the schedule? What about the stupid managers and the useless marketing types? Don’t they bear some of the blame?

No. The managers and marketers look to us for the information they need to make promises and commitments; and even when they don’t look to us, we should not be shy about telling them what we think. The users look to us to validate the way the requirements will fit into the system. The project managers look to us to help work out the schedule. We are deeply complicit in the planning of the project and share a great deal of the responsibility for any failures; especially if those failures have to do with bad code!

Robert C. Martin: Clean Code, Pearson Education, 2009

I can very much relate to that code and – as a engineering manager and project manager with developer history – support confident developers defending their ground. Of course, there are more shades of grey, and I have experienced many arguments with developers who went too far in unconcrete demands for more time because of better quality. In my perspective, a proper discussion based on facts, figures and clear arguments from both sides – management and developers – yields the most best results, preferably as a compromise. But I get why Martin is triggering developers here explicitly.

Following are some statements from famous developers, you can read the first chapter of the book for free here. Then the actual content begins, where Martin describes one technique/rule/guideline after another, better naming, function design, commenting, formatting, error handling, testing etc. What follows are some refactorings of different sized open source code snippets, piece by piece, and in the end a list of „smells and heuristics“, an enumeration of indicators for bad code.

I have tried to apply the knowledge I gathered from reading this book in two ways:

  1. I added phpmd – php mess detection – to my current pet project, with the clean code rules. Of course it can only identify a subset of bad code indicators, but its already a good start. There were some findings which I could quickly fix.
  2. More practically, I started to refactor the code. E.g. I removed duplicate code, reduced function sizes and improved the error handling. Some of those improvements I already merged on master, but I am definetly not yet finished.

The worst part of the book was the example for „improved“ code given on pages 50ff. Maybe I didn’t get something essential, but this code is super-hard to read, understand, and totally cluttered with 1 to 3 line functions with awkward naming. I even suspected that this was kind of a trap/test to trigger the reader’s objection. Seems not. This made me also google for critique on the book, and this blog article also puts some good stances on it.

Can I recommend the book? Yes. I would take basically everything in it with a grain of salt, and there are some elements which I find „too much“. With that in mind, its a good motivation to critically reflect on own code and improve it iteratively.