[JAVA] Testez la classe injectée sur le terrain dans le test de démarrage Spring sans utiliser le conteneur Spring

Si vous avez de l'expérience avec Java, c'est tout à fait naturel, mais je pense qu'il y a des gens qui ont les mêmes problèmes que moi et qui se sentent comme "Oh, c'est vrai", alors je vais l'écrire. C'est juste une histoire de réflexion. Cependant, que cette méthode soit correcte ou incorrecte en tant que conception de test est une connaissance différente, elle n'est donc décrite que comme l'une des méthodes. (Il y a aussi la possibilité d'anti-motif!) Je l'ai écrit, mais c'est aussi la méthode qui m'a inspiré à concevoir à l'aide de l'injection de constructeur dans le futur.

Problèmes lors du test à l'aide des fonctionnalités Spring (conteneurs)

Dans le projet sur lequel je travaille, il faut environ 3 minutes (sur le PC prêté) pour charger diverses informations pour lancer l'application, et le test est lent à s'exécuter. </ b> Je voulais faire un cycle des tests unitaires avec un bon rythme, mais cette fois a été prise pour chaque test en utilisant le conteneur Spring, ce qui était suffisant pour la raison pour laquelle je ne voulais pas tester.

Par exemple, lors de l'appel du conteneur Spring dans le test comme suit (Spring boot ver 1.X)

Classe testée


@Service
public class ParentService {

    @Autowired
    ChildService childService;//Injection sur le terrain

    public SampleForm generateSample(int n) {
        //Injectez ici
        int calNumber = childService.calculate(n);
        return this.prepared(calNumber);
    }

    private SampleForm prepared(int calNumber) {
        int newN = calNumber * 2;
        SampleForm sample = new SampleForm();
        sample.setCalNumber(newN);
        return sample;
    }

}

tester


@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestConfig.class)
@WebIntegrationTest(randomPort = true)
@Transactional
public class SampleTest {
    
    @Autowired
    ParentService parentService;

    @Test
Si vous donnez 2 à la méthode publique void generate, vous obtiendrez un SampleForm avec 4.() {
        SampleForm sample = parentService.generateSample(2)
        assertThat(sample.getCalNumber(), is(4));
    }
}

Je vais le faire comme Par exemple, attendre quelques minutes chaque fois que vous effectuez un test aussi court ne suffit pas.

Jusqu'à présent, par exemple, dans le cas ci-dessus, ParentService.prepared` `` Rendre la méthode publique </ b>


//N'utilisez pas la fonction de conteneur à ressort.
public class SampleTest {
    
    ParentService parentService = new ParenteService();

    @Test
Si vous donnez 2 à la méthode préparée par public void, vous obtiendrez un SampleForm avec 4.() {
        SampleForm sample = parentService.prepared(2)
        assertThat(sample.getCalNumber(), is(4));
    }
}

J'ai essayé de rendre possible le test avec POJO en le faisant autant que possible, en n'appelant pas la méthode d'injection de champ à l'intérieur de la méthode, et en passant l'objet dépendant comme argument. Étant donné que la classe appelée par injection de champ n'est pas injectée à moins que le conteneur Spring ne soit utilisé, elle sera définie comme null pour la nouvelle comme POJO. Si vous écrivez comme suit

ParentService.childeService appelé dans generateSample.NullPointerException se produit lorsque caluclate.



```java

public class SampleTest {
    
    ParentService parentService = new ParentService();

    @Test
Si vous donnez 2 à la méthode publique void generate, vous obtiendrez un SampleForm avec 4.() {
        /*
childService dans la méthode generateSample.Appelez calculer, mais dans ce test
        new ParentService()Étant donné que childService est null, une exception se produit.
        */
        SampleForm sample = parentService.generateSample(2)//Exception!      
        assertThat(sample.getCalNumber(), is(4));
    }
}

ChildService.La méthode caluclate est également publique, validée dans un autre test, et ParentService.Si préparé peut également être vérifié par des tests



#### **`  Parentservice.Puisque generateSample devrait être correct, je pense qu'il devrait être correct sans test (dans l'exemple simple ci-dessus). Cependant, en tant que conception à l'origine, ParentService.préparé n'est pas une méthode à appeler de l'extérieur,<b>Est-il vraiment correct de le rendre public pour les tests? Je me sens différent.</b>`**

De plus, étant donné qu'il ne s'agit pas d'une implémentation aussi simple en réalité, vous pouvez vouloir tester une méthode qui a un objet injecté par injection de champ.

J'y ai soudain pensé. "Dans un langage dynamique, vous pouvez accéder et définir dynamiquement des champs et définir dynamiquement des méthodes ... Eh bien, Java a aussi des réflexions." Dans le langage Java, nous ne touchons généralement pas les champs de manière dynamique. Avec JavaScript, vous pouvez faire référence dynamiquement aux noms de champs et ajouter des méthodes aux objets, bien sûr, mais dans le développement Java, c'est généralement (mon peu profond) à faire pour le code de production. Je ne pense pas (à ma connaissance). Je pense qu'il y a de nombreuses raisons, mais je comprends qu'une caractéristique de Java est que le typage statique compromet la sécurité. Je n'avais donc pas l'idée d'utiliser la réflexion, mais j'ai pensé qu'elle pourrait être utilisée pour effectuer les tests ci-dessus.

Et j'ai écrit comme suit.


public class SampleTest {
    
    
    ParentService parentService = new ParentService();

    @Before
    public void setUp() {
        Class<?> c = parentService.getClass();
        parentService = (ParentService)c.newInstance();
        Field f = c.getDeclaredField("childService");
        f.setAccessible(true);
        f.set(parentService , new ChildService());

    }

    @Test
Si vous donnez 2 à la méthode publique void generate, vous obtiendrez un SampleForm avec 4.() {
        /*
childService dans la méthode generateSample.Appelez calculer. Avec réflexion
Les tests suivants sont possibles car il est défini dans.
        */
        SampleForm sample = parentService.generateSample(2)        
        assertThat(sample.getCalNumber(), is(4));
    }
}

En écrivant comme ci-dessus, vous pouvez accéder et définir le champ sans utiliser le conteneur Spring. Vous pouvez tester. Par conséquent, il est possible d'effectuer un test dans le délai avant le début du test. Dans l'exemple, new est passé et `` ChildService '' est passé, mais il est également possible de définir une simulation avec un comportement.

Cependant, comme je l'ai mentionné au début, qu'est-ce que l'injection en premier lieu? Comme je n'avais que la connaissance du rang, j'ai pensé à la méthode ci-dessus et j'ai décidé de revoir le design en relisant le livre sur Spring et en lisant le blog suivant.

référence Pourquoi Spring recommande l'injection de constructeur plutôt que l'injection de champ

c'est tout.

Recommended Posts