11

テスト駆動開発を使用して信号処理ライブラリを実装しようとしています。しかし、私には少し疑問があります。私が正弦波メソッドを実装しようとしていると仮定します(私はそうではありません):

  1. テストを書く(擬似コード)

    assertEqual(0, sine(0))
    
  2. 最初の実装を書く

    function sine(radians)
        return 0
    
  3. 2番目のテスト

    assertEqual(1, sine(pi))
    

この時点で、私は次のことを行う必要があります。

  1. 円周率やその他の値で機能するスマートコードを実装する、または
  2. 0とpiでのみ機能する最も愚かなコードを実装しますか?

2番目のオプションを選択した場合、いつ最初のオプションにジャンプできますか?私は最終的にそれをしなければならないでしょう...

4

9 に答える 9

9

この時点で、私は次のことを行う必要があります。

  1. 2つの簡単なテストの外で機能する実際のコードを実装しますか?

  2. 2つの簡単なテストでのみ機能する最も愚かなコードを実装しますか?

ない。「一度に1つのテストだけを書く」アプローチをどこから得たかはわかりませんが、それは確かに遅い方法です。

重要なのは、明確なテストを作成し、その明確なテストを使用してプログラムを設計することです。

したがって、正弦関数を実際に検証するのに十分なテストを記述します。2つのテストは明らかに不十分です。

連続関数の場合、最終的には既知の適切な値のテーブルを提供する必要があります。なぜ待つのですか?

ただし、連続関数のテストにはいくつかの問題があります。ばかげたTDD手順に従うことはできません。

0から2*piまでのすべての浮動小数点値をテストすることはできません。いくつかのランダムな値をテストすることはできません。

連続関数の場合、「厳密で考えられないTDD」は機能しません。ここでの問題は、正弦関数の実装が一連の対称性に基づいていることを知っていることです。使用している対称規則に基づいてテストする必要があります。虫はひびや角に隠れます。エッジケースとコーナーケースは実装の一部であり、無意識のうちにTDDに従えば、それをテストすることはできません。

ただし、連続関数の場合は、実装のエッジケースとコーナーケースをテストする必要があります。

これは、TDDが壊れているまたは不十分であることを意味するものではありません。「最初のテスト」へのスラブな献身は、あなたの本当の目標が何であるかを考えずには機能しないと言われています。

于 2009-09-23T02:26:50.807 に答える
5

厳密なベイビーステップTDDの一種では、ダムメソッドを実装してグリーンに戻し、ダムコードに固有の重複をリファクタリングすることができます(入力値のテストは、テストとコードの間の一種の重複です)実際のアルゴリズムを作成することによって。このようなアルゴリズムでTDDの感触をつかむのが難しいのは、受け入れテストが実際にあなたのすぐ隣にあることです(S. Lottが示唆する表)。そのため、常にそれらを監視する必要があります。より一般的なTDDでは、ユニットは全体から十分に離婚しているため、受け入れテストをすぐにプラグインすることはできません。したがって、すべてのシナリオが明確ではないため、すべてのシナリオのテストについて考える必要はありません。

通常、1つまたは2つのケースの後に実際のアルゴリズムがあります。TDDの重要な点は、アルゴリズムではなく設計を推進していることです。設計のニーズを満たすのに十分なケースがあると、TDDの値は大幅に低下します。次に、テストはさらにカバーするコーナーケースに変換され、考えられるすべての側面でアルゴリズムが正しいことを確認します。したがって、アルゴリズムの構築方法に自信がある場合は、それを選択してください。あなたが話している赤ちゃんのステップの種類は、あなたが不確かな場合にのみ適切です。このような小さなステップを踏むことで、実装が実際にはまだ現実的ではない場合でも、コードがカバーしなければならない範囲の境界を構築し始めます。しかし、私が言ったように、それはあなたがアルゴリズムを構築する方法について確信が持てないときのためにもっとあります。

于 2009-09-23T03:00:20.170 に答える
5

IDを検証するテストを作成します。

