あじちゃんのブログ。備忘録。

〜SEもOLなんですかね?

PHPUnitでテストを行う

Testメソッド作成

  • makeでTestUnitを作成
$ php artisan make:test [TestUnitName]

🙂💬 作成したTestUnitは[tests/Feature/]配下に配置されるので、機能が分かれている時は[TestUnitName]にディレクトリも追加する.

Testメソッドを実装

  • 以下が初期状態
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class SampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testExample()
    {
        $this->assertTrue(true);
    }
}
  • 一番重要なのが assertHoge() のようなアサートメソッド. アサーションを行う.
    このメソッドがそのテストの成功失敗を判定する. 基本的に1メソッドに対して1アサートメソッドだが、複数指定も可能.
    (私は今回1:1でやったので複数指定の場合は他を参照してね.)

アサートメソッド一覧

アサートメソッドに関しては私がまとめるよりも公式が充実しているのでそちらを参照のこと.

DBとのやりとりが発生する場合の約束ごと

🙁💭 ここが一番大変だった、というか知らなかったのではまった.

  • 必ず setUp() すること
<?php
......
public function setUp()
{
    parent::setUp(); //おまじない. よくわからんがこれを書くことで環境をリセットしてる.
}

DBとの接続をするときに必ず必要.
各テストメソッドの実行前に呼ばれるメソッドで、テストメソッドごとに同じ環境を用意しテストメソッド内で動的に変化させて追従したい場合に使う.
DBとの接続で必要なのは、メソッドごとにDBの環境をリセットしたいから.
例)テストメソッド1で全データを削除→テストメソッド2で特定のデータを取得できることを確認すると、データが消えているのでNGとなる...ナド

  • tearDown() というのもあって、これは各テストメソッドの実行後に呼ばれる. あと処理が必要な場合はこれも実装が必要.

ざっくり書いてある記事があったので参考に:PHPUNITのテストメソッド実行前後の処理まとめ

DBとの接続(モデルにお任せバージョン)

🙁💭 どのサイトを見てもテスト内部でDBを指定して〜というのばかりで迷ったので書いておく.

<?php
......
/**
 * @test
 */
public function 情報を取得できる()
{
    //Nekoモデルインスタンスを作成し、factory1()で作成したデータをDBに登録している.
    $neko = factory(Neko::class)
                ->create($this->factory1()); //factoryについては後述
                
    $nekolist = $neko->selectAll(); //(modelのselectAll()で値を取得している.)
    
    TestCase::assertNotNull($nekolist); //帰ってきた値がNullでなければ"OK"
}

private function factory1()
{
    $data = ([
        'name'         => 'Tama',
        'kind'         => 'Abyssinian',
        'birthday' => '2022-02-22 22:22:22',
    ]);
        
    return $data;
}
  • @test アノテーションをつけることでメソッド名に"test"を入れなくてもテストメソッドだと判断をしてくれる
    アノテーションもたくさんの種類があるので公式サイトを見てみると良い. わたしは @description をそこそこ使った.

factoryとは?

モデルファクトリー:Laravel 5.1 テスト

テスト実行前に何件かのレコードをデータベースに挿入する必要はよく起きます。こうしたテストデータを手動でそれぞれのカラムへ値を指定する代わりに、Laravelでは「ファクトリー」を使用しEloquentモデルの各属性にデフォルトを設定できます。手始めにアプリケーションのdatabase/factories/ModelFactory.phpファイルを見てください。このファイルには最初からファクトリーの定義が含まれています。

🙂💬 複数のテストにまたがって、共通のいくつかのレコードをデータベースに登録したいときにファクトリーを呼び出すことで簡単にデータを設定することができる. (手動で特定の値を列ごとに設定しなくてよい)

以下ファクトリークラスの例

<?php
......
$factory->define(App\Neko::class, function (Faker\Generator $faker) {
    return [
        'name'         => $faker->name,
        'kind'         => $faker->kind,
        'birthday' => date('Y-m-d H:i:s'),
    ];
});
  • define()のはじめで対応するモデルを指定する.
  • $faker は呼び出し時にcreate()に渡される引数.
  • 渡した引数が定義に足りない場合は、定義の値がDBに登録される.

