20200725のGitに関する記事は9件です。

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git コンフリクトの解消

コンフリクトとは

AとBの作業ブランチがあるとする。
まずAがリモートリポジトリにプッシュされ、マージされる。
次にBがリモートリポジトリにプッシュされる。
このとき、 AとBで変更内容に対立が起こった場合、どちらを採用すればようのか分からなくなる。
この対立をコンフリクトという。

コンフリクト発生

GitHubにプッシュした際に以下の表示が出たらコンフリクトが起きているということ。
コンフリクト発生.png

解消の流れ

1.ローカルでmasterブランチに切り替える
2.リモートのmasterブランチをプルする
3.作業ブランチに切り替える
4.masterブランチをマージする
5.コンフリクトが発生している部分を修正する
6.リモートにプッシュする

1.ローカルでmasterブランチに切り替える

$ git checkout master

2.リモートのmasterブランチをプルする

$ git pull origin master

3.作業ブランチに切り替える

$ git checkout B_branch

4.masterブランチをマージする

$ git merge master

5.コンフリクトが発生している部分を修正する

コンフリクト発生部分.png

<<<<<<< HEAD
ここが作業ブランチの変更内容

=======

ここがmasterの変更内容
.>>>>>>> master

今回は両方の変更を取り込むことにします。

6.リモートにプッシュする

$ git add .
$ git commit -m "コンフリクト解消内容"
$ git push origin HEAD

コンフリクト解消.png

これでコンフリクトが解消できました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

単にテキストを出力して差分を見るというテスト手法

この記事ではどこでも使えて強力なテスト手法を紹介します

  • テストフレームワークを使いません。
  • テキストが出力できれば、どの言語でも使えます。

手順

  1. プログラムを Git 管理する
  2. 結果をテキストでファイル出力するプログラム書く(出力結果は .gitignore しない)
  3. 出力ディレクトリについて git diff を表示する

以上

実践

テスト対象のコードとして、コサインの近似値の計算を Python で書きました。

my_math.py
import 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 result

1. Git 管理する

サンプルの Git レポジトリは
https://github.com/shohei909/diff_test
に用意しました

2. テキストを標準出力する

これに対して以下のようなテストコードを書きます。単に -3 * PI から 3 * PI までの my_math.cos() の計算結果を標準出力しています

test/code/my_cos_test.py
import 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.py
import 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.py
import 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.py
import 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つを行うといいです

  1. test/run.py を実行して差分をコミットする
  2. 出力に意図しない差分があったら 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

cos.png

ちゃんとコサインの近似ができていることが視覚的にわかります。

SVG はテキストと画像の両面の性質があるので、両面のメリット受けられます。例えば、値が変わったかを厳密に見たければテキストで見ればいいですし、目視でわかる変化があったかを見たければ画像としてみればいいです。

試しに、 my_math.cos() の精度を落とす変更を加えてみます

https://github.com/shohei909/diff_test/pull/2/files?short_path=859f44e#diff-859f44ef4608ed7f4557a2bfcb2ce991

page6_15.gif

誤差が目視ではわからないレベルであることが確認できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

単にテキストを出力して差分を見るというテスト手法のススメ

この記事ではどこでも使えて強力なテスト手法を紹介します

  • テストフレームワークを使いません。
  • テキストが出力できれば、どの言語でも使えます。

手順

  1. プログラムを Git 管理する
  2. 結果をテキストでファイル出力するプログラム書く(出力結果は .gitignore しない)
  3. 出力ディレクトリについて git diff を表示する

以上

実践

テスト対象のコードとして、コサインの近似値の計算を Python で書きました。

my_math.py
import 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 result

1. Git 管理する

サンプルの Git レポジトリは
https://github.com/shohei909/diff_test
に用意しました

2. テキストを標準出力する

これに対して以下のようなテストコードを書きます。単に -3 * PI から 3 * PI までの my_math.cos() の計算結果を標準出力しています

test/code/my_cos_test.py
import 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.py
import 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.py
import 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.py
import 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つを行うといいです

  1. test/run.py を実行して差分をコミットする
  2. 出力に意図しない差分があったら 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

