ほげほげ見聞録

技術メモ、備忘録、使い方はそのうち覚える

TDDを始めて~CI環境構築の振り返りメモ

試しに遊んでみたSphinxでブログ書くのが楽しくなっているのでサイトの方で更新してます。




タイトルのとおり振り返りメモ、ポエムともいう。…てか、どう見てもポエムです。
主に今年にやっていた自動テストとCI環境構築について感じた事考えた事をダラダラっと書いていく。

自動テスト・TDD・リファクタリング

「自動テスト」という単語を知るまで

哀しいかな、自分が属している会社では自動テストを書くという習慣はなく、当然ながら存在を知る機会はなかった。
ぼちぼち仕事にも慣れてきた頃に、オブジェクト指向を勉強しなおしたりデザインパターンに出会ったりした。
その時ようやく、会社のコードはレガシーで「テスト」はブラウザチェックだけだな…という漠然とした印象を抱いた。
といっても、その時は話しやすい先輩に「何だかレガシーですよね」と愚痴る程度だったが。別に自分がレガシーじゃないコードを書けているかというとそうでもないので以下略。

その後、長期的なプロジェクトに参加・続ける内に、コードを改修する事が増えてきた。
必要だと思って「リファクタリング」「レガシーソフトウェア改善ガイド」等をざっくり読んでみたが、どちらとも「テストを書け」とあった。しかも自動化。
自動でやると言えばSeleniumくらいしか知らなかったので、そんなものがあったのかと軽く驚き。
調べてみて「テスト書くのは当たり前」などの情報に遭遇してカルチャーショックを受けた。
調べても必要性と導入方法が分からなかったり、振られたタスクで忙しかったり、諸々に嫌気がさしてソシャゲに時間を費やしてしまったり…等の事情でその時は調べるだけに終わった。

テスト駆動開発」の新訳版が出た

プロジェクトも落ち着いてはきたが、何かうまくいっていないという思いがあったので読んでみた。
詳しくは以下の記事。
dwmemo.hatenablog.com

TDD続けてみてどうなったか

上の記事から数カ月経ったわけだが、新規案件では必ずテストを書くように習慣付いた。
改修だと余計なファイルを追加してはいけない場合、そもそも自前のフレームワークでテストを書けない場合もあった。
余計なファイルを追加できない場合は、グローバルインストールしたPHPUnitからテストを実行し、テストファイルと関連の設定ファイルをGitと並行して使っているVCSで管理するという感じで対応した。

簡単な処理はテスト書かずに実装したり、難しそうで関連部分でリファクタリングが必要な場合はテストを書いたりと、緩くやるのが自分には合っているようだ。

必ず失敗はある

自動テスト及びTDDに詳しい人がいれば教えを乞いながら進めるが、本だけだと完全に手探りだ。
テストして問題なかったと思ったら、実はテストが間違っていたなんて事は最初の内は特に多いと思う。
可能ならチーム内で確認する機会を設ける、失敗が許容されやすいプロジェクトでやる、等の予防線を張っておきたい。

カバレッジ

カバレッジは気にしない方が良いという話も聞いた。
数字が出てしまう以上、完璧にしたい欲求や要望が出るのはしょうがないとも思う。
こういう時は「どうしてテストするのか」原点に戻って考え直したい。

また、テストが書かれていない部分を修正する時も無理に全部のテストを書かなくても良いと思った。
例えば、ある関数の一部分を修正する際に、修正部分以外の簡単な部分までテストを書く必要性は低いという事だ。その部分は既に別の方法でテスト済だろうから。
ただし、修正部分に影響されたりロジックが難しかったりする場合は、折角だし追加で書いた方が良いと思う。

CI環境構築

自動テストを書くようになってから、CI環境構築しろという指令があった。
構築している間にPHPerKaigiに行く機会があって、色々な人の話を聞けたのは大きかった。
dwmemo.hatenablog.com

あんな事こんな事あったでしょ

