もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

2つ以上前のコミットに修正を混ぜる

はじめに

こんばんは。

久々に gitで rebase を使ったので、ちょっとした備忘録として残しておきます。

gitのバージョンは以下

$ git --version
git version 2.32.0

本番

今まで自分は rebase 使う際は 無駄なコミットをまとめる際に使ったりしていました。

$ git rebase -i HEAD~3

hint: Waiting for your editor to close the file... 
pick 8edxxxxxx aaaaa
s 2a8xxxxxx bbbbb
s 60cxxxxxx ccccc
s 532xxxxxx dddd

$ git push origin head -f 

みたいな感じで。

今回はコミット漏れした修正ファイルを以前のコミットに混ぜたいということやりたい感じでした。

直前のコミットであれば --amend で出来ます。

$ git commit -a --amend

ただ、今回は2つ前のコミットに混ぜたかったんです。。。

色々調べてこんな感じでできました。

# 修正物をまず退避
$ git stash save

# reabseインタラクションモード実行
$ git rebase -i HEAD~3

hint: Waiting for your editor to close the file... 
pick 8edxxxxxx aaaaa
edit 2a8xxxxxx bbbbb   ← 二個目のコミットを edit にする
pick 60cxxxxxx ccccc
pick 532xxxxxx dddd

# 修正物をもってくる
$ git stash pop 

# 修正物をコミット(amendが必要)
$ git commit -a --amend

# コミットを反映
$ git rebase --continue

上記のコマンドの順番で狙ったコミットに修正物を混ぜることが出来ました。

終わり

rebase 便利ですね。

ぶっちゃけ変になったら git rebase --abort で全部巻き戻せばいいので。

そろそろ今年の目標の資格の勉強しようっと。

現場からは以上です。

NuxtとGoではじめるWebアプリ開発を読んで勉強 vol.1

はじめに

こんばんは。

今回はこの本を読み始めました。

なんとなんと2019年12月の本です。

出てすぐ買ったので、まる2年寝かした本です。 発酵しています。

GoもNuxtも興味あるので、早速やっていきます。

今回は1〜4章まで

4章はGo部分のみやっています。 環境構築にかなりつまずいたので...

本題

各バージョン

とりあえず、本で紹介されている各ツールのバージョンは軒並み古いですので、全部最新版でやっていきます。

go

$ go version
go version go1.18 darwin/amd64

echo

$ go get github.com/labstack/echo/v4
go: added github.com/labstack/echo/v4 v4.7.2

nuxt

"nuxt": "^2.15.8"

dep

本で紹介されている dep は使っておらず gomod を使っています。

(入れるの大変だったから代替品ないかなと調べてたらすでに古いということだったので...)

www.threenext.com

realize

こちらも本で紹介されている realize は使わず air を使いました。

(こちらも同じく入れるの大変だったので調べたら、以下同文)

qiita.com

実行してみる

$ go run server.go 

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.7.2
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080

f:id:kojirooooocks:20220324002516p:plain

おけです。

echoフレームワークのメジャーバージョンやGoのバージョンもだいぶ上がっていたので、全く動かないかなと心配していましたが、Goのコードに関してはほぼ問題なく動きました。

1点、 google.golang.org/api/youtube/v3 で取得するデータを指定するところで本では

call := yts.Videos.List("id,snippet").Chart("mostPopular").MaxResults(3)

と記載されていたのですが、これではエラーになりました。 解決策としては配列で指定すれば良いみたいでした。

call := yts.Videos.List([]string{"id", "snippet"}).Chart("mostPopular").MaxResults(3)

仮にidのみ指定すると以下みたいな感じで取れます。

[
  {
    "id": "jjXZJZ4F8Ak",
    "kind": "youtube#video"
  },
  {
    "id": "r8Eypb1xYPM",
    "kind": "youtube#video"
  },
  {
    "id": "p0vxVY2HMEs",
    "kind": "youtube#video"
  }
]

