2020年1月28日火曜日

Linux で PowerShell に超入門

概要

PowerShell は Windows 環境上で実行可能なスクリプト言語です
コマンドベースの操作でいろいろな情報の取得や変更ができます
PowerShell は .NET をベースにして動作します
どうやら .NET SDK という Linux 環境で .NET を動作、開発できるツールが Microsoft から提供されているようです
今回は .NET SDK のインストール方法と PowerShell のインストール方法を紹介し簡単な動作確認とサンプルスクリプトを紹介します

環境

  • CentOS 7.7.1908
  • .NET SDK 3.1
  • PowerShell 6.2.3

.NET SDK のインストール

Linux で .NET を動作させるための SDK やランタイムをインストールします

  • rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
  • yum -y install dotnet-sdk-3.1
  • dotnet --info

dotnet コマンドが使えるようになっていれば OK です

PowerShell のインストール

PowerShell は .NET 上動作します
dotnet tool を使うとインストールできます

  • dotnet tool install --global PowerShell

インストールすると pwsh というコマンドが使えるようになります
インストールされるパスは以下でした

  • find / -name "pwsh"

=> /root/.dotnet/tools/pwsh

  • dotnet tool list --global

でインストールしたツールの一覧が確認できます

PowerShell プロンプトの起動

  • /root/.dotnet/tools/pwsh

と実行すると PowerShell のプロンプトが起動します
ここから PowerShell のコマンドを入力することができます

例えば取得系のコマンドの一覧を表示するには以下のようなコマンドを入力します

  • Get-Command -Verb Get

一覧にあるタイムゾーンを取得するコマンドを投げてみるとちゃんと取得できることが確認できると思います

  • Get-TimeZone
Id : Asia/Tokyo DisplayName : (UTC+09:00) Japan Standard Time StandardName : Japan Standard Time DaylightName : Japan Daylight Time BaseUtcOffset : 09:00:00 SupportsDaylightSavingTime : True

PowerShell ファイルを作成して実行する

プロンプトに入ってコマンドを実行することもできますが一連の動作はファイルにまとめてスクリプトとして実行したいです
そんな場合は ps1 というスクリプトを作成すれば OK です

  • vim test.ps1
$a = 123
$b = "hoge"
Write-Output $a $b
  • /root/.dotnet/tools/pwsh ./test.ps1

で「123」と「hoge」が表示されるのが確認できると思います

PowerShell のサードパーティのモジュールをインストールしてみる

PowerShell には PowerShell Gallery というパッケージ管理の仕組みがあります
ここからサードパーティのモジュールを簡単にインストールすることができます
PowerShell プロンプトを起動してモジュールをインストールしてみましょう

  • Find-Module -Name VMware*
  • Install-Module -Name VMware.PowerCLI

これでモジュールをインストールできます
あとは普通にインストールしたモジュールのコマンドを使うことができます
またインストールしたモジュールの一覧を確認するコマンドは以下の通りです

  • Get-InstalledModule

モジュール関連のコマンドの一覧を確認するには以下のように実行します

  • Get-Command *-Module
CommandType Name Version Source ———– —- ——- —— Function Find-Module 2.1.3 PowerShellGet Function Install-Module 2.1.3 PowerShellGet Function Publish-Module 2.1.3 PowerShellGet Function Save-Module 2.1.3 PowerShellGet Function Uninstall-Module 2.1.3 PowerShellGet Function Update-Module 2.1.3 PowerShellGet Cmdlet Get-Module 6.2.3.0 Microsoft.PowerShell.Core Cmdlet Import-Module 6.2.3.0 Microsoft.PowerShell.Core Cmdlet New-Module 6.2.3.0 Microsoft.PowerShell.Core Cmdlet Remove-Module 6.2.3.0 Microsoft.PowerShell.Core

最後に

参考サイト

2020年1月24日金曜日

jq で先頭のキー情報だけ出力する方法

概要

タイトルの通りです
jq を使ってキーの一覧がほしい場合に使える方法です

環境

  • jq 1.5

keys を使う

  • curl https://kaka-request-dumper.herokuapp.com | jq '.|keys'
