特定のファイルを後からgitignoreに追加する方法

.gitignoreに追加してもリモートリポジトリにまだ残ってるやんけ!って毎回ググってるからいい加減覚えような

.gitignoreに除外するファイル(ディレクトリ)を追加してから以下を実施する

$ git rm --cached ファイル名
$ git add .
$ git commit -m "chore: .gitignoreに追加"

ディレクトリを除外する場合は$ git rm --cached -r ディレクトリ名でOK


webpackのbuildでmain.js.LICENSE.txtを出力させない

webpackでproduction buildしたときdist/にmain.js.LICENSE.txtを生成させたくなかった

const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  // ...省略
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false,
      }),
    ],
  },
};

extractCommentsがデフォルトでtrueになっているのでfalseにする。
デフォルトは@preserve @license @cc_onアノテーションがあるコメントを拾ってくるっぽい。
allにすると全てのコメントが出力される


Slackbotからの通知メッセージをattachmentsでリッチにする

通常のテキストはこんな感じ↓

これを↓にする

GASのコード

感想

最初attachmentsに渡す値を配列にしてなかったから送信されなくてハマった。。。
個人的な用途としてはcolor, title, title_linkだけで十分かも。(footer_iconとかいらんくね?)

参考

api.slack.com


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。


【TypeScript】Graph API を使って Instagram の投稿を取得する【2022年6月版(v14.0)】

React x TypeScriptでInstagramの投稿をサイト上に表示したかったときの備忘録
使用したGraph APIはv14.0だけど12.x, 13.xあたりも同じっぽい(多分)(未検証)

全体的な流れのうち、1, 2は書くのめんどいので割愛

  1. Instagramをプロアカウントに変更する
  2. facebookInstagramをリンクさせる
  3. facebook developerアカウントでアプリの作成
  4. Graph APIで使用するアクセストークンの有効期限の延長
  5. instagram business idの取得
  6. instagramの投稿の取得してサイトに表示する

1. facebook アプリの作成

アプリタイプにビジネスを選択してアプリの作成

基本情報は適当に入力してアプリ作成後に表示されるアプリIDapp secretをメモっておく

2. 無期限アクセストークンを取得する

グラフAPIエクスプローラ画面で短期アクセストークンを取得する

以下の項目を指定する

  • Facebookアプリ
    • facebook developerアカウントで作成したアプリ
  • ユーザーまたはページ
  • アクセス許可
    • pages_show_list
    • business_management
    • instagram_basic
    • instagram_manage_comments
    • instagram_manage_insights
    • pages_read_engagement
    • pages_read_user_content
    • public_profile (デフォルトで設定済み)

インスタの投稿だけだし instagram_basic しか使わんだろって思って instagram_basicpublic_profile のみにしてたら無期限アクセストークンが取得できなくてハマった。(公式読め)

Generate Access Token を押すとアクセス許可のためのダイアログが表示されるから許可

③アクセストークン左のinfoマークを押してアクセストークンデバッガーの画面に行く

有効期限が1時間以内になっているので「アクセストークンを延長」を押す

④ 長期アクセストークンが表示されるのでデバッグボタンを押す

発行されたアクセストークンの有効期限が「受け取らない」になってればOK

3. instagram business id を取得する

グラフAPIエクスプローラ画面か、Postmanとかで↓を叩くと、

https://graph.facebook.com/v14.0/me?fields=accounts{instagram_business_account}&access_token=[2.で取得したアクセストークン]1

↓下記の形式でデータが戻ってくるので accounts.data.instagram_business_account.id をメモっておく

{
  "accounts": {
    "data": [
      {
        "instagram_business_account": {
          "id": "123456"
        },
        "id": "123456"
      }
    ],
    "paging": {
      "cursors": {
        "before": "xxxxxx",
        "after": "xxxxxx"
      }
    }
  },
  "id": "123456"
}

4. instagramの投稿の取得

https://graph.facebook.com/v14.0/[3で取得したinstagram business id]?fields=media.limit([取得したい件数]){[取得したいfieldをカンマ区切りで指定]}&access_token=[2で取得したアクセストークン] 2 (URLに()使うのモヤる)

をPostmanとかで叩くと、↓の形式で戻ってくるので media.data を使ってサイトに描画する