終わりに

go久々にやってみましたが、全くわかりませんね。 それ以上に nuxtもですが。。。

次はnuxtの部分からやっていきます。

LaravelのFormRequestのテストを書く

こんばんは

今回もLaravelネタです。

FormRequestはLaravel使っているうえで結構使うのですが、それのテストって書いてなかったなぁと思ったので、書いてみようと思いました。

本題

まずこんな感じのFormRequest作ります。

UserRegisterRequest.php

<?php

namespace App\Requests;

use App\Requests\Rules\UserStatusExistsRule;
use Illuminate\Foundation\Http\FormRequest;

class UserRegisterRequest extends FormRequest
{
    /**
     * @return bool
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array
     */
    public function rules(): array
    {
        return [
            'name'    => 'required|string',
            'status'   => ['required', 'numeric', new UserStatusExistsRule()],
        ];
    }
}

name, status を リクエストでもらってそれを検証するようなかんたんな形です。

ちなみに、 UserStatusExistsRule は以下のような形です。

UserStatusExistsRule.php

<?php

namespace App\Requests\Rules;

use App\Domain\ValueObject\UserStatus;
use Illuminate\Contracts\Validation\Rule;

class UserStatusExistsRule implements Rule
{
    /**
     * @param $attribute
     * @param $value
     * @return bool
     */
    public function passes($attribute, $value): bool
    {
        try {
            new UserStatus((int)$value)); // 1 or 2でないとExceptionが発生する
            $result = true;
        } catch (\Throwable $e) {
            $result = false;
        }
        return $result;
    }
}

これに対してのテストコードは以下

UserRegisterRequestTest.php

<?php

namespace Tests\Requests;

use App\Requests\UserRegisterRequest;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Validator;
use Symfony\Component\HttpFoundation\Request;
use Tests\TestCase;

class UserRegisterRequestTest extends TestCase
{
    /**
     * @test
     * @dataProvider validationDataProvider
     * @param array $parameters
     * @param bool $expected
     * @param string $message
     * @return void
     */
    public function validation(array $parameters, bool $expected, string $message): void
    {
        $request = new UserRegisterRequest();
        $validator = Validator::make($parameters, $request->rules(), $request->message());
        self::assertSame($expected, $validator->passes(), $message);
    }

    /**
     * @return array
     */
    public function validationDataProvider(): array
    {
        return [
            [
                [],
                false,
                'リクエストが送られていないのでfalse'
            ],
            [
                ['name' => 'aa'],
                false,
                'statusが送られていないのでfalse'
            ],
            [
                ['status' => '1'],
                false,
                'nameが送られていないのでfalse'
            ],
            [
                ['name' => 'ああああ', 'status' => 'ああ'],
                false,
                'statusが数値型ではないのでfalse'
            ],
            [
                ['name' => 'ああああ', 'status' => '1000'],
                false,
                'statusが許された値ではないのでfalse'
            ],
            [
                ['name' => 'ああああ', 'status' => '1'],
                true,
                '成功パターン'
            ],
        ];
    }
}

作った際のメリットとしては、以下の2点かなと思いました。

  • リクエストの全体を網羅的にかんたんにテストできる。
  • 上記ができるのでControllerのFeatureテストを薄く出来る。

デメリットは書くのめんどくせーと思うくらいですかね。。。

もっとやりやすいテスト方法あれば教えて下さい!

終わりに

最近やっと本読めるような時間ができてきました。

そういえば、Web+DB Press vol.127 のリファクタリングの話すごくためになりました。

特に凝集度の話勉強になりました。

読んでない方はぜひ

laravelのFormRequestで独自ルールの作成

こんばんは。

最近ずっとlaravelしてます。

だいぶやり方忘れています。

今回はバリデーションの独自ルールの作成のやり方を備忘録で残しておきます。

バージョンは以下

# php -v
PHP 7.4.26 (cli)

 # php artisan -V