[
  "body",
  "content_length",
  "form_data",
  "headers",
  "media_type",
  "method",
  "params",
  "path_info",
  "query_string"
]

更に特定のキーに対するキーの一覧を取得する場合は以下の通り

  • curl https://kaka-request-dumper.herokuapp.com | jq '.headers|keys'
[
  "HTTP_ACCEPT",
  "HTTP_CONNECTION",
  "HTTP_CONNECT_TIME",
  "HTTP_HOST",
  "HTTP_TOTAL_ROUTE_TIME",
  "HTTP_USER_AGENT",
  "HTTP_VERSION",
  "HTTP_VIA",
  "HTTP_X_FORWARDED_FOR",
  "HTTP_X_FORWARDED_PORT",
  "HTTP_X_FORWARDED_PROTO",
  "HTTP_X_REQUEST_ID",
  "HTTP_X_REQUEST_START"
]

2020年1月22日水曜日

SpriteKit で SKPhysicsBody の texture の形がおかしくなってしまったときの対処方法

概要

原因不明なのですが突如として SKPhysicsBody(texture: texture, size: texture.size()) で作成した物理ボディがなぜか指定した画像の形通りに動作しない場合の対処方法です
かなり一時的な対応ですが何とか対応できたので紹介します

環境

  • macOS 10.15.2
  • Xcode 11.3.1

解決策

png 画像を一旦削除して再度プロジェクトに取り込む

Xcode のプロジェクト内で管理している画像ファイルを一旦すべて削除します
そして、Finder などから再度画像を取り込むみビルドすることで SKPhysicsBody が正常に動作することが確認できました
バグのような挙動ですが解決しない場合はやってみる価値はあると思います

気づいたきっかけ

同じ画像を使って別の新規プロジェクトで同じ SKPhysicsBody の与え方をしたところ正常に動作するのを確認できたので既存のプロジェクト側に何かしら問題があるだろうと気づきました

しかし全く同じコードなのに物理ボディの挙動が違いなぜだろうと悩んだ結果「もしかしたら画像自体がおかしいのかも」ということでリフレッシュしてみたら直った感じです

もしかするとキャッシュなども影響しているのかもしれません

2020年1月14日火曜日

Scratch + Ruby = smalruby3 を試してみた

概要

smalruby は Scratch の開発を Ruby でできるプロジェクトです
今回は開発環境の準備から簡単なサンプルを動作させるところまでやってみました

環境

  • macOS 10.15.2
  • Ruby 2.6.2p47
    • smalruby3 0.1.11
  • nodejs 10.1.0
    • npm 6.13.6

環境を準備する

Scratch ベースなので基本的には GUI 上で進めます
ブラウザ上で動作する Web 版からデスクトップアプリとして動作するデスクトップ版を使うことになります

Web 版を使う

http://smalruby.jp/smalruby3-gui/ にアクセスすれば何も準備する必要なく初められます

git clone して localhost で起動する

「Compiled successfully」と表示されれば起動完了です
http://localhost:8601 にアクセスすれば Web 版と同じ環境が localhost 上で起動します

デスクトップ版を使う

ビルドが成功すれば GUI アプリケーションが立ち上がるはずですが macOS Catalina だとまだ対応していないのか GUI が起動しませんでした

試してみる

以下のコードを GUI のルビータグに貼り付けて実行してみましょう

self.when(:flag_clicked) do
  move(200)
  turn_right(180)
end

あとは緑の旗をクリックすれば動作します

おまけ: CUI 上だけで開発する

smalruby3 という gem が公開されておりこれを使えば CUI だけでも開発できるようです
sdl と sge というグラフィクス関係のライブラリが必要なので Homebrew でインストールします
sge だけ formula にないので野良のスクリプトからインストールします
結構たくさんの野良スクリプトがありますが自分は以下のスクリプトでインストールできました

  • brew install sdl sdl_image sdl_mixer sdl_ttf
  • brew install https://gist.githubusercontent.com/ymmtmdk/5b15f2b06aef5549eb5a/raw/ebf4c9758b1f772f0f6073e7b2bdbb5e9665ee74/libsge.rb
  • bundle init
  • vim Gemfile