{
  "media": {
    "data": [
      {
        "media_url": "https://scontent.cdninstagram.com/v/xxxxxxxxxxxx/",
        "media_type": "CAROUSEL_ALBUM",
        "id": "123456"
      }
    ],
    "paging": {
      "cursors": {
        "before": "xxxxxxxxxxxx",
        "after": "xxxxxxxxxxxx"
      },
      "next": "https://graph.facebook.com/v14.0/xxxxxxxxxxxx"
    }
  },
  "id": "123456"
}

5. 記事の取得

export default class Instagram {
  public async fetchPosts(limit: number): Promise<Post[]> {
    return await axios
      .get(
        `https://graph.facebook.com/${process.env.GRAPH_VERSION}/${process.env.GRAPH_BUSSINESS_ID}`,
        {
          params: {
            fields: `media.limit(${limit}){media_url,thumbnail_url,permalink,media_type}`,
            access_token: process.env.GRAPH_ACCSESS_TOKEN,
          },
        }
      )
      .then((res) => res.data.media.data)
      .catch((error) => {
        console.error(error);
      });
  }
}

(※ import などは省略)

今回はReactで実装

<section>
  {this.props.posts.map((row: Post, index: number) => (
    <Box {...row} key={index}></Box>
  ))}
</section>

最終的に↓みたいなマークアップで描画されればOK

<a target="_blank" rel="noopener noreferrer" href="https://www.instagram.com/p/xxxxxx/">
  <img src="https://scontent.cdninstagram.com/v/xxxxxx">
</a>

参考

developers.facebook.com developers.facebook.com developers.facebook.com developers.facebook.com

※本記事は2022/06/26時点の情報です。


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。



  1. URLの中で動的な部分は[]で囲っています。
  2. URLの中で動的な部分は[]で囲っています。

hoverしたときにテキストの枠線を左から右に引く

要点

  • 通常時は疑似要素のborder-bottom: 1px solid #000; を指定したまま width: 0%; にしておく
  • hoverしたときにwidth: 100%; にする

ボタンをhoverしたときに背景色をスライドさせる

これのこと

HTML

<button type="button">Button</button>

ボタン部分

button {
  background-color: #fff;
  width: 200px;
  display: block;
  padding: 10px 0;
  border: 1px solid #000;
  position: relative;
  z-index: 1;
}
button:hover {
  color: #fff;
}

ボタン背景部分

button::before {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  content: '';
  background-color: #000;
  transform-origin: right top;
  transform: scale(0, 1);
  transition: transform .3s;
}
button:hover::before {
  transform-origin: left top;
  transform: scale(1, 1);
}

要点

  • transform: scale(0, 1); で疑似要素の背景を非表示にしておく
  • hoverしたときにtransform: scale(1, 1);にして表示する
  • あとはtransform-originでスライドさせたい方向の値を指定する

コード全文

button {
  background-color: #fff;
  width: 200px;
  display: block;
  padding: 10px 0;
  border: 1px solid #000;
  position: relative;
  z-index: 1;
}
button:hover {
  color: #fff;
}
button::before {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: -1;
  content: '';
  background-color: #000;
  transform-origin: right top;
  transform: scale(0, 1);
  transition: transform 0.3s;
}
button:hover::before {
  transform-origin: left top;
  transform: scale(1, 1);
}

その他

感想

右から左、みたいに特定の方向に向かってスライドさせたい場合っていちいち疑似要素使わなかん感じ?クソめんどくね?


スプレッドシートで列名のアルファベットを取得する

B2セルで=COLUMN()したときにBじゃなくて2が表示される

俺がほしいのはBなんだよ!!

ということで完成形

=SUBSTITUTE(REGEXEXTRACT(ADDRESS(ROW(), COLUMN()), "\$.+?\$"), "$", "")

長すぎる......
セル確認したときにぱっと見でどこ参照しているのか分かりづらすぎるんだけどもっと簡単に取得できる方法ない??

やってること

  1. ADDRESS()でセル参照を文字列で取得
  2. $B$2が取得できるのでREGEXEXTRACT()$で囲まれている文字列を検索
  3. SUBSTITUTE()$を空文字に変換

実際はBだけを使うことはないのでINDIRECT()と一緒に使ってセル参照する

Cloud Firestore で collection の基本操作