ブランチをプッシュするとテスト環境にデプロイ・テスト実行できるようにJenkinsおじさんに頑張ってもらった。
dwmemo.hatenablog.com

とっても気を付けたい事

一番、大事なのは、公式ドキュメント。
グーグル先生の検索結果の上位に出てきた個人のテキトーな解説ブログではない。

特にひどかったのがCSRFのチェックでJenkinsのジョブを実行できない件について。ろくな解説なしに、「CSRFのチェックを外せば解決」という一文程度の記事が出てくる。
大事なのは、どうしてそんな設定があるのか、その設定は変更しても良いのか、変えずに処理を行う方法はないのか、考察する事なんではないかと思った(自戒を込めて)。

構築して分かった事

CIは多少時間が掛かっても・余計な操作があったとしても「確実に」実行するものが良い。

今回はプッシュされたブランチを判断するのにNodeJSを挟んでみた。
しかし、見事にその部分でエラーを起こしCIが回らなくなってしまった。
特にNodeJSの部分はエラーが出ても分からない(通知が来ない)という問題点があった。
結局、NodeJSの部分は省いて時間は掛かるがブランチがプッシュされる度にデプロイする方式が選ばれた。

エラーがすぐに分かる事、エラーをすぐに直せる事、そもそもエラーを発生させない事…が特に重要だと思う。

CIに限らずプロジェクトのコードは全てそうあるべき、と言われてしまうと御尤もとしか言えないが…。
記憶容量を割く必要がないのは断然CIの方なので、特に気を付けるべきかなと思う次第だ。

ドキュメント

完成すると「はい、終わり」にしてしまわずに、もしもこの環境を自分以外の人がいじるとしたら…という考えを持ってドキュメントを残しておくべきだ。
頑張った事でも案外すぐに忘れる。

最低限、処理の流れと使っているものについては残しておきたい。
本当だったら社内ウィキ等に試行錯誤の経過を残しておいた方が良いと思われる。
自分の場合は社内ウィキを使う人がほぼ皆無だったので個人ブログのネタにしてしまったが。

テストはCIに組み込むべき

誰もが自動テストを書いて手元でテストスイート実行してくれるなら問題ないんだけどね。
それでも、テストに不慣れなチームだとA環境ではエラーなし、B環境ではエラー発生なんて事が起こりうる。
どの環境でも実行できてエラーの起きないテストを書き、それを自動的に確認するという手順を踏むと不安は減るかなと思う。

最初は組み込む予定なかったのに、途中からCIに入れるとなるとテストの手直しがかなり面倒になる。これは本当に、もう面倒…。


まとめ

自動テストを書き始めてから大体三、四ヶ月で、ようやく必要性や実用性が分かって来た。
また、チームや組織にテストを書く文化を広めるのは一人でやるのは無理なのが良く分かった。自動テストを書く・TDDは個人スキルと割り切ってしまうのが楽。
何をするにも権限が必要だし、権限を貰ったからには責任が付いて回る。
自動テストを強要する権限があったとして、メンバーの残業時間増やせるのか・工数増やせるのかとかそういう問題になってくる。それはプログラマの問題ではなくマネージメントの問題だ。
ここら辺はチームや会社によっても変わるんだろうけど。

CI環境構築ではネットワーク部の主と直接やり取りができて良かった。
普段一緒に仕事しない人の話を聞けるのは勉強にもなるし。
英語苦手なので、公式ドキュメントをヒーヒー言いながら読むのも、まぁ勉強に…なったかな。

TDDにしろCIにしろ、まだまだ勉強不足なので気長に付き合っていこうと思う。

MySQLのautocommitが原因でセーブポイントのエラーが出た時のメモ

事の始まり

PHPUnitでtestsuiteを実行したら以下エラーが発生。

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1305 SAVEPOINT LEVEL2 does not exist