gem "smalruby3"
  • bundle install --path vendor
  • vim app.rb
# coding: utf-8
require "smalruby3"

Stage.new("Stage",
          costumes: [
            {
              asset_id: "cd21514d0531fdffb22204e0ec5ed84a",
              name: "背景1",
              bitmap_resolution: 1,
              data_format: "svg",
              rotation_center_x: 0,
              rotation_center_y: 0
            }
          ],
          variables: [
            {
              name: "val1"
            },
            {
              name: "speed",
              value: "10"
            }
          ]) do
end
  • bundle exec ruby app.rb

最後に

smalruby を試してみました
Web 版もあるので導入は簡単だと思います
というかデスクトップ版と CUI は不安定な感じなので Web 版を使いましょう
まだギャラリー的な仕組みなどはないようです

Ruby を初めて使う人向けではない気がしますが Scratch を Ruby で動かしたい人には便利かなと思います

参考サイト

2020年1月9日木曜日

テックブログを 1000 件以上書いてみた結果

概要

2016/09 にはじめて 2020/01 で 1024 件のブログを執筆しました
成果や収入、書いてみた感想などを備忘録として残しておきたいと思います

背景

テックブログを書き始めたきっかけは単純で自分が試した技術や得た知識、ノウハウを忘れないように残しておきたかったためです
物忘れが激しいという理由もありますが多くの技術がある世界ですべてを記憶するのは不可能だと思っているので、せめて記憶を思い出すことが簡単にできる仕組みだけでも作っておきたかった感じです

記事の内容

大雑把ですが書いている記事の内容を列挙してみました
上からより多い印象です

  • 主に Tips 系、インストールからサンプル動作まで
  • 技術のノウハウ、テクニックなど (こんな感じでも使えるよ的な記事)
  • 最新技術の紹介、Getting Started
  • イベント、書籍、Web サービスなどの感想、紹介
  • ゲーム攻略メモ、雑学ネタなど

正直最後のやつテックではないような気もします、、

タグ上位

書いた記事の上位 20 件のタグです
タグをしっかり設定したので自分がどういったことことに興味があるのか可視化できるようになっていました

  1. Ruby (131)
  2. docker (73)
  3. golang (52)
  4. Swift (51)
  5. VMware (42)
  6. Python (33)
  7. RaspberryPi (29)
  8. sinatra (29)
  9. Arduino (26)
  10. Google Cloud Platform (25)
  11. JavaScript (25)
  12. Mac (22)
  13. kubernetes (20)
  14. Minecraft (18)
  15. VIC (18)
  16. AWS (16)
  17. ESP-WROOM-02 (15)
  18. Firebase (14)
  19. Jenkins (13)
  20. NativeExtensions (13)

年ごとの投稿数

期間中 (2016/09 から 2020/01) の各年ごとの投稿数です
1 日 1 記事以上投稿しないようにしていたので最高でも「365 記事/1年」を超えることはないと思っていたのですがありました

  • 2020 (3)
  • 2019 (258)
  • 2018 (292)
  • 2017 (406)
  • 2016 (65)

アクセス分布

使っている Blogger が自動で Google Analytics と連携してくれているので人気の記事や時間、国ごとのアクセス分布が細かく見れます
(しかもそれが無料で使えるのだから驚きです、、)

ここでは時間ごとのアクセス分布、アクセス元の国、アクセス元の端末の分布を紹介します

日本語のブログなのでアクセスのほとんどは日本からです
そしておもしろかったのが時間帯のアクセスで平日の朝 9 時から夕方 18 時くらいまでに集中しています
要するにこれは仕事をしている方々が見てくれているということなんだと思います
ここにはないですがリファラーも検索の流入がほとんどであとは Qiita やはてぶなどテックな方々が使うサービスが多い印象です

収入

だいたい 1000円/月くらいは入るようです
そこまでアフィリエイトを意識したブログにはしていないのでまぁこんなものかなと思います

