2

私は単体テストについてますます読んでおり、それを機能させることにしました。リポジトリ パターン、依存性注入、および EF を使用して、ASP.NET MVC で記述されたプロジェクトを掘り出しました。私の最初のタスクは、コントローラーの単体テストでした。テストするコントローラーのスニペットを次に示します。

 IUserRepository _userRepository;
    IAttachmentRepository _attachmentRepository;
    IPeopleRepository _peopleRepository;
    ICountryRepository _countryRepository;

    public UserController(IUserRepository userRepo, IAttachmentRepository attachRepo, IPeopleRepository peopleRepo, ICountryRepository countryRepo)
    {
        _userRepository = userRepo;
        _attachmentRepository = attachRepo;
        _peopleRepository = peopleRepo;
        _countryRepository = countryRepo;
    }

    public ActionResult Details()
    {
        UserDetailsModel model = new UserDetailsModel();

        foreach (var doc in _attachmentRepository.GetPersonAttachments(Globals.UserID))
        {
            DocumentItemModel item = new DocumentItemModel();
            item.AttachmentID = doc.ID;
            item.DocumentIcon = AttachmentHelper.GetIconFromFileName(doc.StoragePath);
            item.DocumentName = doc.DocumentName;
            item.UploadedBy = string.Format("{0} {1}", doc.Forename, doc.Surname);
            item.Version = doc.VersionID;

            model.Documents.Add(item);
        }

        var person = _peopleRepository.GetPerson();
        var address = _peopleRepository.GetAddress();

        model.PersonModel.DateOfBirth = person.DateOfBirth;
        model.PersonModel.Forename = person.Forename;
        model.PersonModel.Surname = person.Surname;
        model.PersonModel.Title = person.Title;

        model.AddressModel.AddressLine1 = address.AddressLine1;
        model.AddressModel.AddressLine2 = address.AddressLine2;
        model.AddressModel.City = address.City;
        model.AddressModel.County = address.County;
        model.AddressModel.Postcode = address.Postcode;
        model.AddressModel.Telephone = address.Telephone;

        model.DocumentModel.EntityType = 1;
        model.DocumentModel.ID = Globals.UserID;
        model.DocumentModel.NewFile = true;

        var countries = _countryRepository.GetCountries();

        model.AddressModel.Countries = countries.ToSelectListItem(1, c => c.ID, c => c.CountryName, c => c.CountryName, c => c.ID.ToString());

        return View(model);
    }

Details メソッドをテストし、次のクエリを実行したいと考えています。

1) Globals.UserID プロパティは、セッション オブジェクトから現在のユーザーを取得します。これを簡単にテストするにはどうすればよいですか (組み込みの VS2010 単体テストと Moq を使用しています)

2) ここで AttachmentHelper.GetIconFromFileName() を呼び出しています。これは単にファイルの拡張子を調べてアイコンを表示するだけです。また、添付ファイル リポジトリで GetPersonAttachments を呼び出し、GetPerson、GetAddress、および GetCountries を呼び出し、作成された拡張メソッドを呼び出して、List を SelectListItem の IEnumerable に変換します。

このコントローラーのアクションは悪い習慣の例ですか? 多くのリポジトリと他のヘルパー メソッドを使用しています。私が見る限り、この単一のアクションの単体テストには、非常に多くのコードが必要です。これは逆効果ですか?

テスト プロジェクトで単純なコントローラーを単体テストすることは 1 つのことですが、このような実際のコードに入ると、モンスターになる可能性があります。

私の質問は、テストを容易にするためにコードをリファクタリングする必要があるか、それとも現在のコードを満たすためにテストをより複雑にする必要があるかということだと思います。

4

4 に答える 4

3

複雑なテストは、複雑なコードと同じくらい悪いものです。バグが発生しやすくなります。したがって、テストを単純に保つために、アプリケーション コードをリファクタリングしてテストを容易にすることは、一般的には良い考えです。たとえば、マッピング コードを Details() メソッドから別のヘルパー メソッドに引き出す必要があります。その後、これらのメソッドを非常に簡単にテストでき、Details() のすべてのクレイジーな組み合わせをテストすることについてそれほど心配する必要はありません。