Cloud Firestore使ったときのメモ
検証用のメモだからダブルクウォートとシングルクウォートが混ざってるのは気にするな

Create

const docRef = await addDoc(collection(db, "collection"), {
  title: "title",
  description: "description",
  created_at: new Date(),
});
console.log("Document written with ID: ", docRef.id);

Read

// 単一の取得
const ref = doc(db, "collection", "document ID");
const snap = await getDoc(ref);
console.log(snap.data());

// 複数取得
const q = query(collection(db, "collection", where('title', '==', 'title')));
const snap = await getDocs(q);
snap.forEach((doc) => console.log(doc.data()));

// 順番と件数を指定して複数取得
const q = query(collection(db, "collection"), orderBy('created_at', 'desc'), limit(2));
const snap = await getDocs(q);
snap.forEach((doc) => console.log(doc.data()));

Update

const ref = doc(db, "collection", "document ID");
await updateDoc(ref, {
    title: "update title",
});

Delete

await deleteDoc(doc(db, 'collection', 'document ID'))

参考

firebase.google.com


Atomフィードで取得したRedmineのチケット情報をSlackに通知する

いつ使うの?

以下の状態のRedmineを使用しているとき

  • チケット操作時にメール通知が来ない
  • APIアクセスキーが発行されていない
  • 管理権限を持っていない

Slack App のRSSインテグレーションも使ったんだけど新規チケットの通知しか来ないし思いの外役立たずでした

前提

  • Googleアカウント作成済み
  • Slackbot作成済み

コード

Slackへのリクエストは以下のライブラリを使用しています

github.com

function myFunction() {
  // Redmine のチケット情報を取得する
  const feedURL = 'https://{Redmineのドメイン}/issues.atom?query_id={適当なquery_id}&key={Atomアクセスキー}';
  const response = UrlFetchApp.fetch(feedURL);
  const xml = XmlService.parse(response.getContentText());
  const rootDoc = xml.getRootElement();
  const atom = XmlService.getNamespace('http://www.w3.org/2005/Atom')

  const items = rootDoc.getChildren('entry', atom)
  if (items.length === 0) {
      return;
  }
  const slackUserId = '{SlackのUserId}';
  let message = '<@' + slackUserId + '> さん宛の Redmine のチケットが' + items.length + '件あります。\n';

  items.forEach((item) => {
    const title = item.getChild('title', atom).getText();
    // `/1111` のようにスラッシュ+数字の羅列をチケットIDとみなして検索
    const issueId = item.getChild('id', atom).getText().match(/\/([0-9]+)/)[1];
    // タイトルの文字列にチケットのリンクを貼る
    message += '<https://{Redmineのドメイン}/issues/' + issueId + '|' + title + '>\n';
  })

  const options = {
    username: 'Redmine',
    link_name: true,
    icon_emoji: ":redmine:"
  };
  // slackに投稿
  const slackApp = SlackApp.create('xoxb-XXXXXXXXXXXX'); // Slackbot のtoken
  slackApp.postMessage("#Slackのチャンネル名", message, options);
}

((GASのシンタックスハイライトってjavascriptでいいの…?))

後は定期実行のトリガーを任意で作成してSlackに通知が来ればOK

感想

早くGASでもテンプレートリテラル使えるようにしてくれ


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。


Toggl の作業内容を Redmine に登録する

弊社では作業管理ツールに Redmine を使用しています。
チケットと呼ばれる各タスクに作業時間を入力して、↓みたいに退社時に Slackbot で1日の作業内容がわかるようになっています。

(ちなみに上記のSlackbotは弊社代表の自作です。)

ただ、これが死ぬほどめんどくさいんです。
作業開始時の時刻を覚えておいて作業が完了したら終了時の時刻から逆引きして時間を出すっていう、算数の引き算でつまずいた人間からすると地獄です。冗談抜きに引き算だけで30分くらい取られます。
ということでめんどくさいことはプログラムにやらせよう!

事前準備

Toggl アカウントの作成

toggl.com

これがなきゃ始まりません。
アカウント作成後の profile 画面に API Token が発行されているのでコピります。

Toggl 拡張機能のインストールと設定

chrome.google.com

