Validación de datos: un enfoque diferente

Verificar los datos en la aplicación ingresada por el usuario u obtenida de otra manera en el sentido clásico implica el uso de solo dos expresiones en el código: VERDADERO y FALSO. En otra realización, se usan excepciones que claramente no están destinadas a esto. ¿Hay alguna opción mejor?

La validación se realiza mediante los llamados Validadores (que son solo una parte de todo el proceso de validación de datos). El artículo Validación de datos de usuario del servidor proporciona una versión interesante de la implementación del validador, pero hay varios matices en forma de localización de mensajes y el formato de error en.

Considere el formato de error primero.

El enfoque propuesto es para el método de validación que verifica los datos para devolver una colección (matriz, lista, etc.) de cadenas en lugar de valores booleanos o arrojando excepciones. Tal formato será más flexible e informativo.

Daré un ejemplo en Java:

import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public interface ValidateUser {
	String OK = "OK:";
	String FAIL= "FAIL:";
	Collection<String> apply(String username, String password, String email);
	default Collection<String> passwordValidate(String password){
		var result = new ArrayList<String>(1);
		int size = password.trim().length();
		if(size < 3 || size > 20) result.add("Password error:too short value or too long name. Password must be greater or 3 characters and smaller then 20 simbols.");
		return result;
	}
	default Collection<String> usernameValidate(String name){
		var result = new ArrayList<String>(1);
		int size = name.trim().length();
		if(size < 3 || size > 30) result.add("Username error:too short or too long name. Name must be greater or 3 characters and smaller then 30 simbols.");
		return result;
	}
	default Collection<String> emailValidate(String email){
		var result = new ArrayList<String>(1);
		String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(email);
		if (!matcher.find()) result.add("Email error:" + email + " is not valid");
		return result;
	}
	class Default implements ValidateUser{

		@Override
		public Collection<String> apply(String username, String password,
				String email) {
			var errors = passwordValidate(password.trim());
			errors.addAll(usernameValidate(username.trim()));
			errors.addAll(emailValidate(email.trim()));
			return errors;
		}
		
	}
}

Como puede ver aquí, todos los métodos devuelven una colección de cadenas.

Ejemplo de prueba de unidad:

        @Test
	void validateUserTest() {
		
		var validate = new ValidateUser2.Default();
		var result = validate.apply("aaa", "qwe", "aaa@mail.ru");
		assertTrue(result.isEmpty());
		result = validate.apply("aaa", "qwe", "");
		assertFalse(result.isEmpty());
		assertEquals(1, result.size());
		
		result = validate.apply("aa", "qwe", "aaa@mail.ru");
		assertFalse(result.isEmpty());
		assertEquals(1, result.size());
		
		result = validate.apply("aaa", "qwe", "@mail.qweqwe");
		assertFalse(result.isEmpty());
		assertEquals(1, result.size());
		
		result = validate.apply("aa", "qw", "");
		assertFalse(result.isEmpty());
		assertEquals(3, result.size());
	}

Ahora considere la localización de mensajes de error.

Ejemplo de nuevo en Java:

public interface LocalizedValidation {
	String OK = "OK:";
	String FAIL= "FAIL:";
	Collection<String> apply(String username, String password, String email, Locale locale);
	default Collection<String> passwordValidate(String password, ResourceBundle bundle){
		var result = new ArrayList<String>(1);
		int size = password.trim().length();
		if(size < 3 || size > 20) result.add(bundle.getString("password"));
		return result;
	}
	default Collection<String> usernameValidate(String name, ResourceBundle bundle){
		var result = new ArrayList<String>(1);
		int size = name.trim().length();
		if(size < 3 || size > 30) result.add(bundle.getString("username"));
		return result;
	}
	default Collection<String> emailValidate(String email, ResourceBundle bundle){
		var result = new ArrayList<String>(1);
		String regex = "^[\\w!#$%&'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$";
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(email);
		if (!matcher.find()) result.add(bundle.getString("username")+email);
		return result;
	}
	class Default implements LocalizedValidation{

		@Override
		public Collection<String> apply(String username, String password,
				String email, Locale locale) {
			ResourceBundle bundle = ResourceBundle.getBundle("errors", locale);
			var errors = passwordValidate(password.trim(), bundle);
			errors.addAll(usernameValidate(username.trim(), bundle));
			errors.addAll(emailValidate(email.trim(), bundle));
			return errors;
		}
	}
}


	@Test
	void localizedUserTest() {
		var validate = new LocalizedValidation.Default();
		var result = validate.apply("aaa", "qwe", "aaa@mail.ru", Locale.ENGLISH);
		assertTrue(result.isEmpty());
		result = validate.apply("aaa", "qwe", "", Locale.ENGLISH);
		assertFalse(result.isEmpty());
		assertEquals(1, result.size());
		System.out.println(result.iterator().next());
		
		result = validate.apply("aaa", "qwe", "", new Locale("ru"));
		assertFalse(result.isEmpty());
		assertEquals(1, result.size());
		System.out.println(result.iterator().next());
	}

Los archivos de localización están en src / main / resources.

errors_ru.properties:

mail=Email :   Email:
username=  :     .
password=   :   .

errores.propiedades:

mail=Email error: is not valid:
username=Username error:too short or too long name. Name must be greater or 3 characters and smaller then 30 simbols.
password=Password error:too short value or too long name. Password must be greater or 3 characters and smaller then 20 simbols.

Espero que otros programadores que escriben en sus propios idiomas encuentren este enfoque práctico y conveniente y puedan aplicarlo en casa.

PS

Una variante del método de autenticación de usuario que devuelve la implementación de la interfaz java.util.Map.Entry <K, V> y puede contener tanto el objeto de usuario como los datos de error como una cadena (este enfoque también se puede usar para evitar devolver Null de método cuando se espera el objeto de usuario):

@Override
public Entry<String, User> auth(String username, String password, String email) {
	var errors = passwordValidation.apply(password.trim());
	errors.addAll(userNameValidation.apply(username.trim()));
	errors.addAll(emailValidation.apply(email.trim()));
	if(!errors.isEmpty()) {
		return REntry.ofI(null, errors.stream().collect(Collectors.joining(";")));
	}else {
		try {
			var passwordEncrypted = new Encrypt().apply(password.trim());
			var user = new User.Default(0L, username.trim(), email.trim(), passwordEncrypted, "").create(dataSource);
			return REntry.ofI(user, "user is null!");
		} catch (RuntimeException e) {
			return REntry.ofI(null, e.getMessage());
		}
	}
}

Por supuesto, hay un artículo maravilloso sobre el centro sobre la validación en Java , pero el enfoque que se sugiere allí puede usarse lejos de todos los casos y se basa en anotaciones y excepciones.

Al final del artículo, un par de enlaces útiles sobre pruebas:

  1. Antipatrones de pruebas de software
  2. Conceptos de pruebas automáticas

Source: https://habr.com/ru/post/undefined/


All Articles