ブログは始めた当初は全く収入がなかったようです
もしかすると当初は広告を入れていなかったのかもしれません

テックブログの場合は新鮮さが大事になってくるのでやはり古い記事には価値がなくなってくるのかもしれません
そういった意味でもやはりテックブログの場合は続けることが重要になってくるのかなと思います

続けることが良い

毎日は無理ですが「1記事/1日」くらいのペースで書くことは意識していました
記事の粒度もまちまちですが自分の記事は Tips 系が多いので 1 つの記事にまとめずに機能や使い方で記事を分割して投稿していました
そうしたほうがもちろん記事数が増えるというのもありますが記事にいろいろな情報が含まれなくなり簡潔になるので見た時に要点がつかみやすくなるかなと思っています

また続ける意識があると記事を書くために新しい技術やイベントなどに敏感になります
技術の世界は新しいことへのアンテナをしっかり張ることが重要かなと思うのでそういった意味でも毎日続けることはよかったかなと思います

どんなことでも書く

基本的にはどんな些細なことで書くようにしました
例えば自分の好きな Sidekiq というプロジェクトのチュートリアル的な記事はネット上に山程あります
それでも自分は自分で試した記録を記事にしています

サンプルコードや細かい手順など多少違いますが内容的にはほぼ同じです
それでも記事として公開する理由としては以下かなと思います

  • 自分の記事として書くことで「自分がやったという証跡」を残せる
  • チュートリアルで足りなかった内容を補足することで更に良い情報にする

一番の理由や証跡残しかなと思います
自分はいろいろな技術に触れる上で「あれ、そういえばこれは触ったかなー?」と思うケースが多々あります
そんな場合にググって自分の記事が出てきたりググって出てこなくても Blogger の記事の一覧で検索して出てくればすぐに思い出すことができます

そしてそれを見返すことで記憶を鮮明に思い出すことができるので久しぶりの技術でもスムーズに入っていくことができると思っています

その他気をつけていること

  • 参照した記事やページがある場合は必ず最後の「参考サイト」からリンクするようにしている
  • ブログのフォーマットは同じにしている
    • Table of Contents の設置や検証した環境の記載など
  • もし情報が間違っていた場合は必ず訂正し更新するようにしている
  • タグはしっかり細かく付与する
  • Blogger は記事の URL がカスタマイズできるので URL も記事の内容がわかりやすいものにする
  • スクリーンショット+テキストで残す
    • スクリーンショットだけだと検索しづらいので

最後に

テックブログを 1000 件以上書いたので記念として成果を紹介しました
役に立つ記事を書けている自身はありませんがこれからも頑張って続けていきたいかなと思っています

テック以外の新たな分野の記事を書くのもいいかなと思っています
2000 件に到達したらまた成果をまとめてみると面白いかなと思っています

2020年1月8日水曜日

ruby2.7.0 を試してみた

概要

昨年のクリスマスに ruby2.7.0 がリリースされました
早速ですが使ってみました
とりあえず主要そうな機能と簡単に試せそうな機能について軽く触れている感じです

環境

  • macOS 10.15.2
  • rbenv 1.1.2
  • ruby 2.7.0

ruby2.7.0 の準備

まずはインストールします
執筆時点では Homebrew に 2.7.0 がなかったので rbenv を使いました
rbenv ではすでに 2.7.0 の配信が始まっているようです

まずは ruby-build と rbenv を最新版にします

  • brew upgrade ruby-build rbenv

rbenv でインストール可能なバージョンの一覧を表示すると「2.7.0」があるのが確認できると思います

  • rbenv install --list

あとはインストールします
openssl のパスを指定していますがもしかすると不要です
rbenv が自動で最新版をインストールしてくれるっぽいです

  • export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  • export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"
  • rbenv install 2.7.0

ビルドが完了すれば rbenv のインストールパスにバイナリが配置されます

Downloading openssl-1.1.1d.tar.gz… -> https://dqw8nmjcqpjn7.cloudfront.net/1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2 Installing openssl-1.1.1d… Installed openssl-1.1.1d to /Users/hawksnowlog/.rbenv/versions/2.7.0   Downloading ruby-2.7.0.tar.bz2… -> https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.0.tar.bz2 Installing ruby-2.7.0… ruby-build: using readline from homebrew Installed ruby-2.7.0 to /Users/hawksnowlog/.rbenv/versions/2.7.0