なくても成立はするんですが、Redmine 上でタイマーを打ちたいのでインストールします。
インストール後に、拡張機能の設定で Integrations メニューの中で Redmine のURLを有効にします。
(自社サーバーに Redmine を立ててる場合は Custom URLs for integrationsドメインを登録すればOK)

最終的にRedmine のチケットを見た時に Toggl のボタンが表示されてればOK

プログラムの作成

最低限必要な処理以外は省略してるので「リクエスト周りのエラーハンドリングどうすんの?」とか言う輩は帰ってください。
アーキテクチャ警察も帰ってください。

API Document は以下を参照しました

① 当日の作業記録を取得する

<?php
$togglUri = 'https://api.track.toggl.com/api/v8/time_entries';
$options = ['auth' => [$_ENV['TOGGL_API_TOKEN'], 'api_token']];
$dateTime = new DateTimeImmutable();
$query = http_build_query([
    'start_date' => $dateTime->setTime(00, 00, 00)->format('c'),
    'end_date' => $dateTime->setTime(23, 59, 59)->format('c'),
]);
$entryRequest = (new \GuzzleHttp\Client())->request('GET', "{$togglUri}?{$query}", $options);

$res = $entryRequest->getBody()->getContents();
$entries = json_decode($res, true);

② Toggl の作業内容から Redmine に登録する作業時間データを用意する

<?php
// 活動の選択肢
$activities = ['作業内容1' => 1, '作業内容2' => 2];
foreach ($entries as $entry) {
    // 作業時間のコメント
    $description = $entry['description'] ?? '';

    // 作業時間の時間
    // 開始・終了の差分
    $start = new DateTimeImmutable($entry['start']);
    $stop = new DateTimeImmutable($entry['stop']);
    $diff = $stop->diff($start);
    $min = round(($diff->i / 60), 3);
    $diffHours = $diff->h + $min;

    // Redmine でタイマーを打ったときのフォーマットに沿って` #1234 `の文字列をチケットIDとして検索
    preg_match('/\ #([0-9]+)\ /', $description, $results);
    // description にチケットIDがあればサブパターンマッチの文字列を使用する
    // プロジェクトに紐付かない場合は任意のデフォルトのチケットIDを指定
    $issueId = $results[1] ?? $_ENV['REDMINE_DEFAULT_ISSUE_ID'];

    // タグの登録があれば1番目のタグを活動とする
    // なければデフォルトの活動IDを指定
    $activityId = $activities[$entry['tags'][0] ?? 1];

    // xmlの生成
    $xmlValues = [
        'issue_id' => $issueId,
        'spent_on' => $dateTime->format('Y-m-d'),
        'hours' => $diffHours,
        'activity_id' => $activityId,
        'user_id' => $_ENV['REDMINE_USER_ID'],
        'comments' => $description,
    ];
    $xml = new SimpleXMLElement('<time_entry></time_entry>');
    foreach ($xmlValues as $key => $value) {
        $xml->addChild($key, $value);
    }
}

Toggl の作業レコードごとに以下のようなXMLが生成されます。

<time_entry>
    <issue_id>1234</issue_id>
    <spent_on>2022-02-19</spent_on>
    <hours>1.0</hours>
    <activity_id>1</activity_id>
    <comments>タスク #1234 テキストテキストテキストテキスト</comments>
    <user_id>1</user_id>
</time_entry>

Redmine に作業時間を登録する

<?php
$redmineUri = "{$_ENV['REDMINE_URL']}time_entries.xml?key={$_ENV['REDMINE_API_KEY']}";
$request = (new \GuzzleHttp\Client())->request('POST', $redmineUri, [
    'headers' => ['Content-Type' => 'text/xml',],
    'body' => $xml->asXML()
]);

APIキーが発行されてない Redmine を使ってる場合は知りません!管理者に頼もう!

作業の登録

ここからがやりたかったことです

  1. 作業の前後で Redmine のチケット画面に表示されてる Toggl のタイマーをクリック

    タグを付けると活動がわかるのでタグだけ任意でつけておく
  2. 退勤前にプログラムを実行する
  3. Redmine に作業内容が登録されるのを確認する

最終的に↓みたいにして使ってます!

コード全文は以下に置いてあります。

https://github.com/m1y474/toggl-redmine

最後に