PHPUnitの実行

⚠️プロジェクトのルートにて実施すること

// テストの実行
$ vendor/bin/phpunit

実行結果

成功の場合

$ vendor/bin/phpunit
PHPUnit 6.4.0 by Sebastian Bergmann and contributors.

...............................................................  63 / 108 ( 58%)
.............................................                   108 / 108 (100%)

Time: 3.42 seconds, Memory: 24.00MB

OK (108 tests, 262 assertions)

エラーの場合以下のように出てきます.
. は成功、 F は失敗などいろいろある:PHPUnit マニュアル

$ vendor/bin/phpunit
PHPUnit 6.4.0 by Sebastian Bergmann and contributors.

....F..........................................................  63 / 108 ( 58%)
.............................................                   108 / 108 (100%)

Time: 2.87 seconds, Memory: 24.00MB

There was 1 failure:

1) Tests\Feature\NekoTest::ユーザ一情報覧を取得できる
Failed asserting that [エラー発生個所のフルパス] [エラーの内容]
...略...
~/tests/Feature/NekoTest.php:33

FAILURES!
Tests: 108, Assertions: 262, Failures: 1.

気づき

  • テストの対象にしたい機能をmodelで実装していないとテストにできないので、controllerへ依存しないようなコーディングが命.
  • このテストだけで全部を網羅しようと分割をやり過ぎても良くないので、必要な場面を判断しつつうまく活用するのが良い.
  • 求める結果がどのようなものかを理解していないと書けない.

わかってないところ

この部分は何???カバレッジ??? f:id:azix:20180525180747p:plain

信頼サイト

transaction処理中の動きで変数が更新されず嵌った.(解決済み)

これ、try-catch中の変数の変化を取得できてないのかな?と思い検証.

{{-- blade --}}

@if(session('message'))
    <ul class="list-group">
        <li class="list-group-item list-group-item-success">{{ session('message') }}</li>
    </ul>
@endif
//controller

//成功チェック
$success = false;

try
{
    DB::transaction(function () use ($success) {
        //登録処理
        DB::insert($data);
        $success = true;
    });
}
catch(Exception $e)
{
    $success = false;
}

//成功メッセージの表示
if($success)
{
    return redirect('cbt/user')
    ->with('message', '成功しました'); //ここが出力されない
}
else//TODO
{
    return redirect('cbt/user')
    ->with('message', '予期せぬ状態'); //ここが出力される
}


間違っていた箇所はフラグの変更位置.

 DB::transaction(function () {
        //登録処理
        DB::insert($data);
    });
    
    $success = true; //transaction外でフラグを変更.


どうやら、transaction内部ではDBでの処理が完了し次第、スコープの外に出てしまうようです.
以下のような処理にしても、returnしてくれなかった.

 DB::transaction(function () {
        //登録処理
        DB::insert($data);
        
        return redirect('cbt/user')
        ->with('message', 'これはtransaction内からのredirectです');
    });


まとめ

transactionメソッド中では、DB接続が終了し次第 exit 状態となるため、DB処理の後ろで何かやっても無意味!!!

マイグレーションファイルの作成でエラー

  • 以下のファイルを実行
//@migrationファイル
public function up()
{
    if (!Schema::hasTable('Test')) {
        Schema::create('Test', function(Blueprint $table)
        {
            $table->engine = "InnoDB";
            $table->bigInteger('empId', 20)->unsigned()->comment('従業員ID');   //BIGINT(20) UNSIGNED PRIMARY KEY NOT NULL
            $table->bigInteger('compId', 20)->unsigned()->comment('会社ID');    //BIGINT(20) UNSIGNED
        });
    }
}
  • auto columnは設定していないのに以下のエラーが発生する.
SQLSTATE[42000]: Syntax error or access violation: 1075 Incorrect table definition; there can be only one auto column and it must be defined as a key 


  • こうするとうまくいく. 😕💭カラム長の指定がまずいの???
