- 投稿日:2020-07-25T19:02:18+09:00
Github側でREAD.meをいじったら、gitにpushできなくなった
pushできていたリポジトリにpushできなくなった。
恐らくREADME.mdの内容をgithubのサイト上で変更したことが原因だと思われるが、解決方法がわからない。結論から言えば、この記事通りにやったら無事解決できた。
以下、一通り出たエラーとやったことを書き残しておく。
$git push origin master ! [rejected] master -> master (fetch first) error: failed to push some refs to 'https://github.com/…/リポジトリ名.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.とりあえずpullしろと言われているようなので、pullしてみたが似たようなエラーが。
$ git pull origin master remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (3/3), done. From https://github.com/ユーザー名/リポジトリ名 * branch master -> FETCH_HEAD 060ee7a..4c90029 master -> origin/master Auto-merging README.md CONFLICT (content): Merge conflict in README.md Automatic merge failed; fix conflicts and then commit the result.やはりREADME.mdが原因の様子。
正直gitはpushとpullしかしたことがない私には、mergeがなんなのかよくわからないが、とりあえずエラーをググると$git push
で解決できると書いてあったので実行してみた。$ git push fatal: The current branch master has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin master $git push --set-upstream origin master To https://github.com/ユーザー名/リポジトリ名.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'https://github.com/ユーザー名/リポジトリ名.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.うーん。やってみたが解決しないので、もう一度git push origin masterしてみる
$ git push origin master To https://github.com/ユーザー名/リポジトリ名.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'https://github.com/ユーザー名/リポジトリ名.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. $ git pull origin master error: Pulling is not possible because you have unmerged files. hint: Fix them up in the work tree, and then use 'git add/rm <file>' hint: as appropriate to mark resolution and make a commit. fatal: Exiting because of an unresolved conflict.と、若干違うエラーが出たので、再度検索してみたら出てきたこの記事を参考に、その通りに実行したら無事解決!
0$ git add -A $ git commit [master 015118d] Merge branch 'master' of https://github.com/ユーザー名/リポジトリ名 $ git pull origin master From https://github.com/ユーザー名/リポジトリ名 * branch master -> FETCH_HEAD Already up to date. $ git push origin master Enumerating objects: 52, done. Counting objects: 100% (38/38), done. Delta compression using up to 4 threads Compressing objects: 100% (20/20), done. Writing objects: 100% (20/20), 2.46 KiB | 838.00 KiB/s, done. Total 20 (delta 10), reused 0 (delta 0) remote: Resolving deltas: 100% (10/10), completed with 8 local objects. remote: remote: GitHub found 5 vulnerabilities on ユーザー名/リポジトリ名's default branch (1 high, 3 moderate, 1 low). To find out more, visit: remote: https://github.com/ユーザー名/リポジトリ名/network/alerts remote: To https://github.com/ユーザー名/リポジトリ名.git 4c90029..015118d master -> master
- 投稿日:2020-07-25T18:35:04+09:00
Git コンフリクトの解消
コンフリクトとは
AとBの作業ブランチがあるとする。
まずAがリモートリポジトリにプッシュされ、マージされる。
次にBがリモートリポジトリにプッシュされる。
このとき、 AとBで変更内容に対立が起こった場合、どちらを採用すればようのか分からなくなる。
この対立をコンフリクトという。コンフリクト発生
GitHubにプッシュした際に以下の表示が出たらコンフリクトが起きているということ。
解消の流れ
1.ローカルでmasterブランチに切り替える
2.リモートのmasterブランチをプルする
3.作業ブランチに切り替える
4.masterブランチをマージする
5.コンフリクトが発生している部分を修正する
6.リモートにプッシュする1.ローカルでmasterブランチに切り替える
$ git checkout master2.リモートのmasterブランチをプルする
$ git pull origin master3.作業ブランチに切り替える
$ git checkout B_branch4.masterブランチをマージする
$ git merge master5.コンフリクトが発生している部分を修正する
<<<<<<< HEAD
ここが作業ブランチの変更内容=======
ここがmasterの変更内容
.>>>>>>> master今回は両方の変更を取り込むことにします。
6.リモートにプッシュする
$ git add .$ git commit -m "コンフリクト解消内容"$ git push origin HEADこれでコンフリクトが解消できました!
- 投稿日:2020-07-25T17:02:31+09:00
単にテキストを出力して差分を見るというテスト手法
この記事ではどこでも使えて強力なテスト手法を紹介します
- テストフレームワークを使いません。
- テキストが出力できれば、どの言語でも使えます。
手順
- プログラムを Git 管理する
- 結果をテキストでファイル出力するプログラム書く(出力結果は
.gitignore
しない)- 出力ディレクトリについて
git diff
を表示する以上
実践
テスト対象のコードとして、コサインの近似値の計算を Python で書きました。
my_math.pyimport math def cos(t): t %= math.pi * 2 if (t < math.pi * 0.25): return _cos(t) elif (t < math.pi * 0.75): return _sin(t - math.pi * 0.5) elif (t < math.pi * 1.25): return -_cos(t - math.pi) elif (t < math.pi * 1.75): return _sin(t - math.pi * 1.5) else: return _cos(t - math.pi * 2) def _sin(t): result = current = t; # テイラー展開 for i in range(2, 10, 2): current *= - t * t / (i * (i + 1)) result += current return result def _cos(t): result = current = 1; # テイラー展開 for i in range(1, 11, 2): current *= - t * t / (i * (i + 1)) result += current return result1. Git 管理する
サンプルの Git レポジトリは
https://github.com/shohei909/diff_test
に用意しました2. テキストを標準出力する
これに対して以下のようなテストコードを書きます。単に
-3 * PI
から3 * PI
までのmy_math.cos()
の計算結果を標準出力していますtest/code/my_cos_test.pyimport sys import math import os sys.path.append(os.path.dirname(__file__) + '/../../') import my_math print("ver:0.0") # このテストのバージョン print("my_math.cos()の戻り値に対するテスト") # このテストの説明文 resolution = 8 for i in range(-3 * resolution, 3 * resolution): t = i / resolution print("cos({:<6} * PI): {}".format(t, my_math.cos(math.pi * t)))3. 結果をファイルにリダイレクトして、
git diff
を見るテストコードの実行結果をファイルに保存していくスクリプトを書きます。具体的には以下の内容です
test/code
以下のすべてのコードを実行する- その標準出力を
test/out
下のファイルにリダイレクトtest/run.pyimport glob import subprocess import os import sys test_dir = os.path.dirname(__file__) out_files = set() # code 以下の *.py ファイルを検索 for file in glob.glob(test_dir + "/code/**/*.py", recursive=True): relpath = os.path.relpath(file, test_dir + "/code") file_name = os.path.splitext(relpath)[0] extension = ".txt" out_file_path = test_dir + "/out/" + file_name + extension os.makedirs(os.path.dirname(out_file_path), exist_ok=True) # 出力フォルダが無ければ作成 out_file = open(out_file_path, "w") # `python *.py` のプロセスを実行 subprocess.run([sys.executable, file], stdout=out_file) # 出力したファイルを記録 out_files.add(os.path.abspath(out_file_path)) # 削除ずみのテストの出力を削除 for file in glob.glob(test_dir + "/out/**/*", recursive=True): if os.path.isfile(file): if not os.path.abspath(file) in out_files: # 出力ファイルに含まれてないファイルを削除 os.remove(file)次に、出力結果の
git diff
を出すスクリプトですtest/diff.pyimport subprocess import os # Git で差分を表示 test_dir = os.path.dirname(__file__) subprocess.run(["git", "add", "-N", test_dir + "/out"]) subprocess.run(["git", "--no-pager", "diff", "--relative=test/out", "--ignore-space-change"])上記2つを合わせて実行します
test/test.pyimport importlib importlib.import_module('run') importlib.import_module('diff')以上の3つのスクリプトのうち
run.py
を少し改変すればテスト対象が Python でない場合でも使用できます。実行結果は以下のようになります
diff --git a/my_cos_test.txt b/my_cos_test.txt new file mode 100644 index 0000000..0f305ad --- /dev/null +++ b/my_cos_test.txt @@ -0,0 +1,50 @@ +ver:0.0 +my_math.cos()の戻り値に対するテスト +cos(-3.0 * PI): -1.0 +cos(-2.875 * PI): -0.9238795325112585 +cos(-2.75 * PI): -0.7071067829368665 +cos(-2.625 * PI): -0.3826834323659474 +cos(-2.5 * PI): 0.0 +cos(-2.375 * PI): 0.3826834323659474 +cos(-2.25 * PI): 0.7071067810719247 +cos(-2.125 * PI): 0.9238795325112585 +cos(-2.0 * PI): 1.0 +cos(-1.875 * PI): 0.9238795325112585 +cos(-1.75 * PI): 0.7071067829368671 +cos(-1.625 * PI): 0.3826834323659474 +cos(-1.5 * PI): -0.0 +cos(-1.375 * PI): -0.3826834323659474 +cos(-1.25 * PI): -0.7071067810719247 +cos(-1.125 * PI): -0.9238795325112586 +cos(-1.0 * PI): -1.0 +cos(-0.875 * PI): -0.9238795325112586 +cos(-0.75 * PI): -0.7071067829368671 +cos(-0.625 * PI): -0.3826834323659474 +cos(-0.5 * PI): 0.0 +cos(-0.375 * PI): 0.3826834323659474 +cos(-0.25 * PI): 0.7071067810719247 +cos(-0.125 * PI): 0.9238795325112585 +cos(0.0 * PI): 1.0 +cos(0.125 * PI): 0.9238795325112586 +cos(0.25 * PI): 0.7071067829368671 +cos(0.375 * PI): 0.38268343236594693 +cos(0.5 * PI): -0.0 +cos(0.625 * PI): -0.38268343236594693 +cos(0.75 * PI): -0.7071067810719247 +cos(0.875 * PI): -0.9238795325112586 +cos(1.0 * PI): -1.0 +cos(1.125 * PI): -0.9238795325112586 +cos(1.25 * PI): -0.7071067829368671 +cos(1.375 * PI): -0.3826834323659474 +cos(1.5 * PI): 0.0 +cos(1.625 * PI): 0.3826834323659474 +cos(1.75 * PI): 0.7071067810719247 +cos(1.875 * PI): 0.9238795325112585 +cos(2.0 * PI): 1.0 +cos(2.125 * PI): 0.9238795325112585 +cos(2.25 * PI): 0.7071067829368671 +cos(2.375 * PI): 0.3826834323659474 +cos(2.5 * PI): -0.0 +cos(2.625 * PI): -0.3826834323659474 +cos(2.75 * PI): -0.7071067829368665 +cos(2.875 * PI): -0.9238795325112585出てきた差分が意図通りならOKです。
達成できたこと
これだけではテストとして、不十分に見えますか?
しかし、少なくともテストに求められる重要なことのいくつかは達成できています
- プログラムの挙動の可視化
- プログラムが最後まで動くことのチェック
- デグレード(退化)を起こしていないことのチェック
特に重要なのが3つ目です。Git 差分の表示で変更行がないことを確認すれば挙動が変わってないことのチェックができます。
また、不安に思う部分には assert も書いていけば、いわゆるテストファースト的な手法を複合できます。
テストを書く際のポイント
よく「テストはドキュメント」といった言い回しを耳にしますが、この手法の場合は出力もドキュメントです。
ですから、以下のことを意識して書くといいです
- テストのファイル名に、より良く内容を表現した名前を付ける
- 出力ファイルの冒頭にテスト内容の説明文を書く
- テストのプログラムから出力される各行にはそれが何かわかるようなラベルを書く
CIを行う
CI(継続的インテグレーション)を行うとより快適に開発できるでしょう。
具体的には、プルリクエストをトリガーとして、自動で以下の2つを行うといいです
test/run.py
を実行して差分をコミットする- 出力に意図しない差分があったら failure させる
test/run.py
を実行して差分をコミットするこれを行うことで、テストを実行し忘れた状態でマージされることを防げます。
さらに
- 終了コードが
0
であること- 標準エラー出力がされていないこと
を見てテストが正常に終了したかを判定するといいです。
出力に意図しない差分があったら failure させる
プルリクエスト内容の差分を見るだけであれば以下のコマンドを実行すればいいです
git diff --no-pager マージ先ブランチ(base)...リクエストしてるブランチ(head) --relative=test/out(
--ignore-space-change
をつけるべきかは、ソフトウェアの性質に応じて要検討)ただ、これだけでは変更が意図的かどうかの区別ができません。
変更が意図的なものかどうかを区別するためにつけてるのが、出力ファイルの先頭の
ver:0.0
のバージョン情報です。このバージョン番号は以下のルールに従って書き換えをします
- 出力ファイルの変更が、行の追加のみであることを意図しているなら、マイナーバージョンを上げる。(
0.0
->0.1
)- 出力ファイルの変更が、行の削除を含むことを意図しているなら、メジャーバージョンを上げる。(
0.1
->1.0
)diff を読んで上記のルールを満たしているか判定するプログラムを書けば、マージの可否を判定する CI を組むことができます。
この判定を行うプログラムは以下に公開しています
https://github.com/shohei909/diff_test/blob/master/test/check.py
テスト対象を広げる
実践例で紹介したのは、いわゆる単体テスト的なものですが、より結合テスト的なものに対しても効果的です。
例えば、以下のようなものです
- JSON返すWebサーバーなら、実際にアクセスしたレスポンスを記録する (JSONは
jq . -sort-keys
などで内容のソートをしておくといい)- コマンドラインツールなら、実際にそのツールを実行した結果を記録する
- Webサイトなら、 Headless Chrome でそのページにアクセスした結果のスクリーンショット画像などを記録する
差分を見るテストのメリット
デグレードに対する耐性が高い
この手法はデグレードを発見する力高いです。この点については、あらかじめ期待される結果を手で書く手法と比べても強力です。
というのも、期待される結果を手で書く場合、「手で書ける程度の個数」のケースしか用意できません。網羅的にデグレードをチェックできるほどのケースを書いて用意するのは時間がかかります。
しかし、単に結果を出力しておくのであれば、「目を通せる程度の個数」にまで広がります。
プロトタイピングや仕様変更の速度を落とさない
テストを書く大きなメリットは、リファクタリングが容易になることです。リファクタリング時のデグレードのリスクが抑えられます。
逆に、テストを大量に書くデメリットもあります
- ソフトウェアのプロトタイプが実際に動くまでに時間がかかる
- 仕様変更が発生したときのテストの書き直しが大変になる
単にテキストを出力しておく手法はテストコードは比較的小さく、これらのデメリットが小さいです。
仕様の変更内容によっては、テストコードに対する変更は単に
ver:*.*
を書き換えるだけで済みます。それでいて、プログラムの挙動がどう変わったかは出力の差分を見れば明確です。このテスト手法は、すばやくプロトタイプを作って、その挙動や反省を踏まえてリファクタリングや仕様の改善を繰り返していくような開発フローとの相性がいいです。
挙動が可視化される & 挙動の履歴をたどれるようになる
この手法でテストを書くということは、サンプルコードとその実行結果を残しておくということそのものです。これらがあれば、レビュアーも、新規メンバーも、実装者自身もコードがどういう動きをするのか理解しやすくなります。
また、 Git 上に挙動ベースでの変更履歴が残ることにもメリットがあります。この履歴は、例えば、後からバグが発見された場合に、その原因になった挙動がいつから発生していたか、どの修正によって発生かを調べる手助けになります。
テストフレームワークにロックインされない
テストコードはコード量が多くなりがちな部分なので、例えばテストフレームワークが言語のアップデートに追随してないといった事態には対処が大変です。
この手法では、テストフレームワークのへの依存が無いのでそのリスクがありません。
発展:SVGを出力して差分を見る
テキストの出力はより挙動をわかりやすく表現する形で行うのが理想です。より視覚的に表現する方法として SVG を出力するというのがあります。
先ほどの
my_math.cos()
でそれをやってみたのが以下です。https://github.com/shohei909/diff_test/pull/1/files#diff-859f44ef4608ed7f4557a2bfcb2ce991
ちゃんとコサインの近似ができていることが視覚的にわかります。
SVG はテキストと画像の両面の性質があるので、両面のメリット受けられます。例えば、値が変わったかを厳密に見たければテキストで見ればいいですし、目視でわかる変化があったかを見たければ画像としてみればいいです。
試しに、
my_math.cos()
の精度を落とす変更を加えてみます誤差が目視ではわからないレベルであることが確認できました。
- 投稿日:2020-07-25T17:02:31+09:00
単にテキストを出力して差分を見るというテスト手法のススメ
この記事ではどこでも使えて強力なテスト手法を紹介します
- テストフレームワークを使いません。
- テキストが出力できれば、どの言語でも使えます。
手順
- プログラムを Git 管理する
- 結果をテキストでファイル出力するプログラム書く(出力結果は
.gitignore
しない)- 出力ディレクトリについて
git diff
を表示する以上
実践
テスト対象のコードとして、コサインの近似値の計算を Python で書きました。
my_math.pyimport math def cos(t): t %= math.pi * 2 if (t < math.pi * 0.25): return _cos(t) elif (t < math.pi * 0.75): return _sin(t - math.pi * 0.5) elif (t < math.pi * 1.25): return -_cos(t - math.pi) elif (t < math.pi * 1.75): return _sin(t - math.pi * 1.5) else: return _cos(t - math.pi * 2) def _sin(t): result = current = t; # テイラー展開 for i in range(2, 10, 2): current *= - t * t / (i * (i + 1)) result += current return result def _cos(t): result = current = 1; # テイラー展開 for i in range(1, 11, 2): current *= - t * t / (i * (i + 1)) result += current return result1. Git 管理する
サンプルの Git レポジトリは
https://github.com/shohei909/diff_test
に用意しました2. テキストを標準出力する
これに対して以下のようなテストコードを書きます。単に
-3 * PI
から3 * PI
までのmy_math.cos()
の計算結果を標準出力していますtest/code/my_cos_test.pyimport sys import math import os sys.path.append(os.path.dirname(__file__) + '/../../') import my_math print("ver:0.0") # このテストのバージョン print("my_math.cos()の戻り値に対するテスト") # このテストの説明文 resolution = 8 for i in range(-3 * resolution, 3 * resolution): t = i / resolution print("cos({:<6} * PI): {}".format(t, my_math.cos(math.pi * t)))3. 結果をファイルにリダイレクトして、
git diff
を見るテストコードの実行結果をファイルに保存していくスクリプトを書きます。具体的には以下の内容です
test/code
以下のすべてのコードを実行する- その標準出力を
test/out
下のファイルにリダイレクトtest/run.pyimport glob import subprocess import os import sys test_dir = os.path.dirname(__file__) out_files = set() # code 以下の *.py ファイルを検索 for file in glob.glob(test_dir + "/code/**/*.py", recursive=True): relpath = os.path.relpath(file, test_dir + "/code") file_name = os.path.splitext(relpath)[0] extension = ".txt" out_file_path = test_dir + "/out/" + file_name + extension os.makedirs(os.path.dirname(out_file_path), exist_ok=True) # 出力フォルダが無ければ作成 out_file = open(out_file_path, "w") # `python *.py` のプロセスを実行 subprocess.run([sys.executable, file], stdout=out_file) # 出力したファイルを記録 out_files.add(os.path.abspath(out_file_path)) # 削除ずみのテストの出力を削除 for file in glob.glob(test_dir + "/out/**/*", recursive=True): if os.path.isfile(file): if not os.path.abspath(file) in out_files: # 出力ファイルに含まれてないファイルを削除 os.remove(file)次に、出力結果の
git diff
を出すスクリプトですtest/diff.pyimport subprocess import os # Git で差分を表示 test_dir = os.path.dirname(__file__) subprocess.run(["git", "add", "-N", test_dir + "/out"]) subprocess.run(["git", "--no-pager", "diff", "--relative=test/out", "--ignore-space-change"])上記2つを合わせて実行します
test/test.pyimport importlib importlib.import_module('run') importlib.import_module('diff')以上の3つのスクリプトのうち
run.py
を少し改変すればテスト対象が Python でない場合でも使用できます。実行結果は以下のようになります
diff --git a/my_cos_test.txt b/my_cos_test.txt new file mode 100644 index 0000000..0f305ad --- /dev/null +++ b/my_cos_test.txt @@ -0,0 +1,50 @@ +ver:0.0 +my_math.cos()の戻り値に対するテスト +cos(-3.0 * PI): -1.0 +cos(-2.875 * PI): -0.9238795325112585 +cos(-2.75 * PI): -0.7071067829368665 +cos(-2.625 * PI): -0.3826834323659474 +cos(-2.5 * PI): 0.0 +cos(-2.375 * PI): 0.3826834323659474 +cos(-2.25 * PI): 0.7071067810719247 +cos(-2.125 * PI): 0.9238795325112585 +cos(-2.0 * PI): 1.0 +cos(-1.875 * PI): 0.9238795325112585 +cos(-1.75 * PI): 0.7071067829368671 +cos(-1.625 * PI): 0.3826834323659474 +cos(-1.5 * PI): -0.0 +cos(-1.375 * PI): -0.3826834323659474 +cos(-1.25 * PI): -0.7071067810719247 +cos(-1.125 * PI): -0.9238795325112586 +cos(-1.0 * PI): -1.0 +cos(-0.875 * PI): -0.9238795325112586 +cos(-0.75 * PI): -0.7071067829368671 +cos(-0.625 * PI): -0.3826834323659474 +cos(-0.5 * PI): 0.0 +cos(-0.375 * PI): 0.3826834323659474 +cos(-0.25 * PI): 0.7071067810719247 +cos(-0.125 * PI): 0.9238795325112585 +cos(0.0 * PI): 1.0 +cos(0.125 * PI): 0.9238795325112586 +cos(0.25 * PI): 0.7071067829368671 +cos(0.375 * PI): 0.38268343236594693 +cos(0.5 * PI): -0.0 +cos(0.625 * PI): -0.38268343236594693 +cos(0.75 * PI): -0.7071067810719247 +cos(0.875 * PI): -0.9238795325112586 +cos(1.0 * PI): -1.0 +cos(1.125 * PI): -0.9238795325112586 +cos(1.25 * PI): -0.7071067829368671 +cos(1.375 * PI): -0.3826834323659474 +cos(1.5 * PI): 0.0 +cos(1.625 * PI): 0.3826834323659474 +cos(1.75 * PI): 0.7071067810719247 +cos(1.875 * PI): 0.9238795325112585 +cos(2.0 * PI): 1.0 +cos(2.125 * PI): 0.9238795325112585 +cos(2.25 * PI): 0.7071067829368671 +cos(2.375 * PI): 0.3826834323659474 +cos(2.5 * PI): -0.0 +cos(2.625 * PI): -0.3826834323659474 +cos(2.75 * PI): -0.7071067829368665 +cos(2.875 * PI): -0.9238795325112585出てきた差分が意図通りならOKです。
達成できたこと
これだけではテストとして、不十分に見えますか?
しかし、少なくともテストに求められる重要なことのいくつかは達成できています
- プログラムの挙動の可視化
- プログラムが最後まで動くことのチェック
- デグレード(退化)を起こしていないことのチェック
特に重要なのが3つ目です。Git 差分の表示で変更行がないことを確認すれば挙動が変わってないことのチェックができます。
また、不安に思う部分には assert も書いていけば、いわゆるテストファースト的な手法を複合できます。
テストを書く際のポイント
よく「テストはドキュメント」といった言い回しを耳にしますが、この手法の場合は出力もドキュメントです。
ですから、以下のことを意識して書くといいです
- テストのファイル名に、より良く内容を表現した名前を付ける
- 出力ファイルの冒頭にテスト内容の説明文を書く
- テストのプログラムから出力される各行にはそれが何かわかるようなラベルを書く
CIを行う
CI(継続的インテグレーション)を行うとより快適に開発できるでしょう。
具体的には、プルリクエストをトリガーとして、自動で以下の2つを行うといいです
test/run.py
を実行して差分をコミットする- 出力に意図しない差分があったら failure させる
test/run.py
を実行して差分をコミットするこれを行うことで、テストを実行し忘れた状態でマージされることを防げます。
さらに
- 終了コードが
0
であること- 標準エラー出力がされていないこと
を見てテストが正常に終了したかを判定するといいです。
出力に意図しない差分があったら failure させる
プルリクエスト内容の差分を見るだけであれば以下のコマンドを実行すればいいです
git diff --no-pager マージ先ブランチ(base)...リクエストしてるブランチ(head) --relative=test/out(
--ignore-space-change
をつけるべきかは、ソフトウェアの性質に応じて要検討)ただ、これだけでは変更が意図的かどうかの区別ができません。
変更が意図的なものかどうかを区別するためにつけてるのが、出力ファイルの先頭の
ver:0.0
のバージョン情報です。このバージョン番号は以下のルールに従って書き換えをします
- 出力ファイルの変更が、行の追加のみであることを意図しているなら、マイナーバージョンを上げる。(
0.0
->0.1
)- 出力ファイルの変更が、行の削除を含むことを意図しているなら、メジャーバージョンを上げる。(
0.1
->1.0
)diff を読んで上記のルールを満たしているか判定するプログラムを書けば、マージの可否を判定する CI を組むことができます。
この判定を行うプログラムは以下に公開しています
https://github.com/shohei909/diff_test/blob/master/test/check.py
テスト対象を広げる
実践例で紹介したのは、いわゆる単体テスト的なものですが、より結合テスト的なものに対しても効果的です。
例えば、以下のようなものです
- JSON返すWebサーバーなら、実際にアクセスしたレスポンスを記録する (JSONは
jq . -sort-keys
などで内容のソートをしておくといい)- コマンドラインツールなら、実際にそのツールを実行した結果を記録する
- Webサイトなら、 Headless Chrome でそのページにアクセスした結果のスクリーンショット画像などを記録する
差分を見るテストのメリット
デグレードに対する耐性が高い
この手法はデグレードを発見する力高いです。この点については、あらかじめ期待される結果を手で書く手法と比べても強力です。
というのも、期待される結果を手で書く場合、「手で書ける程度の個数」のケースしか用意できません。網羅的にデグレードをチェックできるほどのケースを書いて用意するのは時間がかかります。
しかし、単に結果を出力しておくのであれば、「目を通せる程度の個数」にまで広がります。
プロトタイピングや仕様変更の速度を落とさない
テストを書く大きなメリットは、リファクタリングが容易になることです。リファクタリング時のデグレードのリスクが抑えられます。
逆に、テストを大量に書くデメリットもあります
- ソフトウェアのプロトタイプが実際に動くまでに時間がかかる
- 仕様変更が発生したときのテストの書き直しが大変になる
単にテキストを出力しておく手法はテストコードは比較的小さく、これらのデメリットが小さいです。
仕様の変更内容によっては、テストコードに対する変更は単に
ver:*.*
を書き換えるだけで済みます。それでいて、プログラムの挙動がどう変わったかは出力の差分を見れば明確です。このテスト手法は、すばやくプロトタイプを作って、その挙動や反省を踏まえてリファクタリングや仕様の改善を繰り返していくような開発フローとの相性がいいです。
挙動が可視化される & 挙動の履歴をたどれるようになる
この手法でテストを書くということは、サンプルコードとその実行結果を残しておくということそのものです。これらがあれば、レビュアーも、新規メンバーも、実装者自身もコードがどういう動きをするのか理解しやすくなります。
また、 Git 上に挙動ベースでの変更履歴が残ることにもメリットがあります。この履歴は、例えば、後からバグが発見された場合に、その原因になった挙動がいつから発生していたか、どの修正によって発生かを調べる手助けになります。
テストフレームワークにロックインされない
テストコードはコード量が多くなりがちな部分なので、例えばテストフレームワークが言語のアップデートに追随してないといった事態には対処が大変です。
この手法では、テストフレームワークのへの依存が無いのでそのリスクがありません。
発展:SVGを出力して差分を見る
テキストの出力はより挙動をわかりやすく表現する形で行うのが理想です。より視覚的に表現する方法として SVG を出力するというのがあります。
先ほどの
my_math.cos()
でそれをやってみたのが以下です。https://github.com/shohei909/diff_test/pull/1/files#diff-859f44ef4608ed7f4557a2bfcb2ce991
ちゃんとコサインの近似ができていることが視覚的にわかります。
SVG はテキストと画像の両面の性質があるので、両面のメリット受けられます。例えば、値が変わったかを厳密に見たければテキストで見ればいいですし、目視でわかる変化があったかを見たければ画像としてみればいいです。
試しに、
my_math.cos()
の精度を落とす変更を加えてみます誤差が目視ではわからないレベルであることが確認できました。
- 投稿日:2020-07-25T16:34:12+09:00
gitコマンド初心者覚書
git初心者、すぐに忘れるので覚書
- githubで + New repositoryで、パブリックで nodeを選び、 できたリポジトリをローカルにクローンする。
$ yarn global add create-react-app $ git checkout -b hello-world $ create-react-app ファイル名 $ git clone https://github.com/----/----.git//githubに新しいリポジトリを作り、ローカルにgit clone する $ cd 作りたいディレクトリ //移動 $ git status // git add されているけどまだ git commit されていないファイルの一覧 編集・変更・削除されているが、まだ git add されていないファイルの一覧 Git管理されていない、かつ .gitignore で管理除外対象にもされていないものの一覧 $ git add .gitignore //管理外にする $ git add //変更内容をインデックスに追加してコミット対象にする $ git diff $ git checkout -b hello-world //ブランチを作る $ git commit //コミットする
- 投稿日:2020-07-25T12:06:43+09:00
【学習メモ】※初心者向け EC2へ誤ってgit cloneした時の対応
解決したいこと
EC2にrubyをインストールするため「rbenv」をgit cloneしてパスを通すはずだったが、「-bash: rbenv: コマンドが見つかりません」と怒られていました。
環境
- EC2インスタンス
- Amazon Linux2
- t2.micro
- Git:git version 2.23.3
原因
そもそも、git cloneするものを間違えていた。git cloneするものが、自分がデプロイするプロジェクトと勘違いしていました。
解決策
ディレクトリを削除し追加!ここからは私の思考過程をご覧ください。
[ec2-user@ip-10-0-0-235 ~]$ ls portfolio_development # これを消したい。 [ec2-user@ip-10-0-0-235 ~]$ rm portfolio_development rm: `portfolio_development' を削除できません: Is a directory # なぬ。 [ec2-user@ip-10-0-0-235 ~]$ rm -rf portfolio_development/ # ディレクトリごと消したい場合は、「-rf」なのね。 [ec2-user@ip-10-0-0-235 ~]$ ls # 消したことを確認。 [ec2-user@ip-10-0-0-235 ~]$ git status # git statusでgit管理下にあるファイル・ディレクトリを確認したが、結構ファイル残ってるな。 On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .bash_history .bash_logout .bash_profil .bash_profile .bashrc .cache/ .rbenv/ .ssh/ .viminfo nothing added to commit but untracked files present (use "git add" to track) [ec2-user@ip-10-0-0-235 ~]$ git clean -f # 強制的に消去! Removing .bash_history Removing .bash_logout Removing .bash_profil Removing .bash_profile Removing .bashrc Removing .viminfo [ec2-user@ip-10-0-0-235 ~]$ git status # 再度確認。しかしディレクトリが残っている。 On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .cache/ .rbenv/ .ssh/ nothing added to commit but untracked files present (use "git add" to track) [ec2-user@ip-10-0-0-235 ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv fatal: destination path '/home/ec2-user/.rbenv' already exists and is not an empty directory. # 当然空のディレクトリじゃないので怒られる。 [ec2-user@ip-10-0-0-235 ~]$ git clean -df Removing .cache/ Skipping repository .rbenv/ Removing .ssh/ [ec2-user@ip-10-0-0-235 ~]$ git status # 最後にこれが残っていたか、、、 On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .rbenv/ nothing added to commit but untracked files present (use "git add" to track) [ec2-user@ip-10-0-0-235 ~]$ git clean .rbenv/ # これで消せるか? fatal: clean.requireForce defaults to true and neither -i, -n, nor -f given; refusing to clean # だめか。消したいなら強制的に消すコマンド使えよって。 [ec2-user@ip-10-0-0-235 ~]$ git clean -f .rbenv/ Removing .rbenv/.DS_Store Removing .rbenv/.browserslistrc Removing .rbenv/.gitignore Removing .rbenv/.ruby-version Removing .rbenv/Gemfile Removing .rbenv/Gemfile.lock Removing .rbenv/README.md Removing .rbenv/Rakefile Removing .rbenv/babel.config.js Removing .rbenv/config.ru Removing .rbenv/ja.js Removing .rbenv/package.json Removing .rbenv/postcss.config.js Removing .rbenv/yarn.lock # やったか・・・? [ec2-user@ip-10-0-0-235 ~]$ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .rbenv/ nothing added to commit but untracked files present (use "git add" to track) # なぜまだ生きている(残っている)・・・?! [ec2-user@ip-10-0-0-235 ~]$ git clean -n # ステージングでないファイル・ディレクトリをこのコマンドで一掃。 [ec2-user@ip-10-0-0-235 ~]$ git init # 初期化してみる。 Reinitialized existing Git repository in /home/ec2-user/.git/ [ec2-user@ip-10-0-0-235 ~]$ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .rbenv/ nothing added to commit but untracked files present (use "git add" to track) # なん・・・だと?! [ec2-user@ip-10-0-0-235 ~]$ git clean -dfn Would skip repository .rbenv/ # 消去したいファイル・ディレクトリを確認 [ec2-user@ip-10-0-0-235 ~]$ git clean -f # ステージングでないファイル・ディレクトリをこのコマンドで一掃。 [ec2-user@ip-10-0-0-235 ~]$ git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .rbenv/ nothing added to commit but untracked files present (use "git add" to track) [ec2-user@ip-10-0-0-235 ~]$ rm -rf ~/.rbenv # ディレクトリを指定し強制消去。 [ec2-user@ip-10-0-0-235 ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv Cloning into '/home/ec2-user/.rbenv'... remote: Enumerating objects: 5, done. remote: Counting objects: 100% (5/5), done. remote: Compressing objects: 100% (5/5), done. remote: Total 2852 (delta 0), reused 1 (delta 0), pack-reused 2847 Receiving objects: 100% (2852/2852), 550.44 KiB | 782.00 KiB/s, done. Resolving deltas: 100% (1781/1781), done. [ec2-user@ip-10-0-0-235 ~]$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile [ec2-user@ip-10-0-0-235 ~]$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile [ec2-user@ip-10-0-0-235 ~]$ source .bash_profile [ec2-user@ip-10-0-0-235 ~]$ rbenv -v rbenv 1.1.2-30-gc879cb0 # 無事に完了!感想
ちょっと疲れました。EC2にGitを入れても、基本的な考えは全く変わらないんだと痛感。以前もGitの勉強で、「ディレクトリを削除する」だけなのに1時間くらいかかった時がありました。でも今回それが(色々忘れてましたのでググりながらやりました)15分で終わったので成長を感じました。もっとスピード上げて頑張ります?
あとは、いきなり「EC2 Git エラー内容」で調べるのではなく、「そもそもGitコマンド使えてるー?」ってところから「Git ディレクトリ 削除」と調べてやってみると案外すんなり行きました。これが「問題の切り分け」「段階ごとの調査」ってことかな?と思いました。
参考記事
Gitのrmコマンドの使い方【画面キャプチャでわかりやすく】
Git clone についてなのですが、1回目不具合で作業を中止して、もう一度やり直しを...
[-bash: rbenv: コマンドが見つかりません]aws(ec2)上のrbenvの初期設定エラーの解決方法
- 投稿日:2020-07-25T11:08:16+09:00
Git,GitHubの基礎 (コマンド)
Gitの基礎復習のため作成
すべて該当ディレクトリで実行
ローカルリポジトリを作成
ターミナル$ git init #隠しディレクトリである.gitが作成され、Gitで管理できるようになる。隠しディレクトリが存在しているか確認
ターミナル$ ls .gitインデックスに追加
ターミナル$ git add "ファイル名" #※実行の際は上記 "" は不要インデックスに追加されている変更修正を確認
ターミナル$ git statusコミット
ターミナル$ git commit -m 'first commit' #'first commit'というタイトルを付けてコミットするログの確認
ターミナル$ git log #コミットの履歴を表示するための操作ローカルリポジトリをリモートリポジトリに紐付け
ターミナル$ git remote add origin "紐付けたいリモートリポジトリで発行されているURL" #※実行の際は上記 "" は不要リモートリポジトリ紐づけ完了確認
ターミナル$ git remoteローカルリポジトリをリモートリポジトリに反映させる
ターミナル$ git push origin master #pushした際にユーザー名とパスワードの入力を求められたら、下記実行。 #ユーザー名求められたら Username for 'https://github.com' : #ここにGitHubのusernameを入力 #パスワード求められたら Password for 'https://github.com' : #ここにGitHubのpasswordを入力gitのローカルリポジトリを削除
ターミナル$ rm -rf .git #レポジトリ名を指定して削除 $ rm -rf "レポジトリ名" #※実行の際は上記 "" は不要違っていたらコメントなどいただけると幸いです。
- 投稿日:2020-07-25T05:19:28+09:00
Githubプライベートレポジトリでgit cloneとgit remote addを行う時のURL
※ssh部分は省略
tldr;
URLのgithub.comの前にユーザー名+@をつける
git clone(ユーザー名 = user01の場合)
git clone https://user01@github.com/xxxx/xxxx.gitgit remote add(ユーザー名 = user01の場合)
git remote add origin https://user01@github.com/xxxx/xxxx.git誤ってプライベートリポジトリをパブリックリポジトリの形式でremote add originしてしまった時の対処法
1. リモートリポジトリのURLを確認
git remote -v以下の結果が出力されることを確認
origin https://user01@github.com/xxxx/xxxx.git (fetch) origin https://user01@github.com/xxxx/xxxx.git (push)2. 既存のリモートリポジトリを削除
git remote rm origin; git remote remote -v # 何も表示されないことを確認3. 正しいURLでremote addし直す(ユーザー名 = user01の場合)
git remote add origin https://user01@github.com/xxxx/xxxx.git
- 投稿日:2020-07-25T01:57:37+09:00
GitHubってなに?
1. はじめに
プログラミングを始めたての人ならすぐに見かける「GitHub」というワード。
ただでさえアルファベット多くて大変なのに、急に出てきたこいつ何だ?と思った方もいるのではないでしょうか?
今回は「GitHubってなに?」というのを極力簡単にお伝えします。
Gitとは
Gitとはプログラムを管理する分散型の管理システムのこと。
....?
うん、「分散型」というよくわからん言葉はいったん置いときましょう。
とにかく基本はプログラム(書いた文字でコンピューターが動くもの)を管理できる仕組みの事です。
システム=「もの」
システムも一つの「もの」です。
なので作るためには細かい部品(つまり機能だったり見た目だったり)が合わさって出来上がります。車を作るためにも、タイヤ、エンジン、ハンドル、車体などいろいろ必要ですよね。
そしてそれぞれを作る人がいます。Gitによってそれが誰がどの部分を作っているのかが分かります。
システムは実体がない
ここまでは現実世界で物を作るのと変わらないですが、システムは実体がありません。
なので、プログラムの変更履歴を見たり、戻ったりすることができます。どういうことかというと、車の部品は接続部が曲がれば、うまく組み立てができないし、塗装が間違った色にしてしまうとその車は台無しになります。
しかしシステムは実体がないため、接続部が間違っていたら修正できるし、色が違えば変えられる、つまり再修正できる特徴があります。この特徴もGitで活かされています。
「分散型」であること
ここでもう一つの大きな特徴が、先ほど保留してた「分散型」であること。
プログラムは2つの空間で保存ができます。
それがローカルリポジトリとリモートリポジトリ、いわゆるプログラム用の「倉庫」です。
ローカルリポジトリ=自分だけの倉庫、リモートリポジトリ=みんなの倉庫と捉えてください。昔は実は「集中型」といって、みんなの倉庫から部品を取り出して作ってみんなの倉庫に戻す作業を行っていました。
しかしそれではみんなの倉庫にいける環境(サーバーにアクセスできる状態)でないと作業が進められないデメリットがありました。そこで自分だけの倉庫を持つ事でそこから作業を進めることができるし、動作確認を行ってからみんなの倉庫に収納することができるようになりました。
これも実体がないものなのでコピーが自由できるからこそ可能な特徴です。
それで「GitHub」って?
ここまででGitという仕組みの意味はなんとなくわかったと思います。
戻りますが、GitHubはそのGitという仕組みの中の一つのサービスです。
(どのスケジュールアプリ使うかとか、どのメモアプリが最適かというの一緒です。)まとめ
システム開発をより便利に、円滑に、かつミスなく進めるためのツールです。
ゲームで言うとセーブポイントですね(←伝わる?)
システム開発は基本チームで作ることがほとんどです。
しっかりGitの使い方は学んでおきましょう!