あとはメインに設定すれば OK です

  • rbenv global 2.7.0
  • rbenv version
  • ``rbenv which ruby`` -v

=> ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]

これを使って試していきます

case でのパターンマッチング

オフィシャルのサンプルは以下のような感じです
JSON の文字列からハッシュを作成し、そのハッシュでパターンマッチングしています

require "json"

json = <<END
{
  "name": "Alice",
  "age": 30,
  "children": [{ "name": "Bob", "age": 2 }]
}
END

case JSON.parse(json, symbolize_names: true)
in {name: name, children: [{name: children_name, age: children_age}]}
  p name
  p children_name
  p children_age
end

case にパターンマッチさせたいオブジェクトを指定します
そして in でパターンとなる構文を指定できます

どうやらパターンマッチングにはいくつかあるようです上記はハッシュパターンというマッチングだと理解しました
例えば以下のように自分で作成したクラスでもパターンマッチングが使えます
ハッシュパターンを使って自分で作成したクラスをパターンマッチングさせたい場合は deconstruct_keys というメソッドを実装します

require "json"

class A
  def initialize
    @a = "A"
  end

  def deconstruct_keys(keys)
    {a: @a}
  end

  attr_accessor :a
end

case A.new
in {a: a}
  p a
else
  p "ng"
end

上記の場合実行するとインスタンス変数に設定した「A」が表示されます
こんな感じで自分のクラスに対してもパターンマッチングが使えます
ちなみにアレイパターンというマッチング方式もありその場合は deconstruct というメソッドを実装し配列を返却するようにすればアレイパターンを使ってパターンマッチングすることができます

require "json"

class A
  def initialize
    @a = "A"
  end

  def deconstruct
    ["C", "B", @a]
  end

  attr_accessor :a
end

case A.new
in ["C", "B", a]
  p a
else
  p "ng"
end

パターンマッチングについてはこちら が参考になりました

irb の複数行対応

例えば irb を使ってメソッドやクラスを定義した際に再度同じメソッドやクラスを定義したい場合は再度 1 行ずつ入力していました
それが 1 度の入力で済むようになっています
公式に動画あるのでそれを見るのが一番はやいと思います
以下は適当にキャプチャしたものです

rbenv でインストールした場合 irb は /Users/hawksnowlog/.rbenv/versions/2.7.0/bin/irb にありました

Compaction GC

未検証です
とりあえず GC.compact がどこでも呼べることは確認しています

キーワード引数に対してハッシュを与えると警告が表示される

これまではキーワード引数に対してハッシュでも渡すことができました
2.7.0 でも渡すことができますが警告が表示されるようになります
Ruby3.0 では正式に使えなくなるようです
具体的には以下の通りです

# coding: utf-8
def hoge(key: "default")
  p key
end

def fuga(key: "default", key2: "default2")
  p key
  p key2
end

hoge
hoge(key: "value")
hoge({key: "value"}) # これは警告
fuga
fuga(key: "value", key2: "value2")
fuga({key: "value", key2: "value2"}) # これも警告

hoge(**{key: "value"}) # double splat 演算子を付与すれば警告を回避できる
fuga(**{key: "value", key2: "value2"}) # double splat 演算子を付与すれば警告を回避できる

改めて見ると確かに単一の引数に対してハッシュが渡せてしまうのは気持ち悪い感じもします
表示される警告の内容は以下のような感じでした

