12

私はMVVMパターンの常連ではなく、基本的にこれは初めてのMVVMパターンです。

私が以前行っていたのは(「通常の」WPF)、ビジネスレイヤーとおそらくデータレイヤー(通常はサービスまたはエンティティフレームワークによって作成されたエンティティを含む)を使用してビューを作成することでした。

少しいじった後、MVVM Lightから標準テンプレートを作成し、これを実行しました。

ロケータ:

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自体ではなく、直接Businessレイヤーで行われます。

基本的に私は2つの質問があります:

  • 私は正しい道を進んでいますか、それともパターンの実装に根本的な欠陥がありますか?

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

4

1 に答える 1

15

ビューモデルには、テストする価値のある関連するコードが1つあります。それは、Loginメソッドです。プライベートであることを考えると、を介してテストする必要がありますLoginCommand

さて、基礎となるビジネスロジックのテストがすでにある場合、コマンドをテストする目的は何ですか?目的は、ビジネスロジックが呼び出され正しいパラメータを使用していることを確認することです。

そのようなテストをどのように行うのですか?モックを使用して。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あなたの場合)-そのイベントはプロパティ値が変更されたときに発生します。これは多くの定型コードであるため、ツール/ライブラリを使用してこのプロセスを自動化することを強くお勧めします。
  • 2セットのテストが必要です。1つはビューモデル用(上記の例のように)、もう1つは基盤となるビジネスロジック用(元のテスト)です。MVVMでは、VMはほとんど役に立たないように見えるかもしれない追加のレイヤーですが、それが全体のポイントです。そこにはビジネスロジックがなく、ビューレイヤーのデータの再配置/準備に重点を置いています。
于 2012-09-08T23:00:36.613 に答える