ソフトウェアの品質を学びまくる

ソフトウェアの品質、ソフトウェアテストなどについて学んだことを記録するブログです。

にしさんに贈る言葉

 にしさんが亡くなった。

 にしさんは、メチャクチャな人であった。面白そうなこと、ワクワクすること、ソフトウェアテスト・品質の分野の発展につながりそうなことなら、何でもやっていた。
 日本のどこにでも、世界のどこにでも行っていた。そこでしゃべっていた。動いていた。
 尊敬する先達はたくさんいるけれど、その中でも「やりたいことがありすぎて、楽しくてしかたない」という光に溢れている人だった。

 にしさんは、ソフトウェアテスト・品質の進むべき道を切り開く先導者であり、扇動者であった。
 ソクラテスのいうシビレエイのような存在であった。
 ビジョナリーというとカッコいいけれど、ビジョンを示すだけでなく、自分でも動き、他人も動かせる人だった。

 そんな人を、わたしたちは突然、永遠に失ってしまった。
 誰も後を継げないかもしれない。あんなメチャクチャな人を、きれいに引き継げる者なんて、そうそういるはずもない。

 でも、にしさんのやっていたこと、やろうとしていたことを、残ったみんなで少しずつ受け持って、みんなで前に進むことならできるかもしれない。
 自分もそのうちの一人になりたいと思っている。
 にしさんが作ってくれた道、教えてくれたことを糧にして、みんなで未来を見たいと思っている。

 悲しい。悲しい。
 あなたに、「死」は似合わないんですよ、ちっとも。

 けど、「しょうがない」。
 そちらにも、おいしいスイーツがあるといいですね。
 ありがとうございました。ゆっくり休んでください。

www.aster.or.jp

猿とシェイクスピアと失敗とChatGPT

 「猿とシェイクスピア」の話は、いろんな文脈で出てくるアナロジーです。

  • 猿がランダムにキーボードをたたき続けると、いずれはシェイクスピアの戯曲と同じテキストが生成される。
     あるいはその逆に、
  • 猿がランダムにキーボードをたたき続けてシェイクスピアの戯曲を生成するには、宇宙の寿命があっても足りない

といったように言及されます*1

 一方『失敗の科学』では、この話に別の観点を与えています。

失敗の科学

失敗の科学

Amazon

 アルファベットと空白からなる27種類の文字からランダムに生成した28文字の文字列が、シェイクスピアの一節28文字と一致する確率は、わずか10の40乗分の1。
 しかし、生成した文章をチェックし、目標の一節に少しでも近いものを選択する。その文字列にランダムな変化を加えて次世代とし、これを繰り返すと、43世代目で目標に到達した、という実験があるそうです。これを「累積淘汰のメカニズム」と呼んでいます。

 「小さく失敗し、その失敗から学んで改善することで、成功に近づくことができる」「成功のためには失敗が必要である」という主張を、「猿とシェイクスピア」のアナロジーで説明しているわけです。お察しの通りこの話は、リーン開発、アジャイル開発の話にもつながっています。
 「確率がどれだけ低くても、決してゼロではない」か、「確率が低すぎれば、それはゼロと変わりない」のアナロジーとしてしか聞いたことがなかったため、とても新鮮な発想でした。

ChatGPTで、累積淘汰のメカニズムを実装してみる

 さて、ここからは話が変わります。この「累積淘汰のメカニズム」、実感してみたい!

  だけどぼくにはプログラミング能力がない

  きみに聴かせる腕もない

DREAM PRICE 1000 西田敏行 もしもピアノが弾けたなら

DREAM PRICE 1000 西田敏行 もしもピアノが弾けたなら

Amazon

 ので、ChatGPTの練習を兼ねて、このメカニズムを実験するコードを書いてみることにしました!
 もっといいプロンプトあるよ! こういうコードの方がいいよ! というか意図したコードになっていないよ!などあれば、教えてください。

スコアリングの仕組みを考える

生成した文章をチェックし、目標の一節に少しでも近いものを選択

 まずこの部分。2つの文字列がどのくらい一致しているかをスコアリングするかを考えます。

 そこで思い出すのが、日本でも流行したWordle。
 あのゲームでは、「文字も位置も一致していれば緑、文字は合っているが位置は合っていない場合は黄色」で表示されましたね。あの色を見れば、どのくらい目標に近づいているかがわかるわけです。HIT、BLOWとも呼ばれるものです。

www.nytimes.com

 このコンセプトを、スコアリングのルールに使います。
 指定したプロンプトは以下です。ルール4を自然言語で書くのが難しかったですが、普通に通じていました。

見本となる文字列Sに対し、与えられた別の文字列xがどのくらい一致しているか点数をつけるPythonのコードを書いてください。ルール*2は以下の通りです。

  • ルール1. Sとxは、大文字アルファベット26種類の文字から構成される。
  • ルール2. Sとxは、ともに10文字である。
  • ルール3. xに指定した10文字それぞれについて、文字も位置もSと一致している場合は、1つにつき5点を与える。
  • ルール4. xに指定した10文字それぞれについて、位置は一致しないが同じ文字がSにも含まれている場合は、1つにつき2点を与える。
  • ルール5. ルール3とルール4の点数を合計した数値が、文字列xと点数となる。

 生成されたコードは、以下。
 特に問題なさそうです。ルール3とルール4で2重計上していることもなさそう。一番最後のコメントの足し算を間違っているけど・・・。

