Metódus szintű validáció Bean Validation 1.1-ben

Posted by | No Tags | Szoftverfejlesztés | Nincs hozzászólás a(z) Metódus szintű validáció Bean Validation 1.1-ben bejegyzéshez

A bizalom alapja a sűrű ellenőrzés.

Egy üzleti metódus bemenő paramétereinek és visszatérési értékének leellenőrzése elkerülhetetlen a mindennapokban, az alábbi bejegyzés azt mutatja be, hogy a Bean Validation szabvány legújabb verziója milyen támogatást nyújt ennek megvalósítására.

A paraméterek ellenőrzésére gyakran születnek nálunk ehhez hasonló kódok:

public class FooService {

    public Foo findFoo(String id) {
        if (id == null || id.isEmpty()) {
            return null;
        }
        Foo foo = null;
        // . . .
        return foo;
    }

}

Foo foo = fooService.findFoo(id);
if (foo != null) {
	// . . .
}

Az ilyen típusú ellenőrzések fontosságát felismerve a Bean Validation szabvány 1.1-es verziója már támogatja a metódus szintű validációk létrehozását is. A metódusok lefutásához különböző előfeltételeket és utófeltételeket rendelhetünk annotációk megadásával, amelyekkel garantálható a bemenő paraméterek és kimenő paraméter érvényessége. A bemenő paraméterek egyéni és csoportos ellenőrzésére is van mód.

A feltételek megadásához használhatunk beépített annotációkat (javax.validation.constraints csomag) vagy akár saját annotációkat is. Ezzel elérhető az ellenőrző kódrészletek rugalmas újrafelhasználása. A metódus szintű validációk alkalmazásával tömörebb és átláthatóbb kódot kaphatunk, ez a fenti kódrészlet esetében így néz ki:

public class FooService {

    @NotNull
    public Foo findFoo(@NotNull @Size(min = 1) String id) {
        Foo foo = null;
        // . . .
        return foo;
    }

}

Foo foo = fooService.findFoo(id);
// . . .

A metódus szintű validációk működését integrációs tesztekkel ellenőrizhetjük. A tesztek lefutásához a következő függőségekre van szükség:

<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0-b07</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.0.1.Final</version>
</dependency>

A validációk elvégzését a javax.validation.ExecutableValidator interfész végzi. Az integrációs tesztben így példányosíthatjuk ezt az interfészt:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
ExecutableValidator methodValidator = validator.forExecutables();

A fenti kódrészlet leellenőrzésére a következő teszt esetek szolgálnak:

@Test
public void shouldRaiseConstraintViolationOnFindFooMethod() throws NoSuchMethodException {
    FooService object = new FooService();
    Method method = FooService.class.getMethod("findFoo", String.class);
    Object[] parameterValues = new Object[]{null};

    ExecutableValidator methodValidator = validator.forExecutables();
    Set<ConstraintViolation> violations = methodValidator.validateParameters(object, method, parameterValues);

    assertEquals(1, violations.size());
    Class constraintType = violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType();
    assertEquals(NotNull.class, constraintType);
}

@Test
public void shouldRaiseConstraintViolationOnFindFooMethod2() throws NoSuchMethodException {
    FooService object = new FooService();
    Method method = FooService.class.getMethod("findFoo", String.class);
    Object[] parameterValues = new Object[]{new String()};

    ExecutableValidator methodValidator = validator.forExecutables();
    Set<ConstraintViolation> violations = methodValidator.validateParameters(object, method, parameterValues);

    assertEquals(1, violations.size());
    Class constraintType = violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType();
    assertEquals(Size.class, constraintType);
}    

@Test
public void shouldRaiseNoConstraintViolationOnFindFooMethodReturnValue() throws NoSuchMethodException {
    FooService object = new FooService();
    Method method = FooService.class.getMethod("findFoo", String.class);
    Object returnValue = null;

    ExecutableValidator methodValidator = validator.forExecutables();
    Set<ConstraintViolation> violations = methodValidator.validateReturnValue(object, method, returnValue);

    assertEquals(1, violations.size());
    Class constraintType = violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType();
    assertEquals(NotNull.class, constraintType);
}

Hasznossági szempontból érdemes megemlíteni a @Valid annotációt, melynek használatára íme egy példa:

public class Foo {

    @NotNull
    @Size(min = 1)
    protected String id;

    @Valid
    protected Bar bar;

    public Foo(String id, Bar bar) {
        this.id = id;
        this.bar = bar;
    }
}

public class Bar {

    @NotNull
    @Size(min = 1)
    private String id;

    public Bar(String id) {
        this.id = id;
    }

}

public class FooService {

    public void createFoo(@NotNull @Valid Foo foo) {
        // . . .
    }

}

Itt a createFoo metódusnak átadott Foo objektum attribútumain is lefutnak automatikusan az ellenőrzések, rekurzívan.

A fenti kódrészlet leellenőrzésére a következő teszt esetek szolgálnak a FooServiceIT osztályban:

    @Test
    public void shouldRaiseNoConstraintViolationOnCreateFooMethod() throws NoSuchMethodException {
        FooService object = new FooService();
        Method method = FooService.class.getMethod("createFoo", Foo.class);
        Foo foo = new Foo("FOO_ID", new Bar("BAR_ID"));
        Object[] parameterValues = new Object[]{foo};

        ExecutableValidator methodValidator = validator.forExecutables();
        Set<ConstraintViolation> violations = methodValidator.validateParameters(object, method, parameterValues);

        assertEquals(0, violations.size());
    }    

    @Test
    public void shouldRaiseConstraintViolationOnCreateFooMethod() throws NoSuchMethodException {
        FooService object = new FooService();
        Method method = FooService.class.getMethod("createFoo", Foo.class);
        Foo foo = new Foo("ID1", new Bar(null));
        Object[] parameterValues = new Object[]{foo};

        ExecutableValidator methodValidator = validator.forExecutables();
        Set<ConstraintViolation> violations = methodValidator.validateParameters(object, method, parameterValues);

        assertEquals(1, violations.size());
        Class constraintType = violations.iterator().next().getConstraintDescriptor().getAnnotation().annotationType();
        assertEquals(NotNull.class, constraintType);
    }

Fontos kérdés, hogy a metódus szintű validációt hogyan lehet alkalmazni az osztályok öröklődésénél? Általános szabály, hogy a metódus lefutásának előfeltételein nem lehet erősíteni, az utófeltételein pedig nem lehet gyengíteni. Az első szabály megsértésére egy egyszerű példa:

public interface BarServiceInt {

    public Bar findBar(@NotNull String id);

}

public class BarService implements BarServiceInt {

    public Bar findBar(@NotNull @Size(min = 1) String id) {
        return null;
    }

}

A fenti kódrészlet ConstraintDeclarationException kivételt eredményez, ami a következő tesztesettel igazolható:

    @Test(expected = ConstraintDeclarationException.class)
    public void shouldRaiseConstraintDeclarationExceptionOnFindBarMethod() throws NoSuchMethodException {
        BarService object = new BarService();
        Method method = BarService.class.getMethod("findBar", String.class);
        Object[] parameterValues = new Object[]{"ID1"};

        ExecutableValidator methodValidator = validator.forExecutables();
        methodValidator.validateParameters(object, method, parameterValues);
    }

Összefoglalva a Bean Validation 1.1 szabvány a metódus szintű validációk támogatásával egy olyan eszközt ad a fejlesztők kezébe, amellyel érdemes megismerkedni és érdemes használni.


No Comments

Leave a comment