Red de conocimiento informático - Material del sitio web - Cómo escribir pruebas unitarias en ReactiveCocoa

Cómo escribir pruebas unitarias en ReactiveCocoa

Arquitectura MVVM

MVVM Alto Nivel

En la arquitectura MVVM, las vistas y los controladores de vistas generalmente se consideran como un todo. En arquitecturas MVC anteriores, el controlador de vista realizaba gran parte del mapeo de datos y la interacción entre la vista y el modelo, pero ahora es el modelo de vista quien lo realiza.

No existe un mecanismo obligatorio para actualizar un modelo de vista o una vista, pero generalmente elegimos ReactiveCocoa, que escuchará los cambios en el modelo, luego asignará estos cambios a las propiedades del modelo de vista y realizará algunos negocios. lógica.

Por ejemplo, tengo un modelo que contiene una propiedad dateAdded y quiero escuchar los cambios y actualizar la propiedad dateAdded del modelo de vista. Pero el tipo de datos de la propiedad dateAdded del modelo es NSDate, mientras que el tipo de datos del modelo de vista es NSString, por lo que el enlace de datos se realiza en el método init del modelo de vista, pero se requiere la conversión del tipo de datos. El código de muestra es el siguiente:

RAC(self, dateAdded) = [RACObserve(self.model, dateAdded) map: ^(NSDate*date){

return [[ViewModel dateFormatter] stringFromDate :date];

}];

ViewModel llama a dateFormatter para la conversión de datos. Este método dateFormatter se puede reutilizar en otros lugares. Luego, el controlador de vista escucha la propiedad dateAdded del modelo de vista y se vincula a la propiedad de texto de la etiqueta.

RAC(self.label, text) = RACObserve(self.viewModel, dateAdded);

Ahora que hemos resumido la lógica de convertir fechas en cadenas en el modelo de vista, esto hace que el código sea comprobable, reutilizable y ayuda a optimizar los controladores de vista.

Ejemplo de inicio de sesión

Ejemplo de inicio de sesión

Como se muestra en la figura, esta es una pantalla de inicio de sesión simple: hay dos cuadros de entrada para ingresar el nombre de usuario y la contraseña y un botón de inicio de sesión. Después de que el usuario ingresa su nombre de usuario y contraseña y hace clic en el botón de inicio de sesión, puede iniciar sesión correctamente. Pero existen algunas restricciones: el nombre de usuario debe coincidir con el formato del correo electrónico y la contraseña debe tener más de 6 dígitos. Solo cuando se cumplen estas dos condiciones, se puede hacer clic en el botón; de lo contrario, no se puede hacer clic en el botón. Puede descargar el código de muestra desde github.

Primero dibujamos la interfaz. Definí un LoginView y lo hice responsable de dibujar la pantalla de inicio de sesión.

Luego, el método viewDidLoad en LoginViewController llama a buildViewHierarchy para cargarlo

#pragma mark - ciclo de vida

- (void)viewDidLoad {

[super viewDidLoad];

// Construir la jerarquía de vistas

[self buildViewHierarchy]

// Vincular datos

[self bindData];< / p>

// Manejar eventos

[self handleEvents];

}

- (void)buildViewHierarchy

{

[self.view addSubview: self.rootView];

[self.rootView mas_makeConstraints: ^(MASConstraintMaker *make) {

make.edges.equalTo ( self.view);

}];

}

A continuación, consideraremos cómo interactúa la interfaz de usuario y qué clases diseñar e implementar para manejar estas interacciones. . Dado que el nombre de usuario y la contraseña deben ajustarse al formato de validación para hacer clic en el botón de inicio de sesión, siempre debemos escuchar las propiedades de texto de nombre de usuarioTextField y contraseñaTextField para manejar la interacción de la interfaz de usuario, dejando la validación y conversión de datos al ViewModel de la arquitectura MVVM. . Por lo tanto, definimos un LoginViewModel y heredamos RVMViewModel. Este RVMViewModel tiene un atributo activo para indicar si ViewModel está activo. Cuando activo es SÍ, la interfaz de usuario se actualiza o muestra; cuando está activo es NO, la interfaz de usuario no se actualiza ni se muestra; Ocultar la interfaz de usuario.

@interface LoginViewModel: RVMViewModel

#pragma mark - Estado de la interfaz de usuario

/*

@brief nombre de usuario

*/

@property (copia, no atómica) NSString *nombre de usuario;

/*

@brief Contraseña

*/

@property (copia, no atómica) NSString *contraseña;

#pragma mark - procesamiento de eventos