def calculate_score(S, x):
    # 初期スコアを0に設定
    score = 0
    
    # ルール3に基づいてスコアを計算
    for i in range(10):
        if S[i] == x[i]:
            score += 5
    
    # ルール4に基づいてスコアを計算
    for i in range(10):
        if x[i] in S and S[i] != x[i]:
            score += 2
    
    return score

# 例
S = "ABCDEFGHIJ"
x = "ABCDWXYZIJ"
print(calculate_score(S, x))  # この例では、スコアは5*6 + 2*2 = 32 となります。

 なんとなく関数名が気に食わなかったので、別の名前を10個提案してもらい、rate_string_similarity に変更しました。

文字列にランダムな変化を加える

その文字列にランダムな変化を加えて次世代とし

 これについて、以下のようなプロンプトを指定しました。

では次のように、文字列中の文字をランダムに置き換える関数をpythonで作成してください。 ルールは以下の通りです。

  • ルール1: 与えられた文字列xのすべての文字をランダムに置換する。
  • ルール2: 置換後の文字は、大文字アルファベット26文字から選ぶ。
  • ルール3: 置換後の文字が、置換前の文字で合ってもよい。

生成されたコードは、以下の通りです。
わたしは .join(random.choice(alphabet) for _ in x) みたいな書き方はできないですね。。

import random

def random_replace(x):
    # 大文字アルファベット26文字を定義
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    
    # 文字列xの各文字をランダムに置換
    replaced = ''.join(random.choice(alphabet) for _ in x)
    
    return replaced

### 例
x = "HELLOWORLD"
print(random_replace(x))

ある文字列と、ランダム置換後の文字列のスコアを比較する

目標の一節に少しでも近いものを選択する。

 使いたい関数はもうできているので、組み合わせるだけです。プロンプトは以下。

ある文字列と、それをランダムに置換した文字列のスコアを比較するPythonのコードを書いてください。
ルールは以下の通りです。

  • ルール1: 文字列xを受け取る。
  • ルール2: 文字列xを、関数random_replaceで置換する。これを文字列yと呼ぶ。
  • ルール3: 文字列xと文字列Sの類似度を、関数rate_string_similarityで点数化する。
  • ルール4: 文字列yと文字列Sの類似度を、関数rate_string_similarityで点数化する。
  • ルール5: 点数が高かった方の文字列を返す。

 生成されたコードは以下。
 「返す」と書くと、print で表現されるのか。

import random

def random_replace(x):
    # 大文字アルファベット26文字を定義
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    
    # 文字列xの各文字をランダムに置換
    replaced = ''.join(random.choice(alphabet) for _ in x)
    
    return replaced

def rate_string_similarity(S, x):
    # 初期スコアを0に設定
    score = 0
    
    # ルール3に基づいてスコアを計算
    for i in range(10):
        if S[i] == x[i]:
            score += 5
    
    # ルール4に基づいてスコアを計算
    for i in range(10):
        if x[i] in S and S[i] != x[i]:
            score += 2
    
    return score

def compare_strings(S, x):
    # ルール2に基づいて文字列xをランダムに置換
    y = random_replace(x)
    
    # ルール3とルール4に基づいてスコアを計算
    score_x = rate_string_similarity(S, x)
    score_y = rate_string_similarity(S, y)
    
    # ルール5に基づいて点数が高かった方の文字列を返す
    return x if score_x >= score_y else y

# 例
S = "ABCDEFGHIJ"
x = "ABCDWXYZIJ"
print(compare_strings(S, x))

試行を繰り返させる

 文字列をランダムに変化させ、変化前と後を比較して、スコアの高い方を残すことができました。これを繰り返していきましょう。
 プロンプトは以下*3

ランダムに指定した文字列に対して置換を繰り返すことで、目的とする文字列に近づけるかどうかを確認します。 次のような操作を繰り返してください。

  • 手順1: 目的とする文字列Sを "ABCDEFJHIJ" とする。
  • 手順2: 初期文字列xをランダムに指定する。xは大文字アルファベット26種類からなる、10文字の文字列である。
  • 手順3: 文字列xに対し、関数compare_stringsを適用し、よりスコアの高い文字列xと、その時のスコアを出力する。
  • 手順4: 以下のいずれかの条件を満たすまで、手順3を繰り返す。
      条件1: xがSに一致する。
     
    条件2: 手順3の実行回数が100回を超える。

 生成されたコードは以下。

import random

def random_replace(x):
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    replaced = ''.join(random.choice(alphabet) for _ in x)
    return replaced

def rate_string_similarity(S, x):
    score = 0
    for i in range(10):
        if S[i] == x[i]:
            score += 5
    for i in range(10):
        if x[i] in S and S[i] != x[i]:
            score += 2
    return score

def compare_strings(S, x):
    y = random_replace(x)
    score_x = rate_string_similarity(S, x)
    score_y = rate_string_similarity(S, y)
    return (x, score_x) if score_x >= score_y else (y, score_y)