sin(x)の例では、二倍角の公式と半角の公式について考えてみてください。

信号処理の教科書を開きます。関連する章を見つけて、それらの定理/系のすべてを、関数に適用可能なテストコードとして実装します。ほとんどの信号処理機能には、入力と出力に対して維持する必要のあるIDがあります。それらの入力が何であるかに関係なく、それらのIDを検証するテストを作成します。

次に、入力について考えます。

  • 実装プロセスを別々の段階に分割します。各ステージには目標が必要です。各段階のテストは、その目標を検証することです。(注1)
    1. 最初の段階の目標は、「大まかに正しい」ことです。sin(x)の例の場合、これは、バイナリ検索といくつかの数学的IDを使用した単純な実装のようになります。
    2. 第2段階の目標は、「十分に正確」であることです。同じ関数を計算するさまざまな方法を試し、どちらがより良い結果を得るかを確認します。
    3. 第3段階の目標は、「効率的」であることです。

(注1)動作させ、正しくし、速くし、安くします。-アランケイに起因

于 2010-07-02T06:53:19.700 に答える
1

最初のオプションにジャンプするときのステップは、「テストに合格するためだけに」コードに「if」が多すぎることを確認したときだと思います。0とpiだけでは、まだそうではありません。

コードの匂いがし始めていると感じ、できるだけ早くリファクタリングすることをいとわないでしょう。それが純粋なTDDの言うことかどうかはわかりませんが、私見では、リファクタリングフェーズ(テスト失敗、テストパス、リファクタリングサイクル)でそれを行います。つまり、失敗したテストで別の実装が要求されない限りです。

于 2009-09-23T02:14:27.923 に答える
1

(NUnitで)次のこともできることに注意してください

Assert.That(2.1 + 1.2, Is.EqualTo(3.3).Within(0.0005);

浮動小数点の等式を扱っているとき。

私が読んだことを覚えているアドバイスの1つは、実装からマジックナンバーをリファクタリングしようとすることでした。

于 2009-09-23T02:21:53.187 に答える
1

すべての単体テストを1回のヒットでコーディングする必要があります(私の意見では)。テストする必要があるものを具体的にカバーするテストのみを作成するという考えは正しいですが、特定の仕様では、0およびPIで機能するsine()関数ではなく、機能する関数が必要です。sine()

十分に信頼できる情報源を見つけてください(数学者の友人、数学の本の裏にある表、またはすでに正弦関数が実装されている別のプログラム)。

手ですべてを入力するのが面倒なので、私はそれを選びましbash/bcた:-)。関数の場合sine()次のプログラムを実行してテストコードに貼り付けます。また、このスクリプトのコピーをコメントとしてそこに入れて、何かが変更された場合に再利用できるようにします(この場合、20度を超える場合の目的の解像度、または必要なPIの値など)。使用する)。

#!/bin/bash
d=0
while [[ ${d} -le 400 ]] ; do
    r=$(echo "3.141592653589 * ${d} / 180" | bc -l)
    s=$(echo "s(${r})" | bc -l)
    echo "assertNear(${s},sine(${r})); // ${d} deg."
    d=$(expr ${d} + 20)
done

これは以下を出力します:

assertNear(0,sine(0)); // 0 deg.
assertNear(.34202014332558591077,sine(.34906585039877777777)); // 20 deg.
assertNear(.64278760968640429167,sine(.69813170079755555555)); // 40 deg.
assertNear(.86602540378430644035,sine(1.04719755119633333333)); // 60 deg.
assertNear(.98480775301214683962,sine(1.39626340159511111111)); // 80 deg.
assertNear(.98480775301228458404,sine(1.74532925199388888888)); // 100 deg.
assertNear(.86602540378470305958,sine(2.09439510239266666666)); // 120 deg.
assertNear(.64278760968701194759,sine(2.44346095279144444444)); // 140 deg.
assertNear(.34202014332633131111,sine(2.79252680319022222222)); // 160 deg.
assertNear(.00000000000079323846,sine(3.14159265358900000000)); // 180 deg.
assertNear(-.34202014332484051044,sine(3.49065850398777777777)); // 200 deg.
assertNear(-.64278760968579663575,sine(3.83972435438655555555)); // 220 deg.
assertNear(-.86602540378390982112,sine(4.18879020478533333333)); // 240 deg.
assertNear(-.98480775301200909521,sine(4.53785605518411111111)); // 260 deg.
assertNear(-.98480775301242232845,sine(4.88692190558288888888)); // 280 deg.
assertNear(-.86602540378509967881,sine(5.23598775598166666666)); // 300 deg.
assertNear(-.64278760968761960351,sine(5.58505360638044444444)); // 320 deg.
assertNear(-.34202014332707671144,sine(5.93411945677922222222)); // 340 deg.
assertNear(-.00000000000158647692,sine(6.28318530717800000000)); // 360 deg.
assertNear(.34202014332409511011,sine(6.63225115757677777777)); // 380 deg.
assertNear(.64278760968518897983,sine(6.98131700797555555555)); // 400 deg.

明らかに、この答えを実際の関数の目的にマッピングする必要があります。私のポイントは、テストはこの反復でのコードの動作を完全に検証する必要があるということです。この反復でsine()、0とPIに対してのみ機能する関数を生成する場合は、それで問題ありません。しかし、それは私の意見では反復の深刻な無駄になるでしょう。

関数が非常に複雑であるため、数回の反復で実行する必要がある場合があります。次に、アプローチ2が正しく、次の反復でテストを更新して、機能を追加する必要があります。それ以外の場合は、この反復のすべてのテストをすばやく追加する方法を見つけてください。そうすれば、実際のコードとテストコードを頻繁に切り替えることを心配する必要がなくなります。

于 2009-09-23T03:31:17.120 に答える
0

TDDに厳密に従うと、最初に機能する最も愚かなコードを実装できます。最初のオプションにジャンプするには(実際のコードを実装するため)、さらにテストを追加します。

assertEqual(tan(x), sin(x)/cos(x))

テストで絶対に必要なものを超えて実装する場合、テストは実装を完全にはカバーしません。たとえば、sin()上記の2つのテストだけで関数全体を実装した場合、三角形関数(ほぼ正弦関数のように見えます)を返すことで誤って「壊す」可能性があり、テストではエラーを検出できません。

数値関数について心配しなければならないもう1つのことは、「等式」の概念であり、浮動小数点計算に固有の精度の低下に対処する必要があります。タイトルだけを読んだ後、あなたの質問はそうなると思いました。:)

于 2009-09-23T02:11:39.257 に答える
0

使用している言語はわかりませんが、数値法を使用する場合は、通常、最初にあなたのような簡単なテストを作成して、アウトラインが正しいことを確認してから、疑わしいケースをカバーするためにさらに値をフィードします。物事がうまくいかないかもしれません。.NETでは、NUnit 2.5には、と呼ばれるこのための優れた機能が[TestCase]あり、次のように複数の入力値を同じテストにフィードできます。

[TestCase(1,2,Result=3)]   
[TestCase(1,1,Result=2)]     
public int CheckAddition(int a, int b)   
{  
 return a+b;   
}
于 2009-10-12T02:27:37.910 に答える
0

短い答え。

  • 一度に1つのテストを記述します。
  • 失敗したら、最初に緑に戻ります。それがうまくいくことができる最も簡単なことをすることを意味するなら、それをしてください。(オプション2)
  • 緑色になったら、コードを確認してクリーンアップを選択できます(オプション1)。または、コードの匂いはまだそれほど多くないと言って、匂いにスポットライトを当てる後続のテストを作成することもできます。

あなたが持っていると思われるもう1つの質問は、いくつのテストを書くべきかということです。恐怖(機能が機能しない場合があります)が退屈になるまでテストする必要があります。したがって、すべての興味深い入出力の組み合わせをテストしたら、完了です。

于 2011-11-24T07:03:28.747 に答える