/*

@brief procesamiento Si el ID de usuario y la contraseña del usuario son válidos para hacer clic en botones y eventos de inicio de sesión

*/

@property (copia, no atómico, seguro) RACCommand *loginCommand;

# pragma mark - Método

- (RACSignal *)isValidUsernameAndPasswordSignal;

@end

También hay un atributo loginCommand arriba, se introducirá el método isValidUsernameAndPasswordSignal en detalle más adelante. Después de definir la clase LoginViewModel, use LoginViewModel como composición y delega en LoginViewController e inicialícelo usando Lazy Initialization.

@interface LoginViewController ()

#pragma mark - Ver modelo

@property (fuerte, no atómico) LoginViewModel *loginViewModel;

@end

@implementation LoginViewController

#pragma mark - Accesor personalizado

- (LoginViewModel *)loginViewModel

-(LoginViewModel *) loginViewModel

{

if(!_loginViewModel) {

_loginViewModel = [LoginViewModel nuevo];

}

return _loginViewModel;

}

Finalmente, llame al método bindData para realizar el enlace de datos

- (void)bindData

{

RAC(self.loginViewModel, nombre de usuario) = self.rootView.usernameTextField.rac_textSignal;

RAC(self.loginViewModel, contraseña) = self.rootView.passwordTextField.rac_textSignal

}

Prueba de vinculación de datos

Si nombre de usuarioTextField.text y contraseñaTextField.text se han vinculado a loginViewModel.username y loginViewModel.password, entonces nombre de usuarioTextField.text y contraseñaTextField.text Se producirán cambios en pérdida de datos. Cualquier cambio en el texto y contraseñaTextField.text provocará cambios en loginViewModel.username y contraseñaTextField.text, lo que provocará cambios en loginViewModel.username y contraseñaTextField.text.

Entonces el caso de prueba se puede diseñar así:

Caso de prueba de enlace de datos

Utilice kiwi para escribir la prueba de la siguiente manera:

SPEC_BEGIN(LoginViewControllerSpec)

describe(@"LoginViewController ", ^{

__block LoginViewController *controller = nil;

beforeEach(^{

controlador = [LoginViewController nuevo];

p>

[vista del controlador];

});

afterEach(^{

controlador = nil;

} );

describir(@"Vista raíz", ^{

__block LoginView *rootView = nil;

beforeEach(^{

rootView = controlador.rootView;

}

contexto(@"cuando se cargó la vista", ^{

);

it(@" debería vincular datos ", ^{

rootView.usernameTextField.text = @"samlau";

rootView.passwordTextField.text = @"... .".passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

[[controller.loginViewModel.username debería] igual: rootView.password debería] igual: rootView.passwordTextField.text];

}) ;

} );

});

});

SPEC_END

Hay dos puntos. en esta prueba es necesario resaltar y explicar:

Después de inicializar el controlador, el controlador debe llamar al método de vista para cargar la vista del controlador; de lo contrario, no llamará al método viewDidLoad.

Si algunos usuarios no entienden cómo un controlador administra el ciclo de vida de la vista, puede leer "Los controladores de vista crean instancias de sus jerarquías de vistas" en la documentación de iOS de la Guía de programación del controlador de vista. Al acceder a sus vistas》Capítulo

Cargar vistas en la memoria desde un documento de Apple

nombre de usuarioTextField y contraseñaTextField deben llamar al método sendActionsForControlEvents para notificar a la interfaz de usuario que se ha actualizado.

[rootView.usernameTextField sendActionsForControlEvents:UIControlEventEditingChanged];

[rootView.passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

Inicialmente, no llamé al método sendActionsForControlEvents, lo que provocó loginViewModel. Las propiedades de nombre de usuario y loginViewModel.password no están actualizadas. En este punto comencé a pensar: ¿se necesitan otras condiciones para activar una actualización? Como estoy usando la propiedad rac_textSignal de UITextField, miré su código fuente:

- (RACSignal *)rac_textSignal {

@weakify(self);

return [[[[[RACSignal

diferir: ^{

@strongify(self);

return [RACSignal return: self]; p>

}]

concat: [self rac_signalForControlEvents.UIControlEventEditingChanged | UIControlEventEditingDidBegin]]

mapa: ^(UITextField *x) {

return x .text;

}]

takeUntil: self.rac_willDeallocSignal]

setNameWithFormat: @"@ -rac_textSignal", self.rac_description];

}

Como se puede ver en el código fuente, un objeto RACSignal solo se puede crear cuando se activa el evento UIControlEventEditingChanged o UIControlEventEditingDidBegin.

Autor: Sam_Lau

Enlace original: /p/412875512bd1