def main():
    # 手順1
    S = "ABCDEFJHIJ"
    
    # 手順2
    x = ''.join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") for _ in range(10))
    
    iteration = 0
    while True:
        # 手順3
        x, score = compare_strings(S, x)
        print(f"Iteration {iteration + 1}: x = {x}, Score = {score}")
        
        # 手順4
        iteration += 1
        if x == S or iteration > 100:
            break

main()

 でけた!

しかしうまくいかない・・・

 このコードをそのままChatGPTで実行させることができそうなのですが、やり方を知らないのでVSCode上で実行してみました。

 その結果・・・100回試行で打ち切りになってしまう。
 最大試行回数を10,000にしても、

Iteration 10001: x = ACZLZAJJIJ, Score = 26

 全文字一致となる50点(位置文字一致5点×10文字)に遠く及ばないし、そもそも試行中に文字列がほとんど変化していない。

ChatGPTに頼り切る

 うーん、コードが悪いのか、そもそもアルゴリズムが間違っているのか・・・と悩むこと数秒。
 「ChatGPTに聞けばいいのか? いやでもそんなこと理解できる?」

 とりあえず聞いてみました。

実行したところ、100回繰り返しても文字列がほとんど変化しません。試行を繰り返すことで、スコアが徐々によくなっていくと思ったのですが、なぜこんなにも変わらないのでしょうか?

 すると・・・

問題の原因は、compare_strings関数内でrandom_replace関数を使用しているためです。random_replace関数は、文字列xのすべての文字をランダムに置き換えるため、毎回完全に新しいランダムな文字列が生成されます。そのため、前回の試行で得られたスコアが良かったとしても、次の試行ではそれを利用することなく、完全に新しいランダムな文字列が評価されることになります。

この問題を解決するためには、文字列xの一部の文字だけをランダムに置き換えるように変更する必要があります。これにより、前回の試行で得られた良い結果を部分的に維持しつつ、新しい試行でさらに良い結果を得ることが期待できます。

以下に、文字列xの一部の文字だけをランダムに置き換える修正を加えたコードを示します。

 ひええええ頭がよすぎるううう!というか自分の頭が悪すぎるううう!
 毎回ランダムなんだから、高いスコアが偶然出る可能性はどんどん下がっていくわけですね。なので、すべての文字ではなく、一部の文字だけを置換する必要があったわけです。

import random

def partial_random_replace(x):
    # 1〜3文字の位置をランダムに選択
    num_replacements = random.randint(1, 3)
    positions = random.sample(range(10), num_replacements)
    
    # 選択された位置の文字をランダムに置き換え
    x_list = list(x)
    alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    for pos in positions:
        x_list[pos] = random.choice(alphabet)
    
    return ''.join(x_list)

def rate_string_similarity(S, x):
    score = 0
    for i in range(10):
        if S[i] == x[i]:
            score += 5
    for i in range(10):
        if x[i] in S and S[i] != x[i]:
            score += 2
    return score

def compare_strings(S, x):
    y = partial_random_replace(x)
    score_x = rate_string_similarity(S, x)
    score_y = rate_string_similarity(S, y)
    return (x, score_x) if score_x >= score_y else (y, score_y)