エラー自体は公式のマニュアルにある。
セーブポイントが削除されているのに操作すると発生…という事は分かった。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.4 SAVEPOINT、ROLLBACK TO SAVEPOINT、および RELEASE SAVEPOINT 構文

原因調査

エラーが起きるのは以下のテスト。

DBにテストデータを作る
→DB操作のテスト
→テストデータ消去と「ALTER TABLE t1 AUTO_INCREMENT=1」実行

この流れの「DB操作のテスト」内に実行するコミットで前述のエラーが発生している。

発生条件は以下。

  • testsuiteで複数テストを実行
  • 最後の「ALTER TABLE」を実行(代わりに「UPDATE」など実行してもエラーは出ない)

条件を絞る

テスト範囲を絞ってみると、エラーが起きる流れが分かった。テスト2でトランザクションの開始が増えるとセーブポイントのレベルの数が変わる。

  1. テスト1でトランザクションの開始とセーブポイントの作成をして、コミットしないままALTER TABLEを実行
  2. テスト2でトランザクションの開始とセーブポイントの作成をして、コミット(ここでエラー)
    解決方法はテスト1でコミットを実行する事だと分かった。
    しかし、原因が分からない。

チョットデキル人に聞いてみる

Google先生に張り付いて検索頑張るのも良いけど、詳しい人に教えを乞うのもたまには良いんじゃないかなと。
TwitterMySQL Casual(http://mysql-casual.org/https://mysql-casual.slack.com/)というSlackが存在する事を知っていたので、参加して聞いてみることにした。

世界のyokuさんからの回答抜粋

ALTER TABLEは暗黙のコミットを引き起こすのでそれが原因かもしれません
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.3 暗黙的なコミットを発生させるステートメント


「暗黙のコミット」という機能があるらしい。
というわけで、フレームワーク内で起こっているのは以下の挙動のようだ。

テスト1でコミット時にセーブポイント1作成
→ALTER TABLEでMySQLの方でコミットされてセーブポイント1が消える(セーブポイントのレベルはフレームワーク内に残る)
→テスト2でセーブポイント2作成
→ネストしているトランザクションを順次コミットする際に、消えたセーブポイント2をRELEASEしようとしてエラー


また、ERROR 1305が発生する以下の一連のクエリも教えていただいた。
確かにALTER TABLE実行後にコミット(RELEASE)しようとしてERROR 1305が発生している。

mysql57 5> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> SAVEPOINT LEVEL1;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> ALTER TABLE t1 Engine = InnoDB;
Query OK, 0 rows affected (0.32 sec)

Records: 0  Duplicates: 0  Warnings: 0


mysql57 5> RELEASE SAVEPOINT LEVEL1;
ERROR 1305 (42000): SAVEPOINT LEVEL1 does not exist

mysql57 5> SAVEPOINT LEVEL2;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> RELEASE SAVEPOINT LEVEL2;
ERROR 1305 (42000): SAVEPOINT LEVEL2 does not exist


んでもって、以下autocommitをオフにした場合のクエリを教えていただいた。

mysql57 5> SET autocommit= 0;
Query OK, 0 rows affected (0.07 sec)

mysql57 5> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> SAVEPOINT LEVEL1;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> ALTER TABLE t1 Engine = InnoDB;
Query OK, 0 rows affected (0.19 sec)

Records: 0  Duplicates: 0  Warnings: 0


mysql57 5> RELEASE SAVEPOINT LEVEL1;
ERROR 1305 (42000): SAVEPOINT LEVEL1 does not exist

mysql57 5> SAVEPOINT LEVEL2;
Query OK, 0 rows affected (0.00 sec)

mysql57 5> RELEASE SAVEPOINT LEVEL2;

Query OK, 0 rows affected (0.00 sec)


autocommitをオフにしてテストを実行してみる

テスト1のALTER TABLE前に`SET autocommit= 0`を実行したところ、テスト2のコミット時にセーブポイントのエラーが出なくなった。

参考

なにはともあれマニュアルを読む。
MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.3.1 START TRANSACTION、COMMIT、および ROLLBACK 構文

autocommitの挙動の話。
MySQLのトランザクション制御がキモい話 - なからなLife
MySQLのAUTOCOMMIT(オートコミット)覚え書き | OpenGroove

autocommitはセッション内でしか有効にならないが、my.cnfでも設定できるらしい。
MySQLのautocommitとトランザクション分離レベルのメモ - Qiita

まとめ

何もない時は一般的なDB知識でクエリを作って実行させるだけだが、謎のエラーに出くわした時に困るので使用DBMSのマニュアルはこまめに確認しておこう。
関連コミュニティなどで詳しい人に助けを求めるととても勉強になる。

回答の詳細はMySQL Casualに参加すると見られるので、気になる人は登録するといいんじゃなかろうか。
自分の質問文は分かりにくいので、もっとうまく質問できるようになりたいなぁ。

グローバルインストールしたPHPUnitをcygwinから実行


自前のフレームワーク使ってたり…テストコードを追加すると注意されたり…してプロジェクトにインストールできない、そんな時もテストしたいよねって場合に使うのがグローバルインストールしたPHPUnit
いざグローバルインストールしたけど、cygwinからPHPUnitを実行できないという問題が発生したので解決メモ。

状態

phpunit.pharを落としてきたはいいけど、cygwinからは実行できずコマンドプロンプトからだと実行可能。
PHPUnit マニュアル – 第1章 PHPUnit のインストール

問題

cygwinだと以下のエラーが発生する。

$ phpunit --debug test/setup.php
Could not open input file: /cygdrive/d/bin/phpunit

phpunitコマンドを認識できていないらしい?

権限を変更?

chmod 755にしてみるも変わらずエラー。

.bash_profileにalias追加?

「【cygwinパス】\home\【ユーザ】.bash_profile」に以下を追加すると実行可能になるという情報を見かける。

alias phpunit="/cygdrive/d/bin/phpunit D:/bin/phpunit"

Cygwin and PHPUnit: Could not open input file: /cygdrive/c/xampp/php/phpunit - Stack Overflow

追加したけど変わらずエラーが出る。

.bashrcにalias追加

aliasの設定は「【cygwinパス】\home\【ユーザ】.bashrc」に追加するという情報を見かける。
上の項目で追加したaliasをこちらに追加すると、PHPUnitが実行された。

ちなみにデフォルトだと色は付かないので、--colors=alwaysオプションを指定する必要あり。
最終的には以下のaliasを追加した。

alias phpunit="D:/bin/phpunit --colors"

これだとデフォルトで色が付く。

alias便利

案件ごとにPHPのバージョンが変わるので、そんな時にPHPUnitもバージョン変更する必要がある。
こうしておけば、プロジェクトのsrcフォルダでphpunitの切り替えが楽。

alias phpunit7="D:/xampp_7.2.4/php/php.exe vendor/phpunit/phpunit/phpunit"



cygwinの.bash_profileと.bashrcの違い

マニュアルと各ファイルの説明を読んだので追記。
マニュアル内に、.bashrcでaliasを設定可能とある
Customizing bash

.bash_profileはファイル内に、ログインシェルの時に実行される…という説明があった。

.bashrcがある場合は実行シェル内でsource .bashrcするので、結局.bashrcに書いておけばいいようだ

余談

.bash_profileも.bashrcも、cygwinを再起動しないと変更した設定が反映されなかった。
変更したのにナンデ!? となるので毎回cygwinを終了させる必要がある。

PHPUnitをJenkinsから実行するメモ


Jenkinsのジョブを実行してPHPUnitを実行する設定のメモ。
更に、テスト実行のジョブを下流ジョブに設定してメールの送信も試す。

例によってフレームワークFuelPHP
デプロイ前にテストを実行出来たら良いのだろうけど、Jenkinsのあるサーバの環境をいじれなかったので流れは以下。

  • developの内容をテスト環境にデプロイ
  • テスト環境上でPHPUnitを実行

そもそもテスト環境でPHPUnitを実行できるか

SSH使ってテストサイトで「php oil test --testsuite=app」を実行。
エラー発生。
「fuel/vendor/bin/phpunit: 許可がありません」

以下のファイルに権限の設定をする必要がある。
chmod 755なら問題ないだろう。


再度実行すると、oilパッケージ内で以下エラー。
「Tests Running...This may take a few moments.: そのようなファイルやディレクトリはありません」

エラーが出ているのはoilパッケージ内のpassthru()で実行しているコマンド。
「fuel/vendor/bin/phpunit -c "fuel/app/phpunit.xml"」

statで確認したが、二つとも存在。どちらもアクセス権は問題ないようだ。
実際に問題だったのは以下のファイル。

これらを色々いじっていたら、上書きした時に「php oil test」できることが判明する。
はて面妖なと考えていると、再び「そのようなファイルやディレクトリはありません」エラーが発生。
どうやらJenkinsが動いたので、ファイルを上書きされたらしい。

アップロードする方法によってテスト実行できるか試す。

  • FTPでアップロード:テスト実行可能
  • Jenkinsでアップロード:エラー発生

FTPでアップロードとJenkinsでアップロードしたファイルの違い

FTPでアップロード

$ stat fuel/vendor/bin/phpunit
  Size: 603             Blocks: 8          IO Block: 4096   通常ファイル


Jenkinsでアップロード

$ stat fuel/vendor/bin/phpunit
  Size: 620             Blocks: 8          IO Block: 4096   通常ファイル

なんかファイルサイズ増えてるんですけど……。
この現象はもう一つのファイル「fuel/vendor/phpunit/phpunit/phpunit」でも発生していた。

アップロード方法によらず、ファイルの文字列は同じ。
ということは、改行コードの問題だろうと分かる。

ちなみに、Backlogのリポジトリから取得したファイルはFTPでアップロードしたものと同サイズ。
つまり、Jenkinsでデプロイする際に一度ワークスペースにファイルを落としてきているのが原因ではないか。
実際に確認すると、ワークスペースのファイルの改行コードはCR+LF、リポジトリはLF。CR+LF→LFに変換するとリポジトリと同サイズになる。

どうやら、Gitの改行コード変換の問題らしい。
Git - Git の設定

Jenkinsのサーバで以下のコマンドを実行。

git config --global core.autoCRLF false


再度デプロイした後にPHPUnitを実行すると、無事に実行できた。

JenkinsジョブからPHPUnitを実行

ファイルのアップロードは済んでいる状態。
別ホストのテスト環境にログインするには「SSH Plugin」使うと楽。

認証情報の追加は、システムの設定 > SSHリモートホスト > 追加。
テスト接続で「Successfull connection」が出れば良し。

ジョブの設定で、ビルド > リモートホストでシェルを実行を追加。
SHリモートホストに追加したサイトを選択。

cd 【oilパス】
php oil test --testsuite=app

ビルド実行してログに「[SSH] exit-status: 0」出力されていれば成功。

パイプラインから下流プロジェクト実行

外部から呼び出すのはパイプラインのジョブなので、これにPHPUnitを実行するジョブを組み込む。
デプロイが成功していればテストを実行するという流れ。

ビルドの成功チェックは以下で可能。

def jobBuild = build job
def result = jobBuild.getResult()

if (result == "SUCCESS") {
}

Google グループ

パイプラインだとエラー時の動作設定がないのでscript中に追記。
Cleaning up and notifications

Scripted Pipeline(nodeで囲む)でなくDeclarative Pipeline(pipelineで囲む)使うと、エラー時の動作などかけるので便利。
Pipeline Syntax
Pipeline Syntax

Declarative PipelineでJenkinsfileを書いてみた(Checkstyle,Findbugs,PMD,CPDとか) - Qiita

というわけでスクリプトの流れ。

def jobResult

pipeline {
    agent any

    stages {
        stage('deproy') {
            steps {
                script {
                    // デプロイ
                    build Deploy
                }
            }
        }
        stage('PHPUnit') {
            steps {
                script {
                    // テスト実行
                    build Test
                    // テスト結果をメール
                    def consoleURL = "${env.JENKINS_URL}/job/【ジョブ名】/${env.BUILD_NUMBER}/consoleText"
                    def data = new URL(consoleURL).getText()
                    mail to: '【アドレス】',
                         subject: 'test',
                         body: data
                }
            }
        }
    }

    post {
        failure {
            script {
                //エラーメール送信
            }
        }
    }
}



変数

pipelineを使うDeclarative Pipelineを調べていると、変数をenvironmentで宣言するなどという情報が出てくる。
試してみたが、environmentで宣言したものは定数で後から変更できなかった。
変数宣言はpipelineの外で行わないとエラーが出る。
ぶっちゃけdefなしだとglobalな変数になるようなので、宣言しない手もある。

The Apache Groovy programming language - Documentation

テスト結果のメール

PHPUnitを実行すると、以下のようなメールが飛ぶ。
プラグイン導入してカバレッジ出す方が良さそうな気もするが、とりあえずどの程度テストが実行されているかのチェックはしやすい。

[SSH] script:

php oil test --testsuite=app

[SSH] executing...
[0;32mTests Running...This may take a few moments.[0m
PHPUnit 6.0.0 by Sebastian Bergmann and contributors.

.......................S...SSSSSS  33 / 156 ( 21%)
...RR..RRR
[SSH] completed
[SSH] exit-status: 0

Finished: SUCCESS
-------------------------

テスト結果が途中で途切れるのは、リダイレクトが実行された場合など。
最後まで到達してテストが失敗になっていないと、失敗として認識されないらしい。

余談:Jenkinsからメールを飛ばす設定

ビルドの成功失敗やテストの結果など、毎回Jenkinsを開いて確認するのは面倒。
そこで、Jenkinsの管理でメール送信の設定をする。

envelope-from(return-path)は、Jenkinsの位置 > システム管理者のメールアドレスに設定。
紛らわしいが、E-mail 通知 > 返信先アドレスではないので注意。

E-mail 通知の項目で入力値を使ったテスト送信ができる。
ただしテスト送信の結果が正常でも、envelope-from(return-path)が存在しないアドレスだとエラーになってメールが届かない場合がある。
インフラの人曰く、ダブルバウンスが発生するのだそうだ。
Bounce address - Wikipedia
Double bounce

この設定をすると、各ジョブでビルド失敗時などにメールを飛ばすことができる。

Jenkinsに何かとPOSTする簡易サーバを作るメモ


前に書いたBacklogからJenkinsを実行する記事の補足。
Backlogの情報をJenkinsに投げる部分についてメモ。
dwmemo.hatenablog.com

前回までのあらすじ

JenkinsのCSRFチェックを外さずにジョブを走らせたい、しかしBacklogからのPOST値取得にはcrumbも一緒にPOSTしなければならなかった…。
そこで、簡易サーバを噛ませてJenkinsを走らせるという遠回りな方法を選んだのであった。
別に簡易サーバ作ってみたかったとかそういうわけではない。

流れ

  • Backlogでリポジトリへのプッシュを検知、簡易サーバに情報をPOST
  • 簡易サーバでJenkinsのcrumbを取得、Backlogの情報と併せてJenkinsにPOST
  • Jenkinsで簡易サーバの投げたデータを取得、解析して各種ジョブを実行

Node.js簡易サーバを作る

Node.jsで作ると簡単ダヨーと聞いたので、Node.jsを使う。
ドキュメント読みつつ各種サイト見つつ見よう見まねなのでエラー処理とか真面目にやってない。

とりあえず簡易サーバ作る

画面に「Hello Server!」を表示するだけのものを作成。

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello Server!\n');
}).listen(【サーバのポート】, '0.0.0.0'); // localアクセスだけなら127.0.0.1でも可

まずはサーバのローカルアクセスできるか確認。
http://127.0.0.1:【サーバのポート】

次に、外部から該当ポートを使用できるようFireWallに設定追加。
外部からアクセスできるか確認。
http://【ホスト】:【サーバのポート】

これを元に、POSTデータの取得、crumbの取得、取得データをまとめてJenkinsに投げる…を行う。

POSTデータの取得

var http = require('http');
var querystring = require('querystring');

http.createServer(function (req, res) {
    var body = '';
    var jsonString = '';

    if (req.method != 'POST') {
        res.write('Fail!\n');
        res.end();
        req.connection.destroy();
        return;
    }

    req.on('data', function (data) {
        body += data;
    });

    req.on('end', function () {
        var post = querystring.parse(body);
        jsonString = post.payload;    // BacklogのJSONはpayloadに入っている

        try {
           jsonString = JSON.parse(post.payload);
           res.writeHead(200, {'Content-Type': 'text/plain'});
           res.write('Hello Server!\n');
        } catch (err) {
           res.writeHead(500, {'Content-Type': 'text/plain'});
           res.write('Error.\n');
           res.end();
           req.connection.destroy();
           return;
        }

        res.end();
    });

}).listen(【サーバのポート】, '0.0.0.0');

先程試した「http://【ホスト】:【サーバのポート】」に「payload["data":"{}"]」をPOSTして、200が帰ってくるか確認。

crumbの取得

crumbは以下のURLから取得する必要がある。

【JenkinsURL】/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)

