TDDを簡単に実行できるようにゼロから設計された新しい言語はどのように見えますか


9

いくつかの最も一般的な言語(Java、C#、Javaなど)では、コードを完全にTDDしたいときに、その言語と対立しているように見えることがあります。

たとえば、JavaとC#では、クラスの依存関係をモックする必要があり、ほとんどのモックフレームワークでは、クラスではなくインターフェイスをモックすることをお勧めします。これは、多くの場合、単一の実装で多くのインターフェースがあることを意味します(この影響は、TDDにより多数の小さいクラスを作成するよう強制されるため、さらに顕著になります)。具象クラスを適切にモックできるようにするソリューションは、コンパイラーを変更したり、クラスローダーをオーバーライドしたりするなど、かなり厄介です。

それで、TDDに最適になるようにゼロから設計された言語はどのようになりますか?(インターフェイスをコンストラクタに渡すのではなく)依存関係を記述する何らかの言語レベルの方法で、明示的に行うことなくクラスのインターフェイスを分離できるのでしょうか?


TDDを必要としない言語はどうですか? blog.8thlight.com/uncle-bob/2011/10/20/Simple-Hickey.html
Job

2
TDDを必要とする言語ありません。TDDは便利な手法であり、Hickeyのポイントの1つは、テストを行ったからといって、思考を停止する可能性があるということではありません。
Frank Shearar、

テスト駆動開発とは、内部APIと外部APIを正しくすることです。したがって、Javaではすべてがインターフェースに関するものです。実際のクラスは副産物です。

回答:


6

何年も前に、私は同様の質問に対処するプロトタイプを一緒に投げました。ここにスクリーンショットがあります:

ゼロボタンテスト

アイデアは、アサーションはコード自体とインラインであり、すべてのテストは基本的に各キーストロークで実行されるというものでした。したがって、テストに合格するとすぐに、メソッドが緑色に変わります。


2
はは、すごい!私は実際、コードとテストを組み合わせるというアイデアがとても気に入っています。単体テスト用の並列名前空間を持つ個別のアセンブリがある.NETでは、(非常に正当な理由はありますが)非常に退屈です。また、コードを移動すると、テストが自動的に移動するため、リファクタリングが容易になります:P
Geoff

しかし、そこにテストを残しますか?量産コードを有効にしたままにしますか?多分それらはCのために#ifdefされるかもしれません、さもなければ私達はコードサイズ/実行時のヒットを見ています。
モーグはモニカを2015

それは純粋にプロトタイプです。それが現実になるとすれば、パフォーマンスやサイズなどを考慮する必要がありますが、それを心配するのは早い段階であり、その時点まで到達したとしても、除外するものを選択するのは難しくありません。または、必要に応じて、アサーションをコンパイル済みコードから除外します。関心をお寄せいただきありがとうございます。
Carl Manaster、2015年

5

静的に型付けされるのではなく、動的に型付けされます。 その後、ダックタイピングは、インターフェースが静的に型付けされた言語で行うのと同じ働きをします。また、テストフレームワークが既存のクラスのメソッドを簡単にスタブまたはモックできるように、そのクラスは実行時に変更可能です。Rubyはそのような言語の1つです。rspecは、TDDの主要なテストフレームワークです。

動的型付けがテストをどのように支援するか

動的型付けを使用すると、モックする必要があるコラボレーターオブジェクトと同じインターフェイス(メソッドシグネチャ)を持つクラスを作成するだけで、モックオブジェクトを作成できます。たとえば、メッセージを送信するクラスがあったとします。

class MessageSender
  def send
    # Do something with a side effect
  end
end

MessageSenderのインスタンスを使用するMessageSenderUserがあるとします。

class MessageSenderUser

  def initialize(message_sender)
    @message_sender = message_sender
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

単体テストの定番である依存性注入のここでの使用に注意してください。戻ってきます。

MessageSenderUser#do_stuff呼び出しが2回送信されることをテストする必要があります。静的に型付けされた言語の場合と同じように、send呼び出された回数をカウントするモックMessageSenderを作成できます。ただし、静的に型付けされた言語とは異なり、インターフェイスクラスは必要ありません。先に進んで作成します。

class MockMessageSender

  attr_accessor :send_count

  def initialize
    @send_count = 0
  end

  def send
    @send_count += 1
  end

end

そして、あなたのテストでそれを使用してください:

mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)