def main():
    S = "ABCDEFJHIJ"
    x = ''.join(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") for _ in range(10))
    
    iteration = 0
    while True:
        x, score = compare_strings(S, x)
        print(f"Iteration {iteration + 1}: x = {x}, Score = {score}")
        
        iteration += 1
        if x == S or iteration > 100:
            break

main()

 新しく作られた関数 partial_random_replace では、まず何文字置換するかを決め、文字列中の置換位置と置換文字をランダムに決めて、置換しています。

 これを実行したところ、「VTRCOBETEU」からスタートした文字列が、731回目の試行で「ABCDEFJHIJ」に変化しました。

まとめ

 実装してほしい仕様を自然言語で書いて、ChatGPTにコードにしてもらい、実行してみて、うまくいかなかったら直してもらう、という過程は、とても楽しいです!

 以下、まとめです。全然関係のなさそうな主張2つになってしまった。

  • 小さい失敗を繰り返して改善・軌道修正していくことで、成功に近づく可能性が高くなる
  • 「これは言っても伝わらなくない?」ということでも、ChatGPTはけっこう理解してくれる

キーボードを叩く猿

おまけ

 目標文字列を「生きるべきか、・・・」にした場合の変化の過程は以下の通りです。

Iteration 2: x = BNKMOJKCHMMOITWILJ AJDOAZEWMIXOZYCSJMON, Score = 51
Iteration 4: x = BNKMOJKCHMMOITWILJ AHDOAZEWMIXOZYYSJMON, Score = 56
Iteration 7: x = BNKMOGKCHMMOITWILP AHDOAZEWMIXOZYRSJMON, Score = 58
Iteration 10: x = BNTMOGKEHMMOITWILP AHDOAZEWMIXOZYRSJMON, Score = 62
Iteration 11: x = BNTMOGKEHMMOITWILP AHDOAZE MIXOZYRSJMON, Score = 67
Iteration 16: x = BNTMOGKEHMMOITWILR AHDOAZE MIXOZYRSJMON, Score = 69
Iteration 21: x = TNTMOGKEHMMOITWILR AHDOAFE OIXOZYRSJMON, Score = 74
Iteration 33: x = TNTMOGKEHUMOITWILR AHDOAFE OIQOZYCSJMON, Score = 76
Iteration 39: x = TNTMOGKEHUMOITWILR AHDOAFE OIQ ZYCSJMON, Score = 79
Iteration 42: x = TNTMOGKEHUMOITEILR AHDOAFE OIQ ZYCSJMON, Score = 81
Iteration 45: x = TNTMOGKEHUMOITEILR AHDOAHE OIQ ZYCSJMON, Score = 83
Iteration 65: x = TNTMOGKEHVMOITEILR AHDOAHE OIQ ZYCSTMON, Score = 86
Iteration 67: x = TNTBOGKEHVMOITEILR AHDOAHE OIQ ZYCSTMON, Score = 91
Iteration 69: x = TNTBOGKEHVMOITEILR AH OAHR OIQ ZRCSTMON, Score = 95
Iteration 70: x = TNTBOUQEHVMOITEILR AH OZHR OIQ ZRCSTMON, Score = 97
Iteration 77: x = TNTBOUQEHVMOITEILR AH OZHR OIQ ZRNSTMON, Score = 99
Iteration 82: x = TNTBOUQEHVMOITEILR AH OOHR OIQ ZRNSTMON, Score = 101
Iteration 92: x = TNTBAOQEHVMOITEILR AH OOIR OIQ ZRNSTMON, Score = 104
Iteration 120: x = TNTBAOQEHSMOITEILR AH OOIR OIQ ZRNSTMON, Score = 106
Iteration 145: x = TFTBAOQEHSMOITEILR AH O IR OIQ ZRNSTMON, Score = 107
Iteration 177: x = TFTBAOQEHSMOITEIHR AH O IR OIQ ZRNSTMON, Score = 109
Iteration 196: x = TFTBEOQEHSMOITEIHR AH O IR OIQ ZRNSTMON, Score = 112
Iteration 199: x = TUTBEOQEHSBOITEIHR AH O IL OIQ ZRNSTMON, Score = 114
Iteration 202: x = TUTBEOQEQSBOITO HR AH O IL OIQ ZRNSTMON, Score = 120
Iteration 214: x = TUTBEOQEQSBOTTO HR AH O I  OIQ ZRNSTMON, Score = 122
Iteration 230: x = TUTBEOQEQSBOTTO HH AH R I  OIQ ZRNSTNON, Score = 124
Iteration 251: x = TOTBEOEECSBOTTO HH AH R I  OIQ ZRNSTNON, Score = 125
Iteration 255: x = TOTBEOEECSOOTTO HH AH R I  OQQ XRNSTNON, Score = 128
Iteration 273: x = TOTBEOEEBSOOTTO HH AH R I  OQH XRNSTNON, Score = 130
Iteration 281: x = TOTBEOEEBSOOTTO BH AH R I  OQH XRNSTNON, Score = 133
Iteration 313: x = TOTBEOEEBSOOTTO BH AH R I  TQH XRNSTNON, Score = 136
Iteration 332: x = TO BEOEEBSOOTTO BH AH R I  TQH XRNSTNON, Score = 139
Iteration 379: x = TO BEOEEBSOOTTO BH AH R I  TQH XRNSTION, Score = 142
Iteration 553: x = TO BEOEEBSOOTTO BH AH R I  TQH HRNSTION, Score = 144
Iteration 646: x = TO BEOEEBSOOTTO BH AH T I  TQH HRNSTION, Score = 147
Iteration 685: x = TO BEOEEBSOOTTO BH AH T I  TQE HRNSTION, Score = 150
Iteration 736: x = TO BEOEEBSOOTTO BH AH T I  TQE HRESTION, Score = 153
Iteration 822: x = TO BEMEIBSOOTTO BH AH T IS TQE HRESTION, Score = 154
Iteration 985: x = TO BEMEIBMOOTTO BE AH T IS TQE HRESTION, Score = 155
Iteration 989: x = TO BEMEIBMOOTTO BE AH T IS THE HRESTION, Score = 158
Iteration 1075: x = TO BEMEI MOOTTO BE AH T IS THE HRESTION, Score = 161
Iteration 1212: x = TO BEAEI MOOTTO BE AH T IS THE HRESTION, Score = 163
Iteration 1842: x = TO BEAEI  OOTTO BE AH T IS THE HRESTION, Score = 165
Iteration 1891: x = TO BEAEI  OOTTO BE AHAT IS THE HRESTION, Score = 168
Iteration 2008: x = TO BEAEI  OTTTO BE AHAT IS THE HRESTION, Score = 171
Iteration 2146: x = TO BEAOI  OTTPO BE AHAT IS THE HUESTION, Score = 172
Iteration 2174: x = TO BEAOI  OTTTO BE AHAT IS THE HUESTION, Score = 177
Iteration 2367: x = TO BE OI  OTTTO BE AHAT IS THE HUESTION, Score = 180
Iteration 3137: x = TO BE OI NOTTTO BE AHAT IS THE HUESTION, Score = 183
Iteration 7172: x = TO BE OR NOTVTO BE AHAT IS THE HUESTION, Score = 184
Iteration 7503: x = TO BE OR NOT TOBBE AHAT IS THE HUESTION, Score = 186
Iteration 7994: x = TO BE OR NOT TO BE AHAT IS THE HUESTION, Score = 189
Iteration 8524: x = TO BE OR NOT TO BE AHAT IS THE QUESTION, Score = 192
Iteration 8581: x = TO BE OR NOT TO BE THAT IS THE QUESTION, Score = 195

 IterationとScoreの関係は以下の通り。これもChatGPTに描いてもらえたかな?

IterationとScoreの関係

*1:Wikipediaでは、「無限の猿定理」(infinite monkey theorem)として説明されています。これを読むと、もっと深い意味のある内容であることがわかります。

*2:変数名Sとかxって何だよって言われるやつかしら・・・

*3:実際には、最初の2行を指定するのを忘れていました。なのに、手順だけでちゃんと回答してくれました。

テスト設計技法を分類する「Ostrandの4つのビュー」の原典に一生たどり着けない

 暑かった夏ももう終わり。そろそろ秋ですね。
 この記事では初秋らしく、テスト技法を分類する切り口の1つとしての「Ostrandの4つのビュー」を紹介します。

 手始めに、『ASTERセミナー標準テキスト』を引用してみましょう。

ASTERセミナー標準テキストV3.1.1より

「4つのビュー」におけるテスト技法の分類

 ここでの主張は、以下の2点です。

  • テストの技法は、4つのカテゴリーに分けることができる
    • User-oriented (ユーザ指向): ユーザの要求に合うか、利用時に満足しているか
      • 例: ユースケーステスト
    • Spec-oriented (要件指向): 仕様通り実装しているか
      • 例: 境界値分析
    • Design-oriented (コード指向): ロジックは正しいか、実行されないコードはないか
      • 例: 制御パステスト
    • Fault-oriented (フォールト指向): 発生しやすい、あるいは、推測される欠陥が検出されるか
      • 例: エラー推測テスト
  • この4つのいずれかに偏るのではなく、バランスをとることが大切である

 「テスト」といった時に、みなさんは上のうちのどれを最初に連想するでしょうか?

 テストを勉強し始めた頃のわたしであれば、Spec-orientedを選んだと思います。仕様は概ね正しいものと見なして、その仕様に準じて動作することを検証するために必要なテストの空間を、いかに効率的に塗りつぶしていくかみたいな楽しさがあります。テスト自体の知識が求められる部分ですね。

 今なら、User-orientedがテストとして面白いと感じます*1
 仕様に準じて一通り動いたとして、「本当にこれでユーザが価値を感じてくれるか、課題を解決できているのか」に焦点を当てて考える*2ものです。ドメイン知識の比率が高くなる部分です。

 この2つとは違う面白さがあるのが、Fault-oriented。ここは、バグに関する過去の知識がモノを言う部分です。
 「この辺でよくミスるんだよな」「過去にこういうやらかしがあった」みたいな知識は、要件にも仕様にも、事細かに現れるものではないため、テストエンジニアの腕の見せ所の一つでしょう。

 Design-orientedは「コード指向」と訳されていますが、アーキテクチャとか、昔の(?)言葉でいうところの「内部設計」からコードまでを含めてを対象としたもので、いわゆる「開発者テスト」に近く、設計・実装の知識が求められる部分です。

 同じ「テスト」なのに、考えるべきことも、求められる知識・スキルも異なるというのが、この分類の面白い点ですね。
 複合的な知識の総動員でテストを設計・実行し、自分たちのプロダクトがあるべき姿にどこまで近づいているかを調べる。これがテストの面白さの一つではないかなと思います。

 西康晴さんの10年前のツイートを引用してみます。

SQuBOKにおけるテスト技法の分類

 SQuBOKでは、テスト技法を使うにあたってインプットする情報をいう切り口で、テスト技法を分類しています。

  1. テスト対象のソフトウェアそのものの情報: 仕様に基づいた技法、コードに基づいた技法
  2. テスターやソフトウェアの利用者が持つ情報: 経験および直感に基づいた技法、フォールトに基づいた技法、リスクに基づいた技法、利用に基づいた技法
  3. その他: 組み合わせの技法

 1.や2.とは別に「組み合わせの技法」が位置付けられていることへの違和感は置いておくとして、根拠とする情報の種類を「ソフトウェアそのものの情報」と「人間がもつ情報」に分類するのがユニークだなと思います。テスト観点の源泉は、開発の成果物に限定されないことを示唆しています。

 Ostrandのビューでいうと、「利用に基づいた技法」がUser-oriented、「フォールトに基づいた技法」がFault-orientedになるでしょう。
 「経験および直感に基づいた技法」に属する探索的テストは、「技法というよりスタイル」とも言われますし、「リスクに基づいた技法」もどちらかといえばテストの「戦略」に属するものなので、対応するビューはなさそうです。

原典はどこにあるのか・・・

 話を戻して、「Ostrandの4つのビュー」、この話の原典はどこにあるのかが気になります。
 わたしの現時点での結論は以下の通りです。

  1. 西康晴氏が、遅くとも2005年の時点*3で、このコンセプトを国内に紹介している
  2. 日本語で「ビュー」に言及しているページは少なくないが、ほとんどは西康晴氏の資料の孫引き*4と思われる
  3. Ostrand氏の論文はいくつか見つかるが、ズバリこの「4つ」を明示しているものにはたどり着けなかった

 つまり、「Ostrandの例の論文」は、インターネット到達不能極にあると判断しています。

 一番近いと感じた論文は、THE CATEGORY-PARTITION METHOD FOR SPECIFYING AND GENERATING FUNCTIONAL TESTSです。
 引用してみましょう。翻訳はChatGPTです*5

Functional tests can be derived from the software’s specifications, from design information, or from the code itself. All three test sources provide useful information, and none of them should be ignored. Code-based tests relate to the modular structure, logic, control flow, and data flow of the software. They have the particular advantage that a program is a formal object, and it is therefore easy to make precise statements about the adequacy or thoroughness of code-based tests. Design-based tests relate to the programming abstractions, data structures, and algorithms used to construct the software. Specification-based tests relate directly to what the software is supposed to do, and therefore are probably the most intuitively appealing type of functional tests.

機能テストはソフトウェアの仕様、設計情報、またはコード自体から派生することができます。これら3つのテスト源はすべて有用な情報を提供し、いずれも無視すべきではありません。コードベースのテストはソフトウェアのモジュラー構造、ロジック、制御フロー、およびデータフローに関連しています。プログラムは正式なオブジェクトであるため、コードベースのテストの適切さや徹底さに関する正確な声明を簡単に行うことができるという特定の利点があります。設計ベースのテストは、ソフトウェアを構築するために使用されるプログラミング抽象化、データ構造、およびアルゴリズムに関連しています。仕様ベースのテストは、ソフトウェアが行うべきことに直接関連しており、したがって、おそらく直感的に魅力的なタイプの機能テストと言えます。

 論文でCode-based、Design-basedといっているものが、「4つのビュー」でいうところのDesign-oritentedに。Specification-basedといっているものが、User-oritentedとSpec-oritentedに対応するように思えます。
 つまり、Fault-orientedに対応するものが、論文には見当たらないですね。

 原典はいったい、インターネットのどの経路を辿ればたどり着けるのか・・・。
 Ostrandの4つのビュー、原典を見つけた方は、ぜひお知らせください。

補足1

 この記事の投稿後、秋山浩一さんに教えていただきました。
 テスト技術者交流会MLにおける2007年のやりとりによると、この分類はOstrand氏の話を元にしたとのことで、論文自体にこの分類が定義されているわけではないようです。

補足2

 さらに、辰巳敬三さんにも教えていただきました。

 にしさんのJaSST’06での論文*6で以下のように言及されているとのこと。

: Ostrand はこうした観点の最上位を「ビュー(view)」と呼んでいる[2]
: [2] J. J. Marciniak, Encyclopedia of Software Engineering, John Wiley & Sons, New-York, 1994.

 論文ではなく、『Encyclopedia of Software Engineering』という書籍で説明されているのですね。Google Booksでは一部しか参照できないのですが、辰巳さんによると以下の通り、確かに4つが述べられています。

Ostrandは SOURCE OF DERIVING TESTS を problem specification, problem implementation, fault information and history, and program usage information の4つに区別し、以下のテストを説明しています。
Specification-Based Tests
Implemented-Based Tests
Fault-Based Tests
Usage-Based Tests

 「インターネット到達不能極」などと言って申し訳ありませんでした、単に有償の文献などを調べていないだけですね。。。

*1:と書いていますが、手を動かしてのテストはもうしばらくやっていません。一生現役みたいな書き方してごめんなさい・・・。

*2:これをテストの時点で考えるのでは遅い、というのはその通り。

*3:以下のような資料がある。
・2005年5月 \@IT ソフトウェアテスト・ミーティング
・2008年6月 ソフトウェアテストシンポジウム四国2008
・2013年4月 WARAI(関西テスト勉強会)スペシャル
追記: 辰巳敬三さんによると、『月刊ジャバワールド』2002年10月号の「ソフトウェア・テストの基本を学ぶ」まで遡ることができるとのこと!

*4:「孫引き」って単に「引用の引用」程度の意味だと思っていたけれど、もっとネガティブな意味を含むと知りました。「他の本から引用してあるものを、原典にさかのぼって調べることなく、そのまま更に引用すること。」

*5:わたしならこのコンテキストではderiveは「導出」、formalは「形式化された」、thoroughnessは「完全さ」と訳すかなー。statementは迷うな。

*6:JaSSTのページには論文はなく、発表スライドのみが公開されています。

リグレッションテストとは何なのか

 小説なんかでいわれる「緻密なプロット」の何がすごいって、「情報に関する情報」のコントロールだと思うんですよね。

  • 人物Xは、事実Fを知らない。
  • 人物Yは、事実Fを知っている。

 これなら単純なのですが、一階層上がって

  • 人物Zは、「人物Xが事実Fを知らない」ことを知っている。
  • 人物Zは、「人物Yが事実Fを知っている」ことを知っている。

 でもすでに複雑ですし、さらにもう一階層上がって

  • 人物Yは、「「自分が事実Fを知っている」ことを、人物Zが知っている」ことを知らない。

となってしまうと、もうかなりややこしいですよね。そしてこの状況は、物語が進むにつれて変化していくわけです。

 このような条件が入り組めば入り組むほど、文章の些細な修正がプロットを破綻させるリスクが高まります。「あれ? この時点でこの人たちは面識なかったはずなのに・・」みたいな。

 ということで、JSTQBの用語集で、「リグレッションテスト」の定義を見てみましょう。

リグレッションテスト(regression testing)
バージョン 3
ソフトウェアの変更されていない領域で欠陥が混入している、もしくは露呈していることを検出するための、変更関連のテストの一種。

 この定義には暗黙の前提として、「ソフトウェアが変更された」ことが含意されていますが、それはいいでしょう。
 次に気になるのは、「変更されていない領域」という表現です。ソフトウェアのある部分を変更したとき、「変更された領域」と「変更されていない領域」に二分できることが含意されているわけです。これって自明なことでしょうか?

 物語のアナロジーで考えてみます。
 ある単語を入れ替えたときに、「変更された領域」はどこでしょうか。単語か、文か、文章か、段落か、章か、物語全体か・・。

 「物語全体」って言わざるを得ないんじゃないかなと思います。
 もちろん多くの場合、変更部分から「遠い」部分ほど、影響が小さくなる傾向はあるでしょう。ただそれはあくまでもグラデーションだし、特に叙述トリック界隈では「序盤のこの何気ない文章が、ここにつながってたのかー!」みたいな(意図した)「影響」が生まれるわけですね。

 ソフトウェアの話に戻りましょう。
 ソフトウェアって、原則として前から後ろに流れていく小説と比べて、「変更による影響の及ぶ範囲」が人間には見極めづらいですよね。だからこそ、「変更によって意図せぬ副作用が生まれていないか」を検証する必要があり、そのための道具の一つがリグレッションテストなんだと思います。

 なので、リグレッションテストのわたしなりの定義は、以下です。

ソフトウェアの変更によって、意図しない領域で欠陥が混入している、もしくは露呈していることを検出するための、変更関連のテストの一種。

 「意図」という言葉に、人間の主観と限界を込めています。

 ・・・とここまで書いて、JSTQB Foundationシラバスを覗いてみると、「2.3.4 変更関連のテスト」に以下のような記述があります。

リグレッションテスト:
修正および変更でコードの一部に対して行った変更が、同一コンポーネント、同一システム内の他コンポーネント、または他システムの振る舞いに意図せず影響を及ぼす場合がある。変更には、オペレーティングシステムやデータベース管理システムの新しいバージョンなど、環境の変更も含まれる。そのような意図しない副作用をリグレッションと呼ぶ。リグレッションテストでは、テストを実行して、そのような意図しない副作用を検出する。

 いや、「わたしなりの定義」とかいらんかったわ! ちゃんとシラバスに書いてある!
 この文章を定義に流用すればいいんじゃないかな・・・?

おまけ1

 変更の影響の特定の難しさについては、以下のまとめが秀逸です。

togetter.com

非プログラマの方に「どうしてそんなに変更するの難しいんですか?」って聞かれたら、「六法全書を思い出して欲しい、条項の中から一文探し出して、それが他と矛盾しないかもチェックして欲しい、もちろんその変えるべき条項文は依頼側は分からないので、そっちで全て調べてください」って伝えてる

おまけ2

 リグレッションテストと関係の深い用語「デグレ」ですが、この言葉は和製英語らしいぜという話は以下の記事で。

www.kzsuzuki.com

おまけ3

 「プログレッションテスト」という言葉を教えてもらいました。

 この記事で簡単な説明があります。

www.ricksoft.jp

プログレッションテスト
顧客の新たな要望に応える新機能を市場に素早く投入する。なのでテストをシフトレフトする。 それには、SeleniumやAppium、BDD (Behavia Driven Development: 振る舞い駆動開発)と言ったスクリプトベースな技術でテストを行うとアジリティが良いそうです。

 この記事(と講演)でいう「リグレッションテスト」「プログレッションテスト」の切り口は、わたしの考えているものとはちょっと違いますが、なるほどーと思いました。

後ろ向きに歩く人々 by midjourney

2023年前半に読んで特によかった本

今年の前半6か月で読んだ本のうち、お気に入りのものを紹介します。
読んだ後時間が経つとどんどん書く気がなくなるので、その時点でツイートに吐き出したものをまとめ直しております。

なお、前回分はこれ。 www.kzsuzuki.com

IT技術系

アジャイルメトリクス

開発方法論がウォーターフォールからアジャイルになったからといって、メトリクスが本質的に変わることなんてあるのだろうか?と思いながら読みました。
結果としては、たぶんYES。最近よく「ソフトウェアの品質とは何か」という哲学質問をTwitterで見かけますが、この本を通じて、自分なりの考えをだいぶ再定義できたように思います。それを吐き出したのが、この記事です。

www.kzsuzuki.com

またこの本は、単に「こんなメトリクスがあるよ」と列挙するものではなく、どのようなツールで計測を自動化し、その結果をどう生かすかということにもたくさんの紙面を割いています。
実はまだ、第8章『ソフトウェア品質を測定する』とその周辺しか読めていないので、下期ちゃんと読みます。。

ビジネス系

解像度を上げる

思考や情報を広げる・狭める・深堀りする・網羅する・・・
こういった「思考の構造化」をどのように行うのかを構造化して説明した本(メタ)。ロジカルシンキングもこの構造の一部として位置づけられるような気がしています。

実はまだ、さっと一読しかできていないのだけれど、何度も読み返して自分の思考の訓練に使えるようにしておきたいと感じました。若いうちに出会えたらもっとよかったなあ。

ビジネスの名著を読む〔マネジメント編〕

「ビジネスの名著を20冊、ぎゅっと1冊に濃縮しました!」というと、「はいはい、ファスト教養ファスト教養」って色眼鏡で見てしまうのですが、栄養もめちゃくちゃある本でした。
ビジネスにおいて誰でも聞いたことがあるようなコンセプト・フレームワークが当時どう語られ、今どのような意味をもつのかということを知ることができます。もちろん原著をしっかり読み込むのが一番なのかもしれませんが、概要+アルファを理解するのにありがたい選択肢です。

この本で扱っている本のうち、『小倉昌男 経営学』で言及されている論文の話が面白かった。
ロケットの打ち上げにおいて、成功と失敗の数が、新しい打ち上げの成功にどういう影響を与えるかを調べたものです。
結果は以下とのこと。

①成功体験も失敗体験も、その後の成功確率を上げる。経験はプラスに働くといえる。
②成功経験と失敗経験の効果の強さを調べると、その後の成功をより高めるのは、失敗経験の方である。
③失敗経験が乏しいまま、成功だけを重ねてしまうと、むしろその後は失敗確立の方が高まっていく。

「うまく失敗する」「小さく素早く失敗する」ことがいかに大事かという問いへの、強力な示唆だと思います。

『グローバル×AI翻訳時代の新・日本語練習帳』

「自分の日本語をAI翻訳で適切に翻訳してもらうには」という切り口で始まります。 PART1の最初で紹介される2つのtipsが「必ず主語を書く」「目的語をちゃんと書く」なんですよ。日本語どんだけ省略するのよっていう。

前半でこのような個別的なテクニックについて語り、後半は伝わりやすい文章構成、異文化コミュニケーションで注意すべき点などがまとまっています。「自分の意図を正しく伝えるには」ってのが本書のテーマで、その1つが「AIの翻訳に耐える文章」ということですね。

ノンフィクション・エッセイ

科学オモテウラ大事典

ポピュラーサイエンス好きにお勧めの一冊。1つのテーマをトピック2つセットで読むという面白い構成。
核分裂と原子爆弾、イオンとマイナスイオン、エジソンとテスラ、・・・みたいな、「科学の光と闇」「技術の裏にあるドロドロ人間模様」的オモテウラもあれば、使い捨てカイロと冷却パック、ミトコンドリア・イブとY染色体・アダム、みたいなオモテウラもあり、楽しく読めます。

気に入ったエピソードの1つが、アルキメデスのてこの話。
「棒と支点と足場を与えてくれれば、地球でも動かしてみせる」というエピソードだけれど、その3つがそろっていたとしても、1017 kmの長さを棒を使って地球を1mm動かすには19兆年かかるという計算なんだと。そういう計算をしてしまうのが好き。

フィクション

アーモンド

中二の長男に勧められて読んだ本。こども向けかな~と思いきや、いい意味で裏切られました。
喜怒哀楽を感じることのできない主人公が、自分に興味を抱く異性や、暴力でしか自分を表現することのできない少年と出会って、奇妙な関わり合いを過ごしていく話。説明しづらいのだけれど、とてもよかった。
情緒に特異性があるという点で、『アルジャーノンに花束を』を思い出した。本棚に眠っているな・・・。

漫画

中年どまんなかになってくると、「夢を持った若者の挑戦と挫折」にめっちゃ弱くなりますよね。
その中でも「舞台に立つ」系の作品は、ままならないもどかしさにこちらも泣けてきます。前回の記事で紹介した『ピアノの森』のサブ主人公の劣等感だったり、(アニメしか観ていませんが)『ぼっち・ざ・ろっく』の結束バンドによる超アウェイなライブだったり・・・。

べしゃり暮らし

「学園の爆笑王」を名乗り、誰よりも自分が面白いと思っている主人公が、漫才出身の転校生に出会って、漫才の道を進んでいく話。
森田先生の作品は『ろくでなしブルース』しか読んだことがなく(なので吉祥寺(ジョージ)は怖い街だと思っている)、何となく食わず嫌いだったのですが、『べしゃり暮らし』は素晴らしい作品でした。「人間模様」というと本当にありきたりな言葉なのですが、漫才に関わる登場人物たちの喜びと、野心・苦悩・葛藤が丁寧に描かれ、わたしたち中年こそが読む話、と感じました(少年ジャンプでの連載だけど。。)。

ランウェイで笑って

『ランウェイで笑って』も良かったですね。
幼い頃からパリコレモデルを夢見続けているが身長が足りない女子と、ファッションデザイナーを志すも家庭の経済状態に阻まれる男子が、お互い支え合ったりライバルになったりしながら成長していくお話です。

何だか著者の画力が急速に爆上がりしていって、見開きが映える映える。デザインした服やモデルのすごさの表現が、後半は能力者バトルみたいな描写になっていて最高でした。

あとい全然青春じゃないですが、『ジャンケットバンク』というギャンブルマンガが面白いです。

「銀行が賭場を仕切っていて、銀行を亡ぼすギャンブラーを討つ」みたいな設定がだいぶ荒唐無稽なのと、ギャンブラー同士だけでなく銀行員もバトルに巻き込まれていくところから、『嘘喰い』の亜種か?と思ったのですが、ギャンブルの内容と勝負が目新しく、今後が楽しみです。

・・・マンガは用法容量を守りましょう。