test.rb:13: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call test.rb:2: warning: The called method `hoge' is defined here

警告を回避するには double splat 演算子を付与するか ruby 実行時のオプションで -W:no-deprecated を指定しても回避できます
コード内のどこかで Warning[:deprecated] = false を指定しても回避できます

  • ``rbenv which ruby`` -W:no-deprecated test.rb

また nil や配列の引数に対する挙動も変わっているので詳細は公式を御覧ください

範囲指定で開始番号が省略可能になった

省略した場合は 0 スタートになるようです
添字の部分には使えるようですが Range クラスのオブジェクトを作成する際にはちゃんとスタートの数字を指定しないとエラーになるようです

.. もメソッドなのでレシーバとなるオブジェクトがないから?

# coding: utf-8
ary = [0, 1, 2, 3, 4]
p ary[..3] # [0, 1, 2, 3]

ary = (..4).to_a # これはダメっぽい

tally

出現回数をカウントしてくれます
injectgroup_by + map を使って自分で作る必要がなくなったのは嬉しいです

ary = ["a", "a", "b", "c", "c", "c", "c"]
p ary.tally # {"a"=>2, "b"=>1, "c"=>4}

令和対応

合字の「㋿」に対応しました

p 0x32ff.chr("utf-8")

確かに古い Ruby だと表示されませんでした

その他細かい点

公式を見てください

最後に

簡単ですが Ruby2.7.0 に触れてみました
ほんの一部しか紹介できておらずまだまだたくさんの機能が追加されたので試してみてください
細かく触っていないのであれですがインパクトがあったのは irb のアップデートだったかなという印象です

詳細はすべて Feature Request の Issue でやり取りが公開されているのでそれを確認するのが早いと思います

2020年1月6日月曜日

golang で関数を扱うテクニック

概要

golang の関数を扱う Tips を紹介します
リファクタリングやテストを書く際に使えるテクニックです

環境

  • macOS 10.15.2
  • golang 1.12.9

オーバーライド的なことがしたい

構造体を新たに定義してそのフィールドにオーバーライドしたい構造体を 1 つ持たせます

package main

import (
    "fmt"
)

type Animal struct {

}

func (a *Animal) Cry() string {
    return "..."
}

type Dog struct {
    Animal
}

func (d *Dog) Cry() string {
    return "bowwow"
}

func main() {
    animal := Animal{}
    fmt.Println(animal.Cry())
    dog := Dog{animal}
    fmt.Println(dog.Cry())
}

Animal の Cry を Dog がオーバーライドしています
それぞれの構造体に合わせた Cry がコールされます
これだけだとあまり有り難みがわかりませんが interface と組み合わせることで柔軟な実装ができるようになります

package main

import (
    "fmt"
)

// Animal
type AnimalInterface interface {
    Cry() string
}

type Animal struct {

}

func (a *Animal) Cry() string {
    return "..."
}

type Dog struct {
    Animal
}

func (d *Dog) Cry() string {
    return "bowwow"
}

// Human
type Human struct {
    Pet AnimalInterface
}

func (h *Human) CryMyPet() string {
    return h.Pet.Cry()
}

func main() {
    animal := Animal{}
    dog := Dog{animal}
    // AnimalInterface を実装しているペットは何でも飼える
    player1 := Human{}
    player1.Pet = &dog
    // 飼っているペットに合わせて鳴き声が変わる
    fmt.Println(player1.CryMyPet())
}

AnimalInterface を抽出し Animal と Dog はそれらを実装します
こうすることで AnimalInterface を持つ Human は AnimalInterface を実装している好きなペットを飼うことができるようになります
また Human からの呼び出しを変更することなくペットの鳴き声を返ることができます

この手法はテスト時の mock の差し替えなどに使われます
本番では使う Pet は Dog だがテスト時には Animal という名前の mock を使わせることができます

オーバーライドが使えないケース

ある関数 (Cry) 内でコールされている関数 (GetName) だけをオーバーライドしようとしてもそれは動作しません
具体的には以下の通りで Dog は GetName だけをオーバーライドしたいのですが Cry がないため Animal の Cry -> GetName とコールされてしまいます
一見オーバーライドした Dog の GetName がコールされそうですがされないのです
しかも以下のサンプルはコンパイルエラーにはならないので更に注意が必要です

package main

import (
    "fmt"
)

// Animal
type AnimalInterface interface {
    Cry() string
    GetName() string
}

type Animal struct {

}

func (a *Animal) Cry() string {
    return a.GetName() + " < " + "..."
}

func (a *Animal) GetName() string {
    return "no name"
}

type Dog struct {
    Animal
}

// func (d *Dog) Cry() string {
//  return d.GetName() + " < " + "bowwow"
// }

func (d *Dog) GetName() string {
    return "Pochi"
}

// Human
type Human struct {
    Pet AnimalInterface
}

func (h *Human) CryMyPet() string {
    return h.Pet.Cry()
}

func main() {
    animal := Animal{}
    dog := Dog{animal}
    // AnimalInterface を実装しているペットは何でも飼える
    player1 := Human{}
    player1.Pet = &dog
    // 飼っているペットに合わせて鳴き声が変わる
    fmt.Println(player1.CryMyPet())
}

もし GetName をオーバーライドしたならそれを呼び出している Cry のオーバーライドも必要です
こんな感じのことをしたい場合は以下の方法を使うのもありだと思います

構造体のフィールドとして関数を持つことで DI 的なことができる

もうひとつ方法として関数自体を構造対のフィールドに設定することで関数の挙動をあとから変更する手法です

package main

import (
    "fmt"
)

type Animal struct {
    Cry func() string
}

func main() {
    animal := Animal{}
    // 構造体を生成後にフィールドに関数を設定する
    animal.Cry = func() string {
        return "..."
    }
    fmt.Println(animal.Cry())
    // 構造体を生成時に関数を定義することも可能
    animal2 := Animal{
        Cry: func() string {
            return "bowwow"
        },
    }
    fmt.Println(animal2.Cry())
}

これでも前者と呼び出しは変えずに関数自体の挙動を変更することができます
こちらのほうが柔軟でかつ挙動を変更したいときに変更できるので便利なのですが 1 つの構造体がいろいろな挙動を持つ可能性があるので複雑になることもあります

正直使い分けかなと思いますが前者のほうが抽象化という意味ではしっかりできているかなと思います

最後に

golang の関数を定義し扱うテクニックを少し紹介しました
これ以外にもいろいろとあると思いますが個人的にこういったテクニックは主にイベントハンドリングやテスト時にかなり有効な手法だと思います

例えば使いこなしているコードとそうでないコードではテストの書き方や見やすさ、書きやすさなどが大きく変わってくると思います

2020年1月5日日曜日

golang でテストさせたくないファイルを無視する方法

概要

例えばスタブとして作ったコードや自動生成させたコードはテストしたくないと思います
パッケージ自体が完全に分かれている場合はテスト時にパッケージを指定しなければいいのですが同一パッケージにある自動生成のファイルはそうはいきません
同一パッケージ内でテストをさせたくないファイルがある場合に有効です

環境

  • macOS 10.15.2
  • golang 1.12.9

流れ

流れとしてはテスト時にはカバレッジは算出せずテスト後にカバレッジだけ後から算出します
カバレッジを算出する際に算出対象のコードを指定する感じです

coverprofile を使ってプロファイルを取得する

  • go test . -coverprofile cover.out.tmp

プロファイルからカバレッジを算出しないソースを除外する

  • cat cover.out.tmp | grep -v "fake_" > cover.out

この「cover.out」に含まれているソースファイルだけがカバレッジの算出対象になります
上出来は grep -v で除外していますが grep を使って絞り込んでも OK です

この段階で test 時に生成した cover.out.tmp は不要なので削除して OK です

  • rm -f cover.out.tmp

カバレッジを算出する

  • go tool cover -func cover.out

これでプロファイルに記載されているソースコードのみカバレッジの算出対象になります
go tool cover というカバレッジを算出するツールがありこれはテスト自体は行わずカバレッジだけを算出することができます

最後に

毎回このコマンドを打つのは面倒なので Makefile などにしましょう
また cover.out.tmp と cover.out は毎回動的に生成したほうが良いです
なぜならテスト対象のソースコードが増える可能性があるからです
ただ git で管理する必要はないので .gitignore に含めておきましょう

正確には一旦すべてのファイルでテストしてそこからカバレッジを算出したいソースだけを絞り込む感じの方式なのでそれは認識しておきましょう