動的に型付けされた言語の「アヒル型付け」自体は、静的に型付けされた言語と比較して、テストにそれほど多くのことを加えません。しかし、クラスが閉じていなくても、実行時に変更できる場合はどうなりますか?それはゲームチェンジャーです。方法を見てみましょう。

クラスをテスト可能にするために依存性注入を使用する必要がなかった場合はどうなりますか?

MessageSenderUserがMessageSenderを使用してメッセージを送信するだけで、MessageSenderを他のクラスで置き換えることを許可する必要がないとします。これは、単一のプログラム内でよく見られます。MessageSenderUserを書き換えて、依存関係の注入なしでMessageSenderを作成して使用するようにします。

class MessageSenderUser

  def initialize
    @message_sender = MessageSender.new
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

MessageSenderUserの使用が簡単になりました。作成するユーザーがMessageSenderを作成する必要はありません。この単純な例では大きな改善のようには見えませんが、MessageSenderUserが複数の場所で作成されているか、3つの依存関係があることを想像してください。これで、システムはユニットテストを円滑に進めるためだけに多くのインスタンスを渡しています。必ずしもデザインが改善されるわけではありません。

オープンクラスを使用すると、依存関係の注入なしでテストできます

動的型付けとオープンクラスを備えた言語のテストフレームワークは、TDDを非常に優れたものにすることができます。MessageSenderUserのrspecテストからのコードスニペットは次のとおりです。

mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff

これがテスト全体です。MessageSenderUser#do_stuffMessageSender#send正確に2回呼び出されない場合、このテストは失敗します。実際のMessageSenderクラスは呼び出されません。テストでは、誰かがMessageSenderを作成しようとするたびに、代わりにモックMessageSenderを取得するように指示しました。依存性注入は必要ありません。

このようなより単純なテストで多くのことを行うのは良いことです。実際にデザインにとって意味のあるものでない限り、依存性注入を使用する必要がないほうがより良いです。

しかし、これはオープンクラスとどう関係しているのでしょうか?への呼び出しに注意してくださいMessageSender.should_receive。MessageSenderの作成時に#should_receiveを定義しなかったので、誰が定義しましたか?答えは、システムクラスを注意深く変更したテストフレームワークは、#should_receiveがすべてのオブジェクトで定義されているように見せかけることができるということです。このようにシステムクラスを変更する際に注意が必要だと思う場合は、そのとおりです。しかし、これは、テストライブラリがここで行っていることに対して完璧なものであり、オープンクラスはそれを可能にします。


正解です。皆さんは私に動的言語について話を戻し始めています:)ここでは、ダックタイピングが重要だと思います。
Geoff

3

それで、TDDに最適になるようにゼロから設計された言語はどのようになりますか?

「TDDでうまく機能する」は、言語を記述するのに十分ではないので、何かのように「見える」可能性があります。Lisp、Prolog、C ++、Ruby、Python ...いずれかを選択してください。

さらに、TDDのサポートが言語自体によって最も適切に処理されるものであることは明らかではありません。もちろん、すべての関数またはメソッドにテストが関連付けられている言語を作成し、それらのテストを検出して実行するためのサポートを組み込むことができます。しかし、ユニットテストフレームワークは既に検出と実行の部分を適切に処理しており、すべての関数のテストの要件を明確に追加する方法を理解することは困難です。テストにもテストが必要ですか?または、2つのクラスの関数があります-テストを必要とする通常のクラスと、それらを必要としないテスト関数ですか?それはとてもエレガントに見えません。

