framework c# wpf unit-testing mvvm mvvm-light

c# - framework - WPF MVVM Prueba de unidad de luz ViewModels



mvvm framework (1)

Su modelo de vista tiene una pieza relevante de código que vale la pena probar, que es el método de Login . Dado que es privado, se debe probar a través de LoginCommand .

Ahora, uno podría preguntarse, ¿cuál es el propósito de probar el comando cuando ya tiene una prueba para la lógica de negocio subyacente? El objetivo es verificar que se llame a la lógica de negocios y con los parámetros correctos .

¿Cómo funciona uno con tal prueba? Mediante el uso de simulacro . Ejemplo con 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();

De esta forma, verifica que el comando llame a la capa empresarial como se esperaba. Tenga en cuenta que Action<bool> se ignora cuando se hacen coincidir parámetros: es difícil hacer coincidir Action<T> y Func<T> y, por lo general, no vale la pena.

Algunas notas:

  • Es posible que desee reconsiderar el hecho de tener un código de cuadro de mensaje en el modelo de vista (esto debe pertenecer a la vista, el modelo de vista debe solicitar o notificar a la vista para mostrar la ventana emergente). Si lo hace, también hará que sea posible probar el modelo de vista (por ejemplo, sin necesidad de ignorar ese argumento de Action )
  • Algunas personas prueban INotifyPropertyChanged propiedades de INotifyPropertyChanged ( UserName en su caso): ese evento se produce cuando cambia el valor de la propiedad. Dado que esto es mucho código repetitivo, se recomienda encarecidamente usar la herramienta / biblioteca para automatizar este proceso.
  • Usted quiere tener dos conjuntos de pruebas , una para el modelo de vista (como en el ejemplo anterior) y otra para la lógica de negocios subyacente (su prueba original). En MVVM, VM es esa capa adicional que podría parecer de poca utilidad, pero ese es el punto, no tener lógica comercial allí y enfocar el reajuste / preparación de datos para la capa de vistas.

No soy habitual en el patrón MVVM y esta es básicamente la primera vez que juego con él.

Lo que solía hacer (WPF "normal") era crear mis vistas con una capa de negocios y tal vez una capa de datos (que generalmente contiene mis entidades creadas por un servicio o Entity Framework).

Ahora, después de jugar, creé una plantilla estándar de MVVM Light e hice esto:

Locador:

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>(); } } }

Iniciar sesión 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"); } }); } }

La vinculación y los comandos funcionan bien, por lo que no hay preguntas. Clase de maqueta empresarial para el diseño y el tiempo de prueba:

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 es una clase estática que realiza llamadas a la base de datos (Entity Framework).

Ahora tengo mi prueba:

[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)); } }

Ahora mi prueba no está en ViewModel sino directamente en la capa Business.

Básicamente tengo 2 preguntas:

  • ¿Estoy en el camino correcto o hay un defecto fundamental en la implementación de mi patrón?

  • ¿Cómo puedo probar mi ViewModel?