安直にhttp.getで取得してみると、この処理を待たずに先の処理を実行する事態が発生。

javascript - NodeJS wait for HTTP request - Stack Overflow

Promiseを使うと非同期処理できるらしい。
「module request-promise」はnpmでインストール。

npm install --save request
npm install --save request-promise

crumb取得URLを叩いて、取得出来たらthen句を実行するという流れ。 promiseの外に書くと、上と同じで処理を待たずに実行してしまう。

        promise('【JenkinsURL】/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)')
            .then(token => {
                var crumb = parse_crumb(token);    // crumb部分を取得する適当な関数

                const options = {
                    method: 'POST',
                    hostname: 【Jenkinsホスト】,
                    port: 【Jenkinsポート】,
                    path: '/job/【ジョブ名】/buildWithParameters?payload=' + encodeURIComponent(post.payload),
                    headers: {
                        'Content-Type': 'multipart/form-data',
                        'Jenkins-Crumb': crumb    // k-vでなくtokenそのまま書けば行けるかもしれない
                    }
                };

「http://【ホスト】:【サーバのポート】」に「payload["data":"{}"]」をPOSTして、Jenkinsが実行されpayloadに値が入っているか確認。
プッシュしたブランチの情報などはここで整形して投げるのもありだが、簡易サーバが止まったりする場合を考えてPOST受け取り・投げる以上の事はしない方が良さそう。

余談:簡易サーバを起動させ続ける

Node.jsのサーバを起動させ続けるにはどうするか、forever使うとできるらしい。
GitHub - foreverjs/forever: A simple CLI tool for ensuring that a given script runs continuously (i.e. forever)
公式の通り導入したら起動させたいファイルを指定すれば終わり。

forever server.js


わざわざ調べたが、リモートアクセス使うので普通にコマンドから起動して放置で良いのではと考えて使用は止めた。

Promise参考

作業中に見たり、後から調べたりして参考になったURL。

FuelPHPのプロジェクトにPHPUnitをインストール

FuelPHP(v1.8)を使用しているプロジェクトにPHPUnitを導入するメモ。
そんな昔のフレームワーク使ってんのとか言ってはいけない

PHPUnitのバージョン

テスト実行にはFuelPHPのoilを使用するのだが、PHPUnitのバージョンが新しいとNull 合体演算子「??」でoilがエラー出すのでPHPUnit6使用。

PHPUnit6からクラス名が変更になっている。
PHPUnit_Framework_TestCase」→「PHPUnit\Framework\TestCase」
Release Announcement for Version 6 of PHPUnit – The PHP Testing Framework

なので、以下のファイル中の該当クラス名を変更する必要あり。

  • fuel/core/classes/testcase.php
  • fuel/packages/oil/classes/command.php

テスト実行

既にローカル環境にPHPUnitがある場合はそれらが優先されてしまうようだ。
今回はxamppのバンドルがあったので、テスト実行すると「No tests executed!」になってしまった。
xamppのPHPUnitディレクトリを移動させるとテスト成功。

グローバルインストールしていない場合は、以下のエラーが出てしまう。
「'phpunit' は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。」

この場合、oilに対してPHPUnit がインストールされている場所を知らせる必要がある。
app/config/oil.php作成し、コメントの設定参照しながらパスを通す。

余談:オプションなしでテスト実行

php oil test」を実行すると、以下エラー。
issueにも上がっていたが、PHPのバージョンの問題らしい?
--groupや--testsuiteなどオプションありだとエラーは出ない。普段はオプション付きで実行するので、実用での問題はなさそう。

Compile Error - Cannot use PHPUnit\Framework\Exception as Exception because the name is already in use in fuel/vendor/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php on line 12」



ASP.Netのメモ

ASP.Netを使うことがあったので、その時に詰まった部分のメモ。ほんと只のメモ。

.aspx側で対応する.csのプロパティ表示

.csのクラスのプロパティをpublicで宣言(privateだとダメ)。

  • エスケープする<%: Id %>
  • エスケープしない:<%= Id %>

URLパラメータ取得

Request.Querystring["param"]を使う。
「?param=」がないとnullになる。
How to get parameter in url ( by C# for .net) | The ASP.NET Forums

URLに危険な文字列を入れた場合

例えば「http://【パス】?【クエリ】=<script>alert()<」を入力してアクセスすると、以下のエラー発生。
HttpRequestValidationException (0x80004005): 危険な可能性のある Request.QueryString 値がクライアント (ak="<script>alert()<") から検出されました。

このエラーはRequest.Querystringを取得する部分でtry-catchしても引っかからない。
Global.aspxの方でcatchする必要があるらしい。
c# - How to catch HttpRequestValidationException in production - Stack Overflow

Console.Write()が出力ウィンドウに出ない

今回はWebアプリだったので、出力できないらしい。
c# - Console.WriteLine does not show up in Output window - Stack Overflow
Possible to output to console from within a class library C#? - Stack Overflow

VS起動時エラー

Visual Studio は例外を検出しました。拡張機能が原因である可能性があります。」

C:\Users\【ユーザ】\AppData\Roaming\Microsoft\VisualStudio\14.0\ActivityLog.xml
    <type>Error</type>
    <source>Editor or Editor Extension</source>
    <description>System.IO.FileNotFoundException: &#x30D5;&#x30A1;&#x30A4;&#x30EB;&#x307E;&#x305F;&#x306F;&#x30A2;&#x30BB;&#x30F3;&#x30D6;&#x30EA; &apos;Microsoft.VisualStu→dio.Data.Tools.Delta.UI,


Tmp削除すると直るという情報があったが、Tmp削除後もエラー出る。
Visual Studio が起動時にエラーがでるようになった&直した - Qiita

結局、インストーラで修復実行したらエラー消えた。
c# - Could not load file or assembly 'Microsoft.VisualStudio.Data.Tools.SqlEditor.dll' - Stack Overflow

GridViewのページングリンク

自動でページングリンクを作成するが、リンク押下時のイベントを設定しておかないとエラーが出る。
勝手に作ってくれないの…。

「GridView 'GridView1' はハンドルされていないイベント PageIndexChanging を送出しました。」
OnPageIndexChangingの設定を.aspxと.csに追加。