Docker 上の Ubuntu + PHP8.1 環境で PHPUnit のコードカバレッジを得るまで

経緯

Docker 上に Ubuntu + PHP8.1 のコンテナ環境を構築して、そこで PHPUnit のコードカバレッジを得るまでに紆余曲折あったのでメモ。

前提

まず前提として、 Dockerfilecompose.yml は以下の記述になっています。

compose.yml

version: '3.9'
services:
  web:
    build:
      context: ./apache/docker
      dockerfile: Dockerfile
      args:
        WEB_ROOT_DIRECTORY: $WEB_ROOT_DIRECTORY
        WEB_CONTAINER_PORTNUM: $WEB_CONTAINER_PORTNUM
        PHP_VERSION: $PHP_VERSION
    volumes:
      # workspace
      - ./workspace:/workspace
      # docker settings template
      - ./template:/template
      # apache log
      - ./apache/log:/var/log/apache2
      # php ini
      - ./php/ini/php.ini:/etc/php/${PHP_VERSION}/fpm/php.ini
      # error log
      - ./php/error_log:/var/log/php
      # SSL
      - ./cert:/etc/ssl/private
    tty: true
    ports:
      - "$WEB_HOST_PORTNUM:$WEB_CONTAINER_PORTNUM"
      - "$WEB_HOST_PORTSSL:$WEB_CONTAINER_PORTSSL"
    entrypoint: bash -c "bash /workspace/entrypoint_web.sh $WEB_ROOT_DIRECTORY $WEB_DOMAIN $WEB_HOST_PORTNUM $WEB_CONTAINER_PORTNUM $WEB_HOST_PORTSSL $WEB_CONTAINER_PORTSSL $PHP_VERSION && /bin/bash"

Dockerfile

FROM ubuntu:latest
# args
ARG WEB_ROOT_DIRECTORY
ARG WEB_CONTAINER_PORTNUM
ARG PHP_VERSION
# install tzdata without interactive
ENV DEBIAN_FRONTEND=noninteractive
# apt update & install
RUN apt update \
 && apt install -y sudo \
                   vim \
                   less \
                   procps \
                   # network ss (instaed of netstat)
                   iproute2 \
                   # apache
                   apache2 \
                   # zip
                   zip \
                   unzip \
                   # SSL
                   openssl \
                   # git
                   git \
                   # pasword
                   passwd \
                   # Set Timezone
                   tzdata \
                   # Set local to jp.
                   language-pack-ja \
                && update-locale LANG=ja_JP.UTF-8 \
                   # cleaning
                && apt clean \
                && rm -rf /var/lib/apt/lists/*
# set env
ENV TZ="Asia/Tokyo" \
    LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
# install php
RUN apt update \
 && apt install -y php \
                libapache2-mod-php \
                php-fpm \
                php-common \
                php-mbstring \
                php-xmlrpc \
                php-gd \
                php-xml \
                php-cli \
                php-zip \
                php-curl \
                php-imagick \
                # SQLite
                php-sqlite3 \
                # MySQL
                #php-pdo \
                #php-mysql \
                # cleaning
                && apt clean \
                && rm -rf /var/lib/apt/lists/*
# composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
 && php composer-setup.php \
 && php -r "unlink('composer-setup.php');" \
 && mv composer.phar /usr/local/bin/composer
# PHP
RUN mkdir -p /run/php
RUN chown www-data:www-data /run/php
# php log
RUN mkdir /var/log/php
RUN chown www-data /var/log/php
RUN chmod 755 /var/log/php
# SSL
RUN mkdir -p /etc/ssl/private
# volume directory
RUN mkdir -p /template
RUN mkdir -p /var/www/${WEB_ROOT_DIRECTORY}
RUN mkdir -p /var/www/${WEB_ROOT_DIRECTORY}/web
RUN mkdir -p /workspace

entrypoint_web.sh

#!/bin/bash

# gen key & certificate
## localhost
openssl req -new -newkey rsa:2048 -nodes \
        -out /etc/pki/tls/certs/localhost.csr \
        -keyout /etc/pki/tls/private/localhost.key \
        -subj "/C=/ST=/L=/O=/OU=/CN=www.example.com"
openssl x509 -days 365 -req \
        -signkey /etc/pki/tls/private/localhost.key \
        -in /etc/pki/tls/certs/localhost.csr \
        -out /etc/pki/tls/certs/localhost.crt
## .env domain
openssl req -new -newkey rsa:2048 -nodes \
        -out /etc/ssl/private/server.csr \
        -keyout /etc/ssl/private/server.key \
        -subj "/C=/ST=/L=/O=/OU=/CN=*.${2}"
openssl x509 -days 365 -req \
        -signkey /etc/ssl/private/server.key \
        -in /etc/ssl/private/server.csr \
        -out /etc/ssl/private/server.crt

# setting file replace and copy
sed -e "s/WEB_ROOT_DIRECTORY/${1}/gi" \
    -e "s/WEB_DOMAIN/${2}/gi" \
    -e "s/WEB_HOST_PORTNUM/${3}/gi" \
    -e "s/WEB_CONTAINER_PORTNUM/${4}/gi" \
    -e "s/WEB_HOST_PORTSSL/${5}/gi" \
    -e "s/WEB_CONTAINER_PORTSSL/${6}/gi" \
        /template/apache/apache_vh.conf > /etc/apache2/sites-available/${1}.conf
sed -e "s/WEB_ROOT_DIRECTORY/${1}/gi" \
    -e "s/WEB_DOMAIN/${2}/gi" \
    -e "s/WEB_HOST_PORTNUM/${3}/gi" \
    -e "s/WEB_CONTAINER_PORTNUM/${4}/gi" \
    -e "s/WEB_HOST_PORTSSL/${5}/gi" \
    -e "s/WEB_CONTAINER_PORTSSL/${6}/gi" \
        /template/apache/apache_vh_ssl.conf > /etc/apache2/sites-available/${1}_ssl.conf

cp /template/apache/ssl.conf /etc/apache2/mods-available/ssl.conf

# apache module enable
a2enmod ssl proxy_fcgi setenvif rewrite
# apache conf enable
echo ServerName www.example.com:${4} >> /etc/apache2/conf-available/example.conf
a2enconf example php${11}-fpm
# apache cirtual site enable
a2ensite ${1} ${1}_ssl
# apache service start
service apache2 start

# PHP
/usr/sbin/php-fpm${7} &

php.ini

;; 略

; XDEBUG ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
xdebug.mode=debug
xdebug.client_host.=host.docker.internal
xdebug.client_port=9000
xdebug.remote_handler="dbgp"

このような設定。 PHP は 8.1 で php-fpm で動作させています。

現象と解決方法

この状態で composer.json に以下のようなスクリプトを記述。

    "scripts": {
        "start": "php -d variables_order=EGPCS -S localhost:8999 -t ./",
        "test": "phpunit",
        "test:coverage": "phpunit --coverage-html coverage"
    }

これで composer run test:coverage を実行すると……

ERROR  Code coverage driver not available.

と怒られてしまいました。

よくよく見ると、Xdebugそのものをインストールしていないので当然でした。

## 略

# apt update & install
# install php
RUN apt update \
 && apt install -y php \
                libapache2-mod-php \
                php-fpm \
                php-common \
                php-mbstring \
                php-xmlrpc \
                php-gd \
                php-xml \
                php-cli \
                php-zip \
                php-curl \
                php-imagick \
                # SQLite
                php-sqlite3 \
                # Xdebug
                php-xdebug \
                # cleaning
                && apt clean \
                && rm -rf /var/lib/apt/lists/*

## 略

これでもう一度コンテナをビルドして PHPUnit を実行。

XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set

今度は違う Warning が出ました。

; XDEBUG ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
xdebug.mode=debug,coverage
xdebug.client_host.=host.docker.internal
xdebug.client_port=9000
xdebug.remote_handler="dbgp"

php.ini をこのように変更してみます。 phpinfo();では Xdebug の項目で

Coverage    ✔ enabled
Step Debugger    ✔ enabled

と出るようになったのできちんと設定を認識していそうですが、実際に PHPUnit を実行すると同じ Warning で引っかかります。

そこで、 composer.json に次のように追記。

    "scripts": {
        "start": "php -d variables_order=EGPCS -S localhost:8999 -t ./",
        "test": "phpunit",
        "test:coverage": "XDEBUG_MODE=coverage phpunit --coverage-html coverage"
    }

これでどうでしょうか……と思ったら、また違う Warning が出ました。

No filter is configured, code coverage will not be processed

どうやら phpunit.xml の構文が古かったので引っかかるようになってしまった模様。

# ./vendor/phpunit/phpunit//phpunit --migrate-configuration

で XMLファイル をコンバートしたところ、漸くカバレッジを得ることができるようになりました。

参考

Ubuntu での Xdebug パッケージノインストール方法

php-xdebug

ERROR Code coverage driver not available.

phpunit.xml の構文

その他の phpunit.xml の Warning

XDEBUG_MODE=coverage or xdebug.mode=coverage has to be set

XDEBUG_MODE=coverage /usr/bin/phpunit

という書き方が composer.json の記述のヒントに。

No filter is configured, code coverage will not be processed

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。