cos.png

ちゃんとコサインの近似ができていることが視覚的にわかります。

SVG はテキストと画像の両面の性質があるので、両面のメリット受けられます。例えば、値が変わったかを厳密に見たければテキストで見ればいいですし、目視でわかる変化があったかを見たければ画像としてみればいいです。

試しに、 my_math.cos() の精度を落とす変更を加えてみます

https://github.com/shohei909/diff_test/pull/2/files?short_path=859f44e#diff-859f44ef4608ed7f4557a2bfcb2ce991

page6_15.gif

誤差が目視ではわからないレベルであることが確認できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 //コミットする
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【学習メモ】※初心者向け 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の初期設定エラーの解決方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 "レポジトリ名"

#※実行の際は上記 "" は不要

違っていたらコメントなどいただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Githubプライベートレポジトリでgit cloneとgit remote addを行う時のURL

※ssh部分は省略

tldr;

URLのgithub.comの前にユーザー名+@をつける

git clone(ユーザー名 = user01の場合)

git clone https://user01@github.com/xxxx/xxxx.git

git 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubってなに?

1. はじめに

プログラミングを始めたての人ならすぐに見かける「GitHub」というワード。

ただでさえアルファベット多くて大変なのに、急に出てきたこいつ何だ?と思った方もいるのではないでしょうか?

今回は「GitHubってなに?」というのを極力簡単にお伝えします。

Gitとは

Gitとはプログラムを管理する分散型の管理システムのこと。

....?

うん、「分散型」というよくわからん言葉はいったん置いときましょう。

とにかく基本はプログラム(書いた文字でコンピューターが動くもの)を管理できる仕組みの事です。

システム=「もの」

システムも一つの「もの」です。
なので作るためには細かい部品(つまり機能だったり見た目だったり)が合わさって出来上がります。

車を作るためにも、タイヤ、エンジン、ハンドル、車体などいろいろ必要ですよね。
そしてそれぞれを作る人がいます。

Gitによってそれが誰がどの部分を作っているのかが分かります。

システムは実体がない

ここまでは現実世界で物を作るのと変わらないですが、システムは実体がありません。
なので、プログラムの変更履歴を見たり、戻ったりすることができます。

どういうことかというと、車の部品は接続部が曲がれば、うまく組み立てができないし、塗装が間違った色にしてしまうとその車は台無しになります。
しかしシステムは実体がないため、接続部が間違っていたら修正できるし、色が違えば変えられる、つまり再修正できる特徴があります。

この特徴もGitで活かされています。

「分散型」であること

ここでもう一つの大きな特徴が、先ほど保留してた「分散型」であること。

プログラムは2つの空間で保存ができます。
それがローカルリポジトリとリモートリポジトリ、いわゆるプログラム用の「倉庫」です。
ローカルリポジトリ=自分だけの倉庫、リモートリポジトリ=みんなの倉庫と捉えてください。

昔は実は「集中型」といって、みんなの倉庫から部品を取り出して作ってみんなの倉庫に戻す作業を行っていました。
しかしそれではみんなの倉庫にいける環境(サーバーにアクセスできる状態)でないと作業が進められないデメリットがありました。

そこで自分だけの倉庫を持つ事でそこから作業を進めることができるし、動作確認を行ってからみんなの倉庫に収納することができるようになりました。

これも実体がないものなのでコピーが自由できるからこそ可能な特徴です。

それで「GitHub」って?

ここまででGitという仕組みの意味はなんとなくわかったと思います。

戻りますが、GitHubはそのGitという仕組みの中の一つのサービスです。
(どのスケジュールアプリ使うかとか、どのメモアプリが最適かというの一緒です。)

まとめ

システム開発をより便利に、円滑に、かつミスなく進めるためのツールです。
ゲームで言うとセーブポイントですね(←伝わる?)
システム開発は基本チームで作ることがほとんどです。
しっかりGitの使い方は学んでおきましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む