以下に人物と住所のマッピング部分を抜粋しましたが、さらに分解することもできます。私が言いたかったことのアイデアをあなたに伝えたかっただけです。

    public ActionResult Details() {
        UserDetailsModel model = new UserDetailsModel();

        foreach( var doc in _attachmentRepository.GetPersonAttachments( Globals.UserID ) ) {
            DocumentItemModel item = new DocumentItemModel();
            item.AttachmentID = doc.ID;
            item.DocumentIcon = AttachmentHelper.GetIconFromFileName( doc.StoragePath );
            item.DocumentName = doc.DocumentName;
            item.UploadedBy = string.Format( "{0} {1}", doc.Forename, doc.Surname );
            item.Version = doc.VersionID;

            model.Documents.Add( item );
        }

        var person = _peopleRepository.GetPerson();
        var address = _peopleRepository.GetAddress();

        MapPersonToModel( model, person );

        MapAddressToModel( model, address );

        model.DocumentModel.EntityType = 1;
        model.DocumentModel.ID = Globals.UserID;
        model.DocumentModel.NewFile = true;

        var countries = _countryRepository.GetCountries();

        model.AddressModel.Countries = countries.ToSelectListItem( 1, c => c.ID, c => c.CountryName, c => c.CountryName, c => c.ID.ToString() );

        return View( model );
    }

    public void MapAddressToModel( UserDetailsModel model, Address address ) {
        model.AddressModel.AddressLine1 = address.AddressLine1;
        model.AddressModel.AddressLine2 = address.AddressLine2;
        model.AddressModel.City = address.City;
        model.AddressModel.County = address.County;
        model.AddressModel.Postcode = address.Postcode;
        model.AddressModel.Telephone = address.Telephone;
    }

    public void MapPersonToModel( UserDetailsModel model, Person person ) {
        model.PersonModel.DateOfBirth = person.DateOfBirth;
        model.PersonModel.Forename = person.Forename;
        model.PersonModel.Surname = person.Surname;
        model.PersonModel.Title = person.Title;
    }
于 2012-12-14T03:13:32.810 に答える
2

サブジェクトについて少し詳しく説明したかっただけです。単体テストしようとしているのはロジックです。コントローラーにはあまりありません。したがって、この特定のケースでは、ビューではなくモデルを返す extract メソッドを実行します。モックされたリポジトリをコントローラー オブジェクトに挿入します。そして、マッピングを実行した後、すべてのプロパティが期待値で満たされることが保証されます。これを行う別の方法は、JSON を生成し、すべてのプロパティが適切に入力されるようにすることです。ただし、マッピング部分自体に単体テストを配置するように努めてから、統合テスト用に BDD を検討します。

于 2012-12-14T03:04:01.433 に答える
1

モデル構築コードのすべてをモデル自体のコンストラクターに移動します。私は、コントローラーをごく少数の単純なタスクに制限しておくことを好みます。

  • 適切なビューの選択 (コントローラー アクションで複数のビューが許可されている場合)
  • 正しいビュー モデルの選択
  • 権限/セキュリティ
  • モデルの検証を表示

したがって、詳細コントローラーがはるかに単純になり、テストがより管理しやすくなります。

public ActionResult Details() {
    return View(new UserDetailsModel(Globals.UserId);
}

コントローラーがタイトでテスト可能になったので、モデルを見てみましょう。

    public class UserDetailsModel {
        public UserDetailsModel(int userId) {
           ... instantiation of properties goes here...
         }

        ... public properties/methods ...
    }

繰り返しますが、モデル内のコードはカプセル化されており、特にそのプロパティについてのみ考慮する必要があります。

于 2012-12-14T17:39:06.837 に答える
0

@KevinM1 で既に述べたように、TDD を実践している場合 (質問にそのタグがある場合)、実装前にテストを作成しています。

最初に、コントローラーの Detail メソッドのテストを作成します。このテストを作成すると、ユーザーを UserDetailsModel にマップする必要があることに気付きます。テストを書くとき、抽象化の背後でテストしたいものの実際の実装に属さない「複雑さを隠す」。この場合、おそらく IUserDetailModelMapper を作成します。この最初のテストが書かれたら、コントローラを作成してグリーンにします。

public class UserController
{
   ctor(IUserRepository userRepo, IUserDetailModelMapper mapper){...}

   public ActionResult Details()
   {
      var model = _mapper.Map(_userRepo.GetPerson());
      return View(model);
   }
}

後でマッパーのテストを作成するときに、Globals.UserId という静的プロパティを使用する必要があると述べました。通常、可能であれば静的データは避けますが、これがレガシー システムである場合は、これを「オブジェクト化」してテスト可能にする必要があります。簡単な方法の 1 つは、このようなインターフェイスの背後に隠すことです...

interface IGlobalUserId
{
  int GetIt();
}

...そして、静的データを使用する実装を行います。これからは、代わりにこのインターフェースを注入して、それが静的データであるという事実を隠すことができます。

「AttachmentHelper」も同様です。インターフェイスの後ろに隠します。ただし、一般的には、XXXHelpers には警鐘が鳴るはずです。これは、あるべき場所 (オブジェクトの一部) にメソッドを配置するのではなく、さまざまな種類のものを混ぜ合わせて配置することを示していると言えます。

于 2013-02-08T16:56:13.250 に答える