Laravel Framework 8.83.0

本題

コマンド的には以下で作れます。

# php artisan make:rule TestRule
Rule created successfully.

作成されたRuleクラスは以下です。

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class TestRule implements Rule
{
    /**
     * Create a new rule instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        //
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return 'The validation error message.';
    }
}

ようするに Illuminate\Contracts\Validation\Rule Interfaceをimplementsしたクラスを作ればいいって感じです。

使う側はこんな感じ

    public function rules(): array
    {
        return [
            'item_id'  => ['required', 'integer', new TestRule()],
        ];
    }

独自ルールになんらかをコンストラクタインジェクションしたいときとかは以下

    public function rules(): array
    {
        return [
            'item_id'  => ['required', 'integer', $this->container->get(TestRule::class)],
        ];
    }

ある程度の形だったら FormRequestには prepareForValidation() とかあるからバリデーション前に 展開して 確認とかもできちゃうから なかなか新規では作らなそうなイメージですが、ある特殊なフォーマットで正規表現して確認したいとか、API通信して存在確認したいとか、そんな感じが多いかなと思います。

いろんな機能があっておもしろいっすね。

終わりに

地獄の2月が終わったので、今月は少し楽になりますww

来週からはちゃんと本読んで勉強しようと思います。

Mockeryのwith()を使う際に引数にオブジェクトをそのまま渡せない

はじめに

こんばんは。

phpunitのモックライブラリの prophecy をずっと使っていたのですが、最近 laravelの案件をやりはじめてMockery使ってテスト書くにあたりちょっと勝手が違った部分があったので、備忘録で残しておきます。

内容はタイトルのとおりです。

php version, laravel versionは以下

 # php -v
PHP 7.4.26

# php artisan -V
Laravel Framework 8.83.0

本題

今回の場合は引数にlaravelの collection を渡すようなメソッドのモックを作りたい感じでした。

prophecy の場合は以下みたいな感じでだと思います。

$mockA = $this->prophesize(aaa::class);
$mockA->toArray()->willReturn([xxxx]);

$mockB = $this->prophesize(bbb::class);
$mockB->register($pk, $mockA->reveal())->shouldBeCalledOnce();

Mockery の場合は以下みたいな感じでした。

$collection = collect([xxxx]);
$mockA = \Mockery::mock(aaa::class)->makePartial();
$mockA->shouldReceive('register')
        ->with(\Mockery::on(function ($actual) use ($collection) {
            $this->assertEquals($actual, $collection);
            return true;
        }))->once();

症状的には こちら の記事と同じだと思います。

prophecy をずっとやってるからなのかもしれませんが、 ちょっと使いづらいと感じてしまいました...

また、こちら の記事では \Hamcrest\Matchers を使う方法も紹介されています。

$collection = collect([xxxx]);
$mockA = \Mockery::mock(aaa::class)->makePartial();
$mockA->shouldReceive('register')
        ->with((\Hamcrest\Matchers::equalTo($collection)))->once();

後者のほうがスッキリしていいですね!

終わりに

ちょっと前に短期案件で久々にテストコードがないプロジェクトにアサインしたのですが、テストコード書きなれると、テストコードがないプロダクトコード触るのが怖すぎて、開発スピードが明らかに落ちてました。

t_wadaさんのスライドにあった

テストを書く時間がないのではなく、テストを書かないから時間がなくなるのです。

っていうのがすごく身にしみました。

speakerdeck.com

現場からは以上です。

circlec ciを回す際に composer install でエラー出た

はじめに

最近、昔から運用されているphpプロジェクトのお手伝いをさせてもらうことになり、いろいろな環境の再構築なども行っております。

その過程でcircleciを使用するということになり早速導入したのですが、妙なエラーが出ました。

めっちゃ簡単ですが今回はそちらの対応の備忘録です。

本題

原因はフレームワークが古く、 composer2 を使うことで起きたエラーだと理解しました。

blog.packagist.com

Invalid PSR-0 / PSR-4 configurations will not autoload anymore in optimized-autoloader mode, as per the warnings introduced in Composer 1.10. Mostly these warnings were for classes that were not meant to autoload anyway so I don't expect major issues, but it's safer to clean these up before upgrading.

警告は出ていたのですがローカルで問題なかったので、無視しておりました...

とりあえずバージョン下げてみたらうまくいくのかな?と思って

      - run:
          name: Composer Downgrade
          command: |
            sudo composer self-update --1

を使うと問題なく対応できましたw

終わりに

過去と自分にメッセージ

警告無視すんな

laravel8のFactoryを使用したテストデータ作成

はじめに

こんばんは。

夜中子供が寝ているときに粛々と仕事と勉強をしております。

表題通りlaravel8でテストデータを作ったのですが、昔やってたlaravelのバージョンとは作り方違ってたので、備忘録として残しておきます。

本番

1. コンフィグの修正

config/app.php を修正して fakerで日本語を使用できるようにします。

'faker_locale' => 'ja_JP',

1. Factory作成

makeコマンド実行

$ php artisan make:factory UserProfileFactory
Factory created successfully.

できたFactoryクラスを以下みたいに修正

<?php

namespace Database\Factories;

use App\Model\UserProfile;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserProfileFactory extends Factory
{
    protected $model = UserProfile::class; // 対象となるModelクラスを指定

    public function definition()
    {
        // return値の値を作成(fakerを使ってダミーデータを作成)
        return [
            'name'        => $this->faker->name,
            'postal_code' => $this->faker->postcode(),
            'prefecture'  => $this->faker->prefecture(),
            'address'     => $this->faker->city() . $this->faker->streetName() . $this->faker-> streetAddress() 
        ];
    }
}

2. Modelに必要なtraitをuseする

makeコマンド実行

php artisan make:model UserProfile
Model created successfully.

作成されたmodelには HasFactory traitがuseされていると思いますが、makeコマンド介さずにmodelクラスを作った場合は以下のように HasFactory traitをuseします。

<?php

namespace App\Model;

use Database\Factories\UserProfileFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class UserProfile extends Model
{
    use HasFactory; // ← 対象のtrait

    protected static function newFactory()
    {
        return UserProfileFactory::new(); // ← 対象のFactoryを指定する
    }
}

3. SeederでFactoryを指定してテストデータを作成する

makeコマンド実行

$ php artisan make:seeder UserProfileSeeder
Seeder created successfully.

できたSeederファイルでFactoryを使用してデータを作成

<?php

namespace Database\Seeders;

use App\Model\UserProfile;
use Illuminate\Database\Seeder;

class UserProfileSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        UserProfile::factory(100)->create(); // これで100件のテストデータが作成できる

        // テストデータの一部 or 全てを上書きしたい場合
        UserProfile::factory()->create([
            'name' => 'あいうえお' // nameだけテストデータではなく 「あいうえお」 にする
        ]);
    }
}

番外編 factoryで使用するfakerに新たなメソッドを生やす

作成したfactoryクラスの親クラスである Illuminate\Database\Eloquent\Factories\Factory に存在している withFaker() をオーバライドして以下のように新たにproviderを作ります。

    protected function withFaker()
    {
        $faker = parent::withFaker();
        $faker->addProvider(new class($faker) extends \Faker\Provider\Base {
            public function ngWord(): string
            {
                return $this->generator->randomElement([
                    'バカ',
                    'アホ',
                    'マヌケ'
                ]);
            }
        });

        return $faker;
    }

これで $this->faker->ngWord() が使えるようになります。

baseクラスを新たに作って生やしてもいいし、 HasFactory trait側のメソッドをオーバライドして対応とか、色々やり方はありそうです。

終わりに

久々にlaravel使っているんですが、テストデータとか作りやすいですね。

現場からは以上です。