Schema::create('Test', function(Blueprint $table)
{
    $table->engine = "InnoDB";
    $table->bigInteger('empId', 20)->unsigned()->comment('従業員ID');   //BIGINT(20) UNSIGNED PRIMARY KEY NOT NULL
    $table->bigInteger('compId')->unsigned()->comment('会社ID');        //BIGINT(20) UNSIGNED
});
mysql> show columns from Test;
+-----------------------------+---------------------+------+-----+---------+----------------+
| Field                       | Type                | Null | Key | Default | Extra          |
+-----------------------------+---------------------+------+-----+---------+----------------+
| empId                       | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| compId                      | bigint(20) unsigned | NO   |     | NULL    |                |
+-----------------------------+---------------------+------+-----+---------+----------------+

🙁💭 目的は達成できたけど原因がわからずもにょる.

BIGINTは8バイト整数なので、必要記憶容量は8バイト.
桁数はマイナスを含め20桁なので、上記では自動で最大が振られたということ...?
他のファイル見ててもそんな感じだった. 目的のサイズが最大より小さくなるなら指定する感じかな?

⚠️ BIGINT(20)の20は表示幅のこと. 桁数ではない. TINYINT(1)で格納可能値の勘違いをしていた件


てか、指定していないのに[empId]が勝手にprimary keyになってるしauto_incrementになってる...
$table->primary('empId'); を指定していたところエラー( SQLSTATE[42000]: Syntax error or access violation: 1068 Multiple primary key defined )となったため外していた.
unsigndBigIntegerの場合だとprimaryもauto_incrementも自動付与はされず、自分で指定する必要があった.
指定の仕方で型は同じでも付属するオプションが違うようです...(難しい)

// [primary key][auto_increment] [tyep = "bigint(20) unsigned"]
$table-> unsigned bigInteger('Id');

// [tyep = "bigint(20) unsigned"]
$table->bigInteger('Id')->unsigned();

まとめ

😭 むずかしい!
(もはやまとめたくない. 今日は悪い子になる.) 後で読む→Laravel 5.3 Eloquent ORM 入門 2 (マイグレーション)

トランザクション処理で詰まった

結論

トランザクション処理では外部で定義したの変数は指定して呼び出す必要がある.

詳細

ざっくり以下のような状態で実行すると...

$kinds = ['cat','dog','mouse'];

DB::transaction(function (){
    //格納データ配列の作成
    $data = [];
    foreach($kinds as $kind){
        array_push($data,[
            'kind'=>$kind,
            'createdAt'=>date('Y-m-d H:i:s'),
        ]);
    }

    //登録処理
    DB::insert($data);
});

このようなエラーメッセージが表示される.

//ERROR MESSAGE
Undefined variable: kind

引数の呼び出しが必要だったようで、以下のようにして解決。

$kinds = ['cat','dog','mouse'];

