2012-09-08 22 views
11

私はMVVMパターンの正規版ではありません。これは基本的には初めてのプレーです。WPF MVVM LightユニットのテストViewModels

ビジネス・レイヤーとデータ・レイヤー(通常はサービスまたはEntity Frameworkによって作成されたエンティティを含む)を使用してビューを作成していました(通常のWPF)。

は現在、いくつかいじるの後に私はMVVM光から標準テンプレートを作成し、これをした:

ロケータ:

public class ViewModelLocator 
{ 
    static ViewModelLocator() 
    { 
     ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); 

     if (ViewModelBase.IsInDesignModeStatic) 
     { 
      SimpleIoc.Default.Register<IUserService, DesignUserService>(); 
     } 
     else 
     { 
      SimpleIoc.Default.Register<IUserService, IUserService>(); 
     } 

     SimpleIoc.Default.Register<LoginViewModel>(); 
    } 

    public LoginViewModel Login 
    { 
     get 
     { 
      return ServiceLocator.Current.GetInstance<LoginViewModel>(); 
     } 
    } 
} 

ログインのViewModel:

public class LoginViewModel : ViewModelBase 
{ 
    private readonly IUserService _userService; 

    public RelayCommand<Object> LoginCommand 
    { 
     get 
     { 
      return new RelayCommand<Object>(Login); 
     } 
    } 

    private string _userName; 
    public String UserName 
    { 
     get { return _userName; } 
     set 
     { 
      if (value == _userName) 
       return; 

      _userName = value; 
      RaisePropertyChanged("UserName"); 
     } 
    } 

    /// <summary> 
    /// Initializes a new instance of the LoginViewModel class. 
    /// </summary> 
    public LoginViewModel(IUserService userService) 
    { 
     _userService = userService; 

     _closing = true; 
    } 

    private void Login(Object passwordBoxObject) 
    { 
     PasswordBox passwordBox = passwordBoxObject as PasswordBox; 
     if (passwordBox == null) 
      throw new Exception("PasswordBox is null"); 

     _userService.Login(UserName, passwordBox.SecurePassword, result => 
     { 
      if (!result) 
      { 
       MessageBox.Show("Wrong username or password"); 
      } 
     }); 
    } 
} 

結合およびコマンドがそうそこに正常に動作します質問はありません。設計とテストのためのビジネスモックアップクラス:

public class DesignUserService : IUserService 
{ 
    private readonly User _testUser; 
    private readonly IList<User> _users; 

    public void Login(String userName, SecureString password, Action<Boolean> callback) 
    { 
     var user = _users.FirstOrDefault(u => u.UserName.ToLower() == userName.ToLower()); 

     if (user == null) 
     { 
      callback(false); 
      return; 
     } 

     String rawPassword = Security.ComputeHashString(password, user.Salt); 
     if (rawPassword != user.Password) 
     { 
      callback(false); 
      return; 
     } 

     callback(true); 
    } 

    public DesignUserService() 
    { 
     _testUser = new User 
     { 
      UserName = "testuser", 
      Password = "123123", 
      Salt = "123123" 
     }; 

     _users = new List<User> 
     { 
      _testUser 
     }; 
    } 
} 

UserDataは、データベース(Entity Framework)を呼び出す静的クラスです。

今、私は私のテストを持っている:

[TestClass] 
public class Login 
{ 
    [TestMethod] 
    public void IncorrectUsernameCorrectPassword() 
    { 
     IUserService userService = new DesignUserService(); 

     PasswordBox passwordBox = new PasswordBox 
     { 
      Password = "password" 
     }; 
     userService.Login("nonexistingusername", passwordBox.SecurePassword, b => Assert.AreEqual(b, false)); 
    } 
} 

今、私のテストはのViewModel自体ではなく、直接ビジネス層にはありません。

は基本的に私は2つの質問があります。正しい道に

  • アムIを、あるいは私のパターンの実装に根本的な欠陥があるのでしょうか?

  • ViewModelをテストするにはどうすればよいですか?

答えて

15

ビューモデルには、テストに値するコードの1つがあります。これはLoginメソッドです。それがプライベートであることを考えると、それはLoginCommandによってテストされるべきです。

これで、基本的なビジネスロジックのテストが既に完了しているときに、コマンドをテストする目的はどのようなものでしょうか?目的は、ビジネスロジックが正しいパラメータと呼ばれていることを確認することです。

このようなテストにはどのようになりますか? mockを使用します。 FakeItEasyと例:予想通り、あなたがそのコマンドを確認

var userServiceFake = A.Fake<IUserService>(); 
var testedViewModel = new LoginViewModel(userServiceFake); 

// prepare data for test 
var passwordBox = new PasswordBox { Password = "password" }; 
testedViewModel.UserName = "TestUser"; 

// execute test 
testedViewModel.LoginCommand.Execute(passwordBox); 

// verify 
A.CallTo(() => userServiceFake.Login(
    "TestUser", 
    passwordBox.SecurePassword, 
    A<Action<bool>>.Ignored) 
).MustHaveHappened(); 

この方法では、ビジネス層を呼び出します。パラメータを一致させる場合はAction<bool>が無視されます。Action<T>Func<T>との一致は難しく、通常はそれほど価値がありません。

ほとんどのノート:

  • あなたは(これは見るために属している必要があり、ビューモデルが要求のいずれかのポップアップを表示するビューを通知しなければならない)ビューモデルにメッセージボックスコードを有する再考したい場合があります。そうすることで、ビューモデルのテストを通してより多くのことを可能にします(例えば、そのAction引数を無視する必要はありません)
  • 一部の人は、INotifyPropertyChangedのプロパティ(あなたの場合はUserName)をテストします。そのイベントはプロパティ値が変更されたときに発生します。これは定型コードが多いため、このプロセスを自動化するツール/ libraryを使用することが強く推奨されます。
  • の2つのテストセット(上記の例のように)と、基礎となるビジネスロジック(元のテスト)の2つのセットがあります。MVVMでは、VMはビジネスロジックを持たず、むしろデータの再配置/ビューレイヤーの準備に専念するために、ほとんど使われていないかもしれない余分なレイヤーですが、それは全体的なポイントです。
+0

ありがとうございました!しかし、私は探している、あなたはまだコマンドの結果をテストしているか、またはコマンドが正しく実行されただけですか? – YesMan85

+1

@ Rogier21:* "コマンドの結果" *であなたは何を理解していますか?直接的な結果は、ビジネス層への呼び出し(これは上のテストでカバーされています)です。間接的な結果は、ビジネス層のコードが何であれ、どんなものでもあります。そして、それはビジネス層テストでテストする必要があります(私が見る限り、あなたは既に 'DesignUserService'テストでそれを行います)。 –

+0

はい、私はあなたのポイントを参照してください。あなたは2つのテストを受けることになります.1つはビジネスレイヤーのメソッドの適切な結果をテストするため(Loginなど)、もう1つはViewModelからメソッドが正しく呼び出されているかどうかをテストすることですか? – YesMan85

関連する問題