- 投稿日:2021-06-21T18:09:53+09:00
Go:テスト実行で環境変数を渡す方法など
はじめに Go開発していく上でテストはプログラム(成果物)の品質を保証するという観点でとても重要です。 しかし、最初のうちは色々と疑問とか慣れないことでストレスが溜まったりします。 筆者がGoテストで躓いてしまった時に調べたことやそれを通して得たチョットした知見などを共有したいと思います。 環境変数の渡し方 テスト実行の際に、環境変数が必要な場合に↓のように環境変数として渡すことができる。 1つの環境変数を渡す ENV_VAR=develop go test -v -run Test_xxxxx ENV_VARは環境変数名。developはその値。 複数の環境変数を渡す スペース区切り渡すだけ。 ENV_VAR1=develop API_URL=http://example.mock.com go test -v -run Test_xxxxx 環境変数が多いときはシェルスクリプトなどに記述したほうがいいと思います。 Testで前のテストのキャッシュを利用しない あまり利用する場面はないのかもしれませんが、こんな事もできるというメモ程度。 go clean -testcache && go test use non-cacheable flags on your test run. The idiomatic way is to use -count=1 (キャッシュさせないフラグとして、 -count=1をつけてテストを実行することもできる) go test -count=1 -v -run Testxxxxxx_Method テスト関数だけをテストしたい testしたいメソッドが記述されているディレクトリに移動する go test -v -run Test_xxxxxxxFunc と実行する # command-line-arguments とエラーになる 複数のテストが記述されたxxxx_test.goを実行。 $ go test -v xxxx_test.go # command-line-arguments [command-line-arguments.test] .\xxxx_test.go:10:7: undefined: xxxxx FAIL command-line-arguments [build failed] 失敗します。 原因 go run、go test、go buildなどを実行するときには、依存している全てのファイルが必要になります。 ドキュメント に説明があります。 解決するには 以下のように依存する全てのファイルを指定します。 go test -v xxxx_test.go abc_service.go xxx_util.go xxx_errors.go go run でも同じエラーが発生 下のようなファイル構成では、 ディレクトリ構成 . ├── main.go ├── service.go ← このファイルに関数が定義されています。 └── service_test.go $ go run main.go とすると、同じ様にエラーになります。 $ go run main.go # command-line-arguments main.go:xx:10: undefined: ExecuteNiceMethod 公式ドキュメント:Compile and run Go program で解説があります。 Run compiles and runs the named main Go package. Typically the package is specified as a list of .go source files from a single directory, ... 通常、パッケージは単一のディレクトリからの.goソースファイルのリストとして指定されます。 ということですね。 全てのgoファイルをリストとして指定しろ、とういうことです。 同ディレクトリに main.go 以外にも.goファイルが存在するときには注意が必要です。 解決方法 つまり、service.goもコンパイルには必要なので、 $ go run main.go service.go とすることで解決します。 ただし同ディレクトリに他にもgoファイルがあると大変なので、シェルスクリプトなどでまとめて実行できるように用意しておくのもいいかもしれません。筆者は↓のシェルスクリプトを用意してます。 execute_all_go_file.sh # ディレクトリのテストファイル`*_test.go`を除く全ての`.go`ファイルを実行するようにしています。 go run $(find . -name "*.go" -and -not -name "*_test.go" -maxdepth 1) 最後に テストはスクリプト動作の最低限の動作保証です。スムーズにテストを進めることで無駄なストレスなく、シンプルに質の高いプログラムの完成を目指せます。 気が向いたら随時更新していくかもしれません。 同じ様に悩んでいらっしゃる方の参考になるといいなと思い共有しました。
- 投稿日:2021-06-21T16:53:40+09:00
logging zap: レベル毎ファイル出力
Overview ログレベル毎にファイル出力する要件がまだある。 かつ、構造化ログを扱う。 zapが柔軟に設定できそうだった。 Detail 設定方法 出力ファイルの作成 zap core.EncoderConfigのインスタンス作成 zapcore.NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Coreで、レベル別のcoreのインスタンスを作成 オプションの生成 zap.New(core zapcore.Core, options ...Option) *Loggerで*Loggerのインスタンスを生成 Sample InfoとErrorを設定。Errorのみ標準エラーとファイルに出力。エラーハンドリングは省略。 package main import ( "os" "time" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { infoFile, _ := os.OpenFile("/tmp/info.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) errorFile, _ := os.OpenFile("/tmp/error.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) encoderConfig := zapcore.EncoderConfig{ TimeKey: "time", LevelKey: "level", NameKey: "name", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } infoCore := zapcore.NewCore( zapcore.NewConsoleEncoder(encoderConfig), zapcore.AddSync(infoFile), zapcore.InfoLevel, ) sy := zapcore.NewMultiWriteSyncer(os.Stderr, errorFile) errorCore := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), sy, zapcore.ErrorLevel, ) opts := []zap.Option{} opts = append(opts, zap.WrapCore(func(core zapcore.Core) zapcore.Core { return zapcore.NewSampler( core, time.Second, 100, //Initial 100, //Thereafter, ) })) logger := zap.New(zapcore.NewTee(infoCore, errorCore), opts...) logger.Info("test") logger.Error("aaaaaaa") // logger.Fatal("oooooo") } Reference
- 投稿日:2021-06-21T13:31:50+09:00
Go 備忘録
はじめに 投稿するには憚れるような細かいもので、いろいろ調べているのに、しばらくすると忘れてしまう内容をアウトプットしておきます。併せて自分なりの解決策も共有します。随時(気が向いた時に)更新予定です。 可変長引数関数 Variadic functions(可変個引数関数)は、無限に引数を受け取ることができます。 (Go by Example : variadic-functions) フォーマットは func f(args ...Type) です。 pack/unpack演算子 dotが3つには意味があって、...は、unpack演算子です。 ...は、pack とunpack の両方の演算子を意味しますが、3つのドット (...) が末尾にある場合、スライスを unpack(展開・解凍)します。 ↓のappendの例はです。 append(s1, s2...) この例ではスライスを一連の引数に解凍(unpack)していることになります。 (Passing_arguments_to_..._parameters) で解説されています。 いつか詳細を投稿してみます。 環境変数をコマンド実行時に設定する テスト実行の際に、環境変数が必要な場合に↓のように環境変数として渡すことができる。 ENV_VAR=develop go test -v -run Test_xxxxx ENV_VARは環境変数名。developはその値。 Testで前のテストのキャッシュを利用しない あまり利用する場面はないのかもしれませんが、こんな事もできるというメモ程度。 go clean -testcache && go test use non-cacheable flags on your test run. The idiomatic way is to use -count=1 (キャッシュさせないフラグとして、 -count=1をつけてテストを実行することもできる) go test -count=1 -v -run Testxxxxxx_Method #command-line-arguments # go runすると下のようなのエラーが表示されます。 $ go run main.go # command-line-arguments main.go:xx:10: undefined: ExecuteNiceMethod 何がいけないのか 同ディレクトリの別ファイルで定義している関数:ExecuteNiceMethod が読み込めていないようです。 ディレクトリ構成 . ├── main.go ├── service.go ← このファイルに関数が定義されています。 └── service_test.go ドキュメントには go run: Typically the package is specified as a list of .go source files. 実行に必要な.goファイルのリストを指定する必要がある。 全てのgoファイルをリストとして指定しろ、とういうことです。 同ディレクトリに main.go 以外にも.goファイルが存在するときには注意が必要です。 解決方法 つまり、service.goもコンパイルには必要なので、 $ go run main.go service.go とすることで解決します。 ただし同ディレクトリに他にもgoファイルがあると大変なので、シェルスクリプトなどでまとめて実行できるように用意しておくのもいいかもしれません。筆者は↓のシェルスクリプトを用意してます。 execute_all_go_file.sh # ディレクトリのテストファイル`*_test.go`を除く全ての`.go`ファイルを実行するようにしています。 go run $(find . -name "*.go" -and -not -name "*_test.go" -maxdepth 1) 最後に 同じ様に悩んでいらっしゃる方の参考になると幸いです。
- 投稿日:2021-06-21T11:57:26+09:00
goをmacで!チュートリアル
概要 業務でgoを使うことになったので導入のチュートリアル Dockerは使わずにMacのローカル環境に入れる 前提 macOS Catalina (まだBig Surが安定してなさそうなので) goのバージョンは、1.13.xを使用。(今回は1.13.15で実施。ちなみに現時点の最新は1.16.5) goの格納先のpathは一例なので好きに変えても良い 手順 goのダウンロード 公式サイトのDownloadページ を開く https://golang.org/dl/ 下のほうにある、Archived versions のプルダウンの中から go1.13.X を押下してアコーディオンを開く go1.13.X.darwin-amd64.tar.gz をクリックしてダウンロード ダウンロードしたzipを解凍 解凍したファイルを以下のディレクトリに移動 ~/go1.13.X シンボリックリンク作成 goのバージョンを切り替えやすくするため、GOROOTに設定するpathはシンボリックリンクを作成する $ ln -s ~/go1.13.X ~/goroot $ ls -l ~ pathを通す $ vi ~/.bash_profile 以下を追加 export GOROOT=$HOME/goroot export GOPATH=$HOME/gosources export PATH=$PATH:$GOROOT/bin 再読み込み $ source ~/.bash_profile 確認 $ go version go version go1.13.X darwin/amd64 ※この時、以下のような警告が出る場合がある。 悪質なソフトウェアかどうかをappleでは確認できないため、このソフトウェアは開けません。 この場合は以下の手順を実行してからバージョン確認する MacのFinder で、開きたいアプリケーションを検索 Launchpadは使用しない。(Launchpadではショートカットメニューにアクセスできない) Controlキーを押しながらgoのアプリケーションアイコンをクリックして、ショートカットメニューから「開く」を選択 「開く」をクリック これでgoのアプリケーションがセキュリティ設定の例外として保存され、今後は登録済みのアプリケーションと同様に、ダブルクリックすることで開くことができるようになる 参考:https://support.apple.com/ja-jp/guide/mac-help/mh40616/mac
- 投稿日:2021-06-21T11:57:26+09:00
macにGoを導入する
概要 業務でgoを使うことになったので導入の流れをメモ Dockerは使わずにMacのローカル環境に入れる 前提 macOS Catalina (まだBig Surが安定してなさそうなので) goのバージョンは、現時点の最新の 1.16.5※導入後にvscodeで使用するdelveツールが1.14からしかサポートしなくなったので最新入れます(2021.6.25現在) goの格納先のpathは一例なので好きに変えても良い 手順 goのダウンロード 公式サイトのDownloadページ を開く https://golang.org/dl/ go1.16.5 を押下してアコーディオンを開く go1.16.5.darwin-amd64.tar.gz をクリックしてダウンロード ダウンロードしたzipを解凍 解凍したファイルを以下のディレクトリに移動 ~/go1.16.5 シンボリックリンク作成 goのバージョンを切り替えやすくするため、GOROOTに設定するpathはシンボリックリンクを作成する $ ln -s ~/go1.16.5 ~/goroot $ ls -l ~ pathを通す $ vi ~/.bash_profile 以下を追加 export GOROOT=$HOME/goroot export GOPATH=$HOME/gosources export PATH=$PATH:$GOROOT/bin 再読み込み $ source ~/.bash_profile 確認 $ go version go version go1.16.5 darwin/amd64 ※この時、以下のような警告が出る場合がある。 悪質なソフトウェアかどうかをappleでは確認できないため、このソフトウェアは開けません。 この場合は以下の手順を実行してからバージョン確認する MacのFinder で、開きたいアプリケーションを検索 Launchpadは使用しない。(Launchpadではショートカットメニューにアクセスできない) Controlキーを押しながらgoのアプリケーションアイコンをクリックして、ショートカットメニューから「開く」を選択 「開く」をクリック これでgoのアプリケーションがセキュリティ設定の例外として保存され、今後は登録済みのアプリケーションと同様に、ダブルクリックすることで開くことができるようになる 参考:https://support.apple.com/ja-jp/guide/mac-help/mh40616/mac
- 投稿日:2021-06-21T10:51:42+09:00
sqlboiler ~参照編まとめ~
sqlboilerの参照系のクエリ構築の記事になります。 ポイントとしてはクエリに利用するテーブル名やカラム名などはタイプセーフに扱いましょう。 タイプセーフにクエリを構築することで、テーブル構造が変わった際の変更忘れもコンパイルエラーを起こして、安全に開発することができます。 クエリ構築の概念 sqlboilerでのクエリ構築は主に、Query Mod Systemにより制御されます。 Query Mod SystemはStarterとFinisherによるクエリ構築を提供しています。 Starter sqlboilerで生成されたmodelの複数名になります。(UsersやStaffsなど) Finisher StarterとWhereなどの取得条件の後にくる関数で、Finisherをセットすることで最終的なクエリ結果をsqlboilerのmodelで返します。 下記はusersテーブルから全てのデータをUsersmodelとして取得している例になります。 import ( "context" qm "github.com/volatiletech/sqlboiler/v4/queries/qm" "github.com/sqlboiler_sample/models" // sqlboilerのgenerateコマンドで作成されたmodel群 ) var err error // contextのセット(以下省略) ctx := context.Context() // dbコネクションのセット(以下省略) if dbConn, err = sql.Open("devdb", fmt.Sprintf("connection_%d", time.Now().UnixNano())); err != nil { panic(err) } // Strat().Finisher();の形でクエリ結果をmodelの形で取得する var users []*models.Users if users, err = models.Users().All(ctx, db); err != nil { fmt.Println(err.Error()) } 通常のクエリ 該当レコードを1つ取得 var err error var user *models.User if user, err = models.Users().One(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコードを全て取得 var err error var users models.UserSlice if users, err = models.Users().All(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコード数を取得 var err error var usersCount int64 if usersCount, err = models.Users().Count(ctx, db); err != nil { fmt.Println(err.Error()) } 該当レコードの存在有無を取得 var err error var userExists bool if userExists, err = models.Users().Exists(ctx, db); err != nil { fmt.Println(err.Error()) } 複雑なクエリ 上記のような単純なクエリだけでなく、複雑なクエリが必要になることもあります。 通常のSQLと同じように、whereやgroup_by, limit, offsetなどSQLで使える基本的なものは sqlboilerでも使えます。 var err error var users models.UserSlice if users, err = models.Users( qm.Select("id", "name"), qm.InnerJoin("credit_cards c on c.user_id = users.id"), qm.Where("age > ?", 30), qm.AndIn("c.kind in ?", "visa", "mastercard"), qm.Or("email like ?", `%aol.com%`), qm.GroupBy("id", "name"), qm.Having("count(c.id) > ?", 2), qm.Limit(5), qm.Offset(6), ).All(ctx, db); err != nil { fmt.Println(err.Error()) } sqlboilerはタイプセーフに利用しよう! クエリに限らずですが、sqlboiler model で生成された識別子を利用することで安全な実装をすることができます。 【タイプセーフに使う】 上記処理をタイプセーフに書き換えるとこうなります。 var err error var users models.UserSlice if users, err = models.Users( ID qm.Select(UserColumns.ID, UserColumns.Name), // 「テーブル名Columns」のように、各テーブルのカラムがstructとして生成されます InnerJoin( fmt.Sprintf("%s on %s.%s = %s.%s", TableNames.CreditCards, TableNames.CreditCards CreditCardColumns.ID, TableNames.Users, UserColumns.ID), ), qm.Where(fmt.Sprintf("%s > ?", UserColumns.Age), 30), qm.AndIn(fmt.Sprintf("%s.%s in ?", CreditCardColumns.Kind), "visa", "mastercard"), qm.Or(fmt.Sprintf("%s like ?", UserColumns.Email), `%aol.com%`), qm.GroupBy(UserColumns.ID, UserColumns.Name), qm.Having(fmt.Sprintf("count(%s) > ?", CreditCardColumns.ID), 2), qm.Limit(5), qm.Offset(6), ).All(ctx, db); err != nil { fmt.Println(err.Error()) } この記事に書いてあることができたら・・・ 「Issue」のタスクをこなすことができます❗️ IssueではIssue単位で企業のお仕事ができるサービスです Ex. Firebaseでメールアドレス確認メール文章変更の実装 単価3,000円で受けて10時間ほどで実装した場合 3,000円 × 10時間 = 30,000円 登録は こちら から! サービスページは こちら! 企業様登録はこちら
- 投稿日:2021-06-21T10:49:21+09:00
sqlboilerの導入
環境 Go 1.16~ sqlboiler v.4.4.0 1. sqlboilerのインストール # slqboilerのインストール go install github.com/volatiletech/sqlboiler/v4@latest # driverのインストール(今回はMySQL) go install github.com/volatiletech/sqlboiler/v4/drivers/sqlboiler-mysql@latest 古いバージョンをインストールして期待通りの動作にならないことを防ぐため、/vサフィックス(/v4の部分)を忘れないようにしましょう! 2. 構成ファイル(sqlboiler.toml)の作成 データベースドライバーや構成オプションを設定するために、slqboiler.tomlファイルを作成します。 MySQLを利用する場合下記のような構成になります。 sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" tips 【オプションの追加】 オプションとしてwhitelist、blacklistが存在します。 whitelist→追加したテーブルのsqlboiler modelが作成されます blacklist→追加したテーブルのsqlboiler modelは作成されなくなります whitelist sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" blacklist=["users"] # usersテーブルのsqlboiler modelのみが作成される blacklist すでにデータベースには存在しているが、sqlboiler modelとして生成したくない場合はテーブル名を指定します。 sqlboiler.toml [mysql] dbname="devdb" host="localhost" port=3306 user="root" pass="" sslmode="false" blacklist=["staffs"] # staffsテーブルのsqlboiler modelは作成されない 3. sqlboilerモデル群の作成 sqlboiler ドライバー名でsqlboiler modelの生成を行います。 $ sqlboiler mysql コマンド実行後、実行フォルダ配下にsqlboiler model群が作成されます。 . ├── models │ ├── users.go # DBに存在するテーブル │ ├── users_test.go # users modelのテストファイル │ ├── staffs.go │ ├── staffs_test.go │ ├── boil_main_test.go │ ├── boil_queries.go │ ├── boil_queries_test.go │ ├── boil_suites_test.go │ ├── boil_table_names.go # model出力されたテーブル名が定義されている │ ├── boil_types.go # enum値が定数として保存 advanced! 【オプションをつけてmodel生成をカスタマイズ】 model生成コマンドにもオプションをつけることができます。 Name 役割 Defaults pkgname 作成ファイル名の指定 "models" output 出力フォルダ名 "models" tag Struct tag出力オプション [] debug デバッグモードの出力制限 false add-global-variants グローバル変数の出力制限 false add-panic-variants panic 変数の出力制限 false no-context context.Context利用無効 false no-hooks hooks機能の無効 false no-tests テストファイル無効 false no-auto-timestamps タイムスタンプ無効 false no-rows-affected APIで影響を受ける行の無効 false no-driver-templates DBドライバーのテンプレート解析無効 false tag-ignore タグの無視カラムリスト [] 下記のような実行もできます。 sqlboiler mysql --pkgname sqlboilermodels --output "./package/sqlboilermodels" --wipe --config ./sqlboiler.toml ・pkgname 出力ファイル名の指定 ・output 出力フォルダの指定 ・wipe 健全性確保のため、出力modelファイルを先に削除→出力する ・config sqlboiler.tomlで構成した内容で設定を上書き 出力結果 . ├── package │ └── sqlboilermodels(--output, --pkgname) │ ├── users.go │ ├── users_test.go │ ├── staffs.go │ ├── staffs_test.go │ ├── boil_main_test.go │ ├── boil_queries.go │ ├── boil_queries_test.go │ ├── boil_suites_test.go │ ├── boil_table_names.go │ ├── boil_types.go 参考 この記事に書いてあることができたら・・・ 「Issue」のタスクをこなすことができます❗️ IssueではIssue単位で企業のお仕事ができるサービスです Ex. Firebaseでメールアドレス確認メール文章変更の実装 単価3,000円で受けて10時間ほどで実装した場合 3,000円 × 10時間 = 30,000円 登録は こちら から! サービスページは こちら! 企業様登録はこちら
- 投稿日:2021-06-21T01:16:40+09:00
Go言語でRSAを実装してみる
Go言語で公開鍵暗号RSAを実装してみました。 こちらのPythonコードをGo言語に翻訳しただけの記事です。 大変参考になるので、この記事を読むよりこっちのほうがいいと思います。 コード 以下がコードとなりますが、変な書き方とかありましたら教えて欲しいです、、、 思ったより実装がシンプルだったことに驚きました。 一応Githubにも載せてます main.go package main import ( "fmt" "math/big" ) type publicKey struct { E int N int } type privateKey struct { D int N int } func main() { publicKey, privateKey := generateKeys(101, 3259) plainText := "This is a secret" encryptedText := encrypt(plainText, publicKey) decryptedText := decrypt(encryptedText, privateKey) fmt.Println("Public Key:", publicKey.E, publicKey.N) fmt.Println("Private Key:", privateKey.D, privateKey.N) fmt.Println("Message:", plainText) fmt.Println("Encrypted Text:", encryptedText) fmt.Println("Decrypted Text:", decryptedText) } func generateKeys(p int, q int) (public publicKey, secret privateKey) { N := p * q L := lcm(p-1, q-1) var D, E int for i := 2; i < L; i++ { if gcd(i, L) == 1 { E = i break } } for i := 2; i < L; i++ { if (E*i)%L == 1 { D = i break } } return publicKey{E: E, N: N}, privateKey{D: D, N: N} } func lcm(p int, q int) int { return (p * q) / gcd(p, q) } func gcd(p int, q int) int { // https://play.golang.org/p/SmzvkDjYlb for q != 0 { t := q q = p % q p = t } return p } func encrypt(plainText string, publicKey publicKey) string { E, N := publicKey.E, publicKey.N resultString := "" for _, char := range plainText { bigChar := big.NewInt(int64(char)) bigE := big.NewInt(int64(E)) bigN := big.NewInt(int64(N)) res := *bigChar.Exp(bigChar, bigE, bigN) resultString += string(rune(res.Int64())) } return resultString } func decrypt(encryptedText string, privateKey privateKey) string { D, N := privateKey.D, privateKey.N resultString := "" for _, char := range encryptedText { bigChar := big.NewInt(int64(char)) bigD := big.NewInt(int64(D)) bigN := big.NewInt(int64(N)) res := *bigChar.Exp(bigChar, bigD, bigN) resultString += string(rune(res.Int64())) } return resultString } 走らせてみる (master) ✗ go run main.go Public Key: 7 329159 Private Key: 46543 329159 Message: This is a secret Encrypted Text: ???????????段??段? Decrypted Text: This is a secret