Toggl 過激派なので Toggl 布教の記事も書きたい…(Toggl Trackという名称を使ってない時点で過激派ではない)

ちなみにこのプログラムを作るまでは、下記サイトをiframeに埋め込んだものを拡張機能にして手動で計算してました!お世話になりました!2桁以上の引き算は苦手です!!

www.jalps.net

GitHubにpushしたらHerokuへデプロイする

f:id:IlIIlIIIlIIlI:20220217233028p:plain

Herokuデビューしたときの備忘録
結構前のだから手順抜けてるかも

前提

  • GitHubとHerokuのアカウントが作成済み
  • GitHubとHerokuのアカウントが連携済み

App作成

App nameは空欄にしとけばHeroku側で勝手に作ってくれるのでそのままCreate appを押す
名前つけたい場合は使われてない名前なら任意でつけてOK

f:id:IlIIlIIIlIIlI:20220217231302p:plain

Deploy設定

App作成後にDeployの画面が表示されるのでDeployment MethodGitHubを選択する

f:id:IlIIlIIIlIIlI:20220217231449p:plain

Connect to GitHubが表示されるので対象のRepositoryのConnectを押す

f:id:IlIIlIIIlIIlI:20220217231728p:plain

Automatic deploysで任意のブランチを選択してEnable Automatic Deploysをオンにする

f:id:IlIIlIIIlIIlI:20220217231932p:plain

手動デプロイがしたいときはManual deployから任意のブランチを選択してDeploy Branch押すだけ f:id:IlIIlIIIlIIlI:20220217232040p:plain


textareaの入力文字数によって高さを可変にする

こういうやつ

コード

<textarea></textarea>
<script>
  const textarea = document.querySelector('textarea');
  textarea.addEventListener('input', () => {
    textarea.style.height = null;
    textarea.style.height = `${textarea.scrollHeight}px`;
  });
</script>

改行の数をカウントしてtextareaのrowsに入れたりしたけど, それだと文字の折返しのときに高さが取れなかったのでtextareaのスクロール量を高さに入れるようにした

実際使うときは高さの上限決めた方がいいかも

LINEスタンプ作ったときの備忘録

直近死んだ犬2匹を素材にしてLINEスタンプを作りました。

store.line.me

store.line.me

これでトークの中でレオンとネネは生き続ける。嬉しい。
一気に2匹も死んでペットロス半端なかったけどこれで少しは緩和されるといいな

多分またいつか作るから備忘録というか忘れそうな部分をまとめておきます


作り方

[方法1] LINEスタンプメーカーを使って作る

creator.line.me 風呂でスマホ触っててスタンプ作ろうってなったときにこれにたどり着きました。
作成〜申請までスマホで1Hちょっとで作れたけど、写真素材の透過が甘かったり用意されてるステッカーがダサいあらかじめ決められてるから自由度は低いと思います。

[方法2] LINEクリエイターズマーケットを使って作る

自分でスタンプ素材を作ってWeb上で申請する方法。
LINEスタンプメーカーで作ったものが気に入らなくて作り直したときに使いました。
Photoshopなどのフォトエディタツールで作ったスタンプ素材をLINEクリエイターズマーケットに登録して、必要情報を入力して申請しました
スタンプ素材は自分の好きなように作れるけど、LINEスタンプメーカーよりは申請までの入力項目が多いので手間だと思います。

申請から承認までの時間

LINEスタンプメーカーとLINEクリエイターズマーケットの両方とも22時とか23時に申請して翌日の13~15時頃には結果が来ていました。

申請がリジェクトされた。再申請から承認までの時間は?

LINEスタンプメーカーで申請したときは一発で承認されたけど、LINEクリエイターズマーケットで申請したときはリジェクトされました。
販売情報のテキストに不備があったので修正して、その日のうちに再申請したら5時間後くらいには承認されました。
リジェクトされたらメッセージセンターに理由が書いてあるのでその通り修正して再申請すれば承認されると思います。

表示情報や販売情報の入力不備に関しては、LINEスタンプメーカーだとアプリが適当にやってくれるのでよっぽど不適切な文字列を入れない限り、リジェクトされる可能性は低いと思う。

承認されたけどリリースされない

