2

単体テストの作成に関してはまだ初心者なので、どうすれば正しいのか頭を悩ませているケースによく遭遇します。計画されたデザインのテストを書いているときに、ふけを誘発するこれらのインスタンスの 1 つに出くわしました。私のデザイン:

ユーザーの入力に基づいて dataFetcherClass にメッセージを送信する 1 つの ViewController。(以下のコードは、罪のない人を保護するために変更されています)。

-(void) userPushedLocalBusinessButtons{
    [_businessDataFetcher fetchLocalData];
}

-(void) userPushedWorldwideBusinessButtons{
    [_businessDataFetcher fetchWorldwideData];
}

これらのアクションのデータ形式は同じです。これは、dataFetcher がその変更からデータを収集する場所です。したがって、BusinessDataFetcherClass には次のメソッドがあります。

-(void) fetchLocalData{
    _dataAddress = @"localData.json";
    [self fetchData]; 
}

-(void) fetchWorldwideData{
    _dataAddress = @"worldwideData.json";
    [self fetchData]; 
}

fetchData メソッドは非同期でデータをフェッチし、完了したら収集したデータとともに通知を送信します。ここで、fetchLocalData または fetchWorldwideData が実行されたときに ivar _dataAddress が変更されたことを確認する単体テストを作成したいと思います。

これは、コードを変更しないと明らかに不可能です。これは _dataAddress をパブリック プロパティにすることで簡単に解決できると言う人もいますが、それが 1 つの解決策です。もう 1 つの方法は、_dataAddress ivar の値を返すメソッドを作成することです。どちらの場合も、実際のコードベース自体の全体的な品質を向上させるのではなく、テストのためだけにコードを変更する必要があるため、どちらの選択肢にも完全に満足しているわけではありません。

私は 2 番目の選択肢にたどり着き、メソッド -(NSString *) dataAddress; を含めました。私の質問は (見出しで述べたように) これで問題ないのでしょうか? 私のデザインは問題ですか?TDD の最大の目標はリグレッションを回避することであることは明らかですが、コード全体の品質を向上させることも重要な目標であると考えています。時折毛羽立ちを追加することは予想されますか?

4

3 に答える 3

1

クラスの内部状態をテストしたくない - これは意味がありません。あなたが気にする唯一のことは、あなたのクラスが外の世界との相互作用で何をしているのかです (情報が内向きか外向きか、あるいはその両方か)。

別の言い方をすれば、クラスのテストを書いたら、目に見える動作を維持しながらクラスの実装 (内部) を書き直しても、テストが壊れることはありません。もしそうなら、あなたのテストはIMOに壊れています。

クラスの動作をテストする良い方法は、モック オブジェクトを使用することです。たとえば、iOS 用のOCMockを参照してください。

モック オブジェクトを使用すると、ターゲット クラスの動作をテストできます。これを行うには、特定の方法でターゲット クラスを記述する必要があります。例では、クラスをオフにしてハードコードされた特定のプロバイダーを使用するのではなく、ネットワーク プロバイダー クラスを渡すことができる必要があります。 (再利用可能なコンポーネントは、それ自体を構成するべきではありませんが、構成する必要があります)。このように設定すると、単体テスト クラスは、正しい URL がヒットされていることを確認するモック ネットワーク サービス プロバイダーを渡すことができます。

モック オブジェクトは一見複雑に見えるかもしれませんが、特別なテスト方法などで汚染することなく、正しいもの (ターゲット クラスの動作) をテストしています。

また、コードをテストしやすくすると、再利用しやすくなることにも注意してください。テスト ケースは、コードの 2 番目の「ユーザー」になります。

于 2013-01-21T13:37:20.960 に答える
1

fetchLocalData または fetchWorldwideData が実行されたときに ivar _dataAddress が変更されたことを確認する単体テストを作成したいと思います。

単体テストを作成するときは、クラスの外部動作をテストする必要があります。これは、クラスの実装の詳細です。データの取得方法を変更したい場合、単体テストが失敗してもクラスは機能する可能性があります。これにより、単体テストが面倒になり、役に立たなくなります。

非同期にデータを取得し、完了したら収集したデータとともに通知を送信します。

これは、そのクラスのメソッドの外部動作のようです。これは、チェックするためにテストを作成する必要があるものです。私はobjective-cを知らないので、ここに疑似コードの例があります:

setup expected local data (preferably with a mock)
call fetchLocalData on BusinessDataFetcherClass 
wait a little bit
check that local data is populated on ViewController

私のデザインは問題ですか?

ここでの設計により、テストの作成が少し難しくなりますが、大きな問題ではありません。特に、テストで発生する必要がある「待機」。テストが指摘している設計上の問題は、クラスに少なくとも 2 つの責任があることです。データのフェッチと非同期の管理です。これらの責任を分割すると、それぞれのテストが容易になります。

TDD の最大の目標はリグレッションを回避することであることは明らかですが、コード全体の品質を向上させることも重要な目標であると考えています。時折毛羽立ちを追加することは予想されますか?

この場合、おそらくこれ以上の毛羽立ちは必要ないと思いますが、単体テストでは時々発生します。テストを含むコードを作成すると、コードの 2 つのクライアント (テスト コードと製品コード) が作成されます。この「綿毛」の一部を強制したり、デザインの変更を強制したりするのは、異なるコンテキストで 2 つのクライアントを満足させる必要があるためです。良いニュースは、2 人のクライアントを簡単に満足させることができるデザインがあれば、必要に応じて 3 番目と 4 番目のクライアントをかなり簡単に満足させることができるということです。私にとって、この効果は TDD の最も重要な利点の 1 つです。

于 2013-01-21T14:48:05.657 に答える
0

私も ObjectiveC の開発者ではありませんが、あなたがこれを投稿している理由は、あなたが自分のコードを聞いていて、コードが何かが正しくないことを伝えているからだと思います。

fetchData通話の結果をどうするかお尋ねします。どこかでデータをレンダリングしていると思われます。iOS がそれをレンダリングしている場合、インスタンス変数をアサートするのではなく、アサートできるコールバックがどこかにある可能性があります。クラス内から UI を更新する場合、Observer を導入して UI とデータを取得するコードを分離すると、テストが容易になります。次に、テストをレシーバーとして登録し、そこで状態の変化をアサートできます。

それが役立つことを願っています!

ブランドン

于 2013-01-22T03:08:04.033 に答える