たぶん、ツールとフレームワークでTDDをサポートする方が良いでしょう。IDEに組み込みます。それを奨励する開発プロセスを作成します。

また、言語を設計している場合は、長期的に考えることをお勧めします。TDDは1つの方法論にすぎず、すべての人が好む作業方法ではないことに注意してください。想像するのは難しいかもしれませんが、さらに良い方法が来ている可能性があります。言語デザイナーとして、あなたはそれが起こったときに人々があなたの言語を放棄しなければならないようにしたいですか?

質問に本当に答えることができるのは、そのような言語はテストに役立つということだけです。私はそれがあまり役に立たないことを知っていますが、問題は質問にあると思います。


そうですね、うまく表現するのは非常に難しい質問です。私が言っているのは、Java / C#のような言語の現在のテストツールは、言語が少し邪魔になっているように感じ、余分な/代替言語機能によってエクスペリエンス全体がよりエレガントになる(つまり、90のインターフェイスがない)ということです私のクラスの%、より高いレベルのデザインの観点から意味のあるクラスのみ)。
Geoff

0

まあ、動的に型付けされた言語は明示的なインターフェースを必要としません。RubyやPHPなどを参照してください。

一方、JavaやC#、C ++などの静的に型付けされた言語では、型が強制され、それらのインターフェイスを作成する必要があります。

私が理解していないのは、彼らのあなたの問題は何ですか。インターフェースは設計の重要な要素であり、設計パターン全体で使用され、SOLIDの原則を尊重します。たとえば、私はPHPでインターフェイスを頻繁に使用しています。これは、インターフェイスがデザインを明示的にし、デザインを強制するためです。一方、Rubyでは型を強制する方法がなく、アヒル型の言語です。しかし、それでも、そこでインターフェースを想像し、正しく実装するために設計を頭の中で抽象化する必要があります。

したがって、あなたの質問は興味深いように聞こえるかもしれませんが、依存関係注入技術の理解または適用に問題があることを意味します。

そして、あなたの質問に直接答えるために、RubyとPHPには、ユニットテストフレームワークに組み込まれ、個別に提供される優れたモックインフラストラクチャがあります(PHPのMockeryを参照)。場合によっては、これらのフレームワークでは、静的な呼び出しやオブジェクトの初期化のモックなど、依存関係を明示的に注入することなく、提案されていることを実行できます。


1
インターフェイスは素晴らしいものであり、重要な設計要素であることに同意します。ただし、私のコードでは、クラスの90%にインターフェイスがあり、そのインターフェイスの実装は、クラスとそのクラスのモックの2つしかありません。これは厳密にはインターフェースの要点ですが、洗練されていないと感じざるを得ません。
Geoff

JavaとC#でのモック化についてはあまり詳しくありませんが、私が知る限り、モック化されたオブジェクトは実際のオブジェクトを模倣しています。オブジェクトのタイプのパラメーターを使用し、代わりにメソッド/クラスにモックを送信することで、依存関係の注入を頻繁に行います。関数someName(AnotherClass $ object = null)のようなもの{$ this-> anotherObject = $ object?:新しいAnotherClass; これは、インターフェースから派生せずに依存関係を注入するために頻繁に使用されるトリックです。
Patkos Csaba、2012

1
これは間違いなく、動的言語が私の質問に関してJava / C#タイプの言語よりも優れている点です。具象クラスの典型的なモックは、実際にはクラスのサブクラスを作成します。つまり、具象クラスコンストラクターが呼び出されます。これは絶対に避けたいものです(例外はありますが、独自の問題があります)。動的モックはダックタイピングを利用するだけなので、具象クラスとそのモックの間に関係はありません。私はPythonでコードを作成するのが常でしたが、それは私のTDD時代の前でした。
Geoff
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.