DB::transaction(function () use ($kind) {
    //...同処理...
}

🙁💭 このtransactionがメソッドでスコープが限定されてるものだってことを知らなかったせいで嵌ってしまった。基礎を知るis大切。一晩きちんと寝たら気づいたのでよかった。

参考

連想配列が格納された配列

$input   =   ('id'       => '52738495'
             'name'     => 'ねこ'
             'birthday' => '2000/01/31');

↑みたいな連想配列を作りたい。

  • できた
$values = array(100,200,300,400,500);

$data = [];  //全体の配列

foreach($values as $value) {
    $item = array('id'=>$value, 'name'=>'ねこ','birthday'=>'2000/01/31');
    array_push($data,$item);
}
echo "<pre>";
print_r($data);
echo "</pre>";
//出力
Array
(
    [0] => Array
        (
            [id] => 100
            [name] => ねこ
            [birthday] => 2000/01/31
        )

    [1] => Array
        (
            [id] => 200
            [name] => ねこ
            [birthday] => 2000/01/31
        )

    [2] => Array
        (
            [id] => 300
            [name] => ねこ
            [birthday] => 2000/01/31
        )

    [3] => Array
        (
            [id] => 400
            [name] => ねこ
            [birthday] => 2000/01/31
        )

    [4] => Array
        (
            [id] => 500
            [name] => ねこ
            [birthday] => 2000/01/31
        )

)

【未解決】登録処理がうまくいかない

  • こんなデータがあります
$kinds = array('みけ','さば','とら','はちわれ','くろ');
  • これをその他の要素とまとめてやります
//動物種類(kind)ごとにデータをまとめる
$inputData = [];
foreach($kinds as $value) {
    $item = array(
        'kind'=>$value,
        'birthday'=>date('Y-m-d H:i:s'),
    );
    array_push($inputData,$item);
}

//登録処理
for($i = 0; $i < count($inputData); $i++){
    DB::insert([
        'kind'=>$inputData[$i]['kind'],
        'birthday'=>$inputData[$i]['birthday'],
    ]);
}
  • 実行します

  • デデドン!(エラー)

(/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php)
"ksort() expects parameter 1 to be array, string given"

f:id:azix:20180518180952p:plain

🙂💭  原因わからず。どうしようもなく。
配列の渡し方とかが悪いんだろうけど今回は諦めます。わかる人教えてください。

続きを読む

TypeScriptを使う(本当に使ってみるだけの場合。)

TypeScriptとはなんぞや

  1. 大規模開発言語に近い仕様でソースコードを書くことができる
  2. コンパイルするとJavaScriptになる

コンパイラをインストールする

$ npm install -g typescript

/Users/ホスト名/.nvm/versions/node/v8.11.1/bin/tsserver -> /Users/ホスト名/.nvm/versions/node/v8.11.1/lib/node_modules/typescript/bin/tsserver
/Users/ホスト名/.nvm/versions/node/v8.11.1/bin/tsc -> /Users/ホスト名/.nvm/versions/node/v8.11.1/lib/node_modules/typescript/bin/tsc
+ typescript@2.8.3
added 1 package in 2.891s

プロジェクトフォルダを作成

  • コマンド
$ cd htdocs/
$ mkdir typescript-test1
$ cd typescript-test1/
$ touch tscode.ts
$ touch show.html
$ touch tsconfig.json
  • 構成
📁 htdocs
└ 📁 typescript-test1
    ├ 🗒 tscode.ts
    ├ 🗒 show.html
    └ 🗒 tsconfig.json

とりあえず出力して表示までやってみる

わーっと書いてみる

[show.html]

<!DOCTYPE>
<html>
<head>
<meta charset="utf-8">
<style>
  body {
    width :500px;
    padding :10%;
  }
</style>
</head>
<body>
  <script type="text/javascript" src="tscode.js"></script>
</body>
</html>


[tscode.ts]

class Cat{

    name:string;

    sayHello(){
        return this.name + "だにゃん😺";
    }

    constructor(name:string){
        this.name = name;
    }
}

let aki = new Cat("あき");  //letはローカル変数を示す

document.body.innerHTML = aki.sayHello();

🙂💬 変数の宣言は汎用的な[var]よりローカル変数である[let]の使用が推奨されているそう.
🙂💭 classとかあるし本当にJavaとかC#とかに近い感じがする.


[tsconfig.json]

{"compileOprions": {
    "outFile": "jscode.js"
}}

🙂💬
・[tsconfig.json]が存在するディレクトリがTypeScriptプロジェクトのルート・フォルダであることを示すことになる.
・上記ではコンパイルした後に出力されるファイル名を指定している.

コンパイルする

環境:VSCode(version 1.23)

  • コマンドパレット(shift + ⌘ + P)で「tasks」と入力すると↓が出てくるので「configure task」を選択してください

f:id:azix:20180517182643p:plain

  • ビルドに[tsconfig.json]を選択し、実行(Enter)

f:id:azix:20180517182632p:plain

  • VSCodeのターミナルを表示しておくと以下のような結果が表示されます

f:id:azix:20180517182636p:plain

  • コンパイルが完了すると[tscode.js]が作成されます. 中身はこんな感じ↓
var Cat = /** @class */ (function () {
    function Cat(name) {
        this.name = name;
    }
    Cat.prototype.sayHello = function () {
        return this.name + "だにゃん😺";
    };
    return Cat;
}());
var aki = new Cat("あき"); //letはローカル変数を示す
document.body.innerHTML = aki.sayHello();

😕💭 若干めんどくさい感じのJSだーって印象...

f:id:azix:20180517182647p:plain

備考

React&WebPackで実行環境を作るならReact & Webpackチュートリアルになっているのでさんこうにすると良いです.