承認されたらLINEクリエイターズマーケットのアイテム管理画面の右上らへんに赤いリリースボタンが表示されます。
リリースボタンを押すことでリリースが完了します。
LINEスタンプメーカーのときは忘れた。

リリースしたけど購入用URLに反映されない

5~10分くらい待てば反映されます。

スタンプのタグはなにを設定すればいいの?

特にこだわりがなければ何も設定しなくていいと思う。
設定した場合はLINEのトーク画面で文字入力時にサジェストとしてスタンプが出てくる。
設定しなかった場合はリリース後の2,3日後にLINE側で適当につけてくれる。
再申請は必要になっちゃうけど後日つけられたタグを確認して、このタグ違うなってものがあれば修正すればいい。

売上分配金が入らない

LINEスタンプメーカーで作るとデフォルトで売上分配金なしになってます。
申請前に絶対に確認しよう!
ちゃんと読んでない自分も悪いけどこれ罠だと思う。せめてデフォルト未選択にしてほしいっす…

制作ガイドラインについて

スタンプの制作ガイドラインに以下の記載があります。

トリミングされた画像の外枠とコンテンツの間には10pxぐらいの余白が必要です。画像をつくるときは上下左右のバランスに注意してください。

ガイドラインを一通り読んだ上で自分はこれをガン無視しました。余白なしで外枠ギリギリまで素材を設置しました。
リジェクトされたときに「あーやっぱりか」って思ったけどリジェクトの理由は前述の通り、販売情報のテキストの不備だったので関係ありませんでした。
これ実際どうなんだろう?みんな守ってるのかな?写真スタンプだと関係ないとかある?

使用したツール

LINEクリエイターズマーケットでのスタンプ作成時に以下のツールを使用させていただきました。
ありがとうございました。

medibangpaint.com

lightbox.on.coocan.jp

fukidesign.com

apngasm.sourceforge.net

※本記事に記載されている内容は2022/02/06時点のものです。

hoverでbackground-imageを切り替えたときの画像のチラつきが気になる

↓これのこと*1

なんでチラつくの?

レンダリングのときは読み込まれなくて、hoverのときに初めて画像を読み込んでるから
Networkタブを見てみるとhoverのタイミングで読み込んでるのがわかる

対応策

① preloadで読み込んでおこう

headタグの中にpreloadを指定する

<link rel="preload" href="./images/hover.png" as="image">

あらかじめ読み込んでくれてるので初回hoverでもチラつかない!

preloadって何?

指定したリソースを先に読み込むようにブラウザに指示すること
imageの他にもstyleやscriptなども指定できる
詳しくはMozilla先生に聞いてね developer.mozilla.org

② background-imageにhoverの時の画像も指定しよう

hoverしたときじゃない方のbackground-imageにhoverしたときの画像も指定して非表示にしておく

button {
    background-image: url(./images/normal.png), url(./images/hover.png);
    background-size: contain, 0; // 2枚目はsizeを0にしておく
    &:hover {
        background-image: url(./images/hover.png);
    }
}

(環境によるのかもだけど)preload指定してNetworkタブで事前に読み込まれてるのも確認してるのに、何故かチラつくことがあるので②のほうが確実だと思う

感想

正直俺は気にならん


*1:チラつかないときはブラウザキャッシュを削除してみてください

PHPUnit で作成したテスト項目をHTMLで出力する

PHPUnit--log-junit オプションでxmlを出力する

# ./vendor/bin/phpunit tests/Feature/SampleControllerTest.php --log-junit output.xml
PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 00:00.901, Memory: 24.00 MB

OK (1 test, 1 assertion)

xsltproc を使用して html に変換する

# xsltproc phpunit.xslt output.xml > output.html 

(※ xsltproc コマンドがない場合は別途インストールする。Macにはデフォルトで入ってるはず。。)
出力用のテンプレ(phpunit.xslt)は以下よりDL

PHPUnit xslt · GitHub

htmlが出力されていることを確認
テンプレを変えたい場合は phpunit.xslt を任意で修正すればOK

参考

https://phpunit.readthedocs.io/ja/latest/textui.htmlphpunit.readthedocs.io

inokara.hateblo.jp

qiita.com


株式会社エイルシステムではWebエンジニア・モバイルアプリエンジニアを募集しています。
実務経験がなくてもOKです。ご興味のある方は弊社HPよりご連絡ください。