Framework d'extension JUnit ajouté dans JUnit 4.7. Dans JUnit, il existe différents mécanismes pour extraire le traitement commun de la configuration des appareils Les règles sont un mécanisme qui rend plus facile et plus flexible l'extraction des processus courants.
Les caractéristiques sont les suivantes.
En raison des caractéristiques ci-dessus, les règles sont faciles à réutiliser et peuvent être décrites de manière déclarative. On peut dire que c'est un mécanisme qui peut être facilement étendu au moment de l'exécution du test.
Le moment d'application de la règle diffère entre l'annotation de règle et l'annotation de règle de classe. L'annotation Règle est appliquée à chaque fois que la méthode de test est exécutée (identique à l'annotation Avant). L'annotation ClassRule est appliquée à chaque classe de test (identique à l'annotation BeforeClass).
Plusieurs règles peuvent être définies dans une classe de test. Cependant, si RuleChain (décrit plus loin) n'est pas utilisé, l'ordre d'exécution ne peut pas être contrôlé et il sera aléatoire.
RuleExampleTest.java
public class RuleExampleTest {
@Rule
public Timeout timeout = new TimeOut(100);
@ClassRule
public TestName testName = new TestName();
@Test
public void Tests dont l'exécution peut prendre beaucoup de temps() throws Exception {
doLongTask();
}
}
Remarque: un dossier temporaire est un dossier qui stocke temporairement les fichiers nécessaires.
Habituellement, lors de l'exécution de tests portant sur des systèmes de fichiers, chaque test Maintenez l'indépendance des tests en créant des dossiers en prétraitement et en les supprimant en post-traitement. Si vous définissez la classe org.junit.rules.TemporaryFolder en tant que règle, chaque fois que la règle est exécutée Étant donné que les dossiers sont créés et supprimés, il n'est pas nécessaire de mettre en œuvre ces processus.
public class TemporaryFolderExampleTest {
@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();
@Test
public void mkFiles crée deux fichiers() throws Exception {
File folder = tmpFolder.getRoot();
TemporaryFolderExample.mkDefaultFiles(folder);
String[] actualFiles = folder.list();
Arrays.sort(actualFiles);
assertThat(actualFiles.length, is(2));
assertThat(actualFiles[0], is("UnitTest"));
assertThat(actualFiles[1], is("readme.txt"));
}
}
TemporaryFolderExample.java
public class TemporaryFolderExample {
public static void mkDefaultFiles(File folder) {
String rootPath = folder.getPath();
File file = new File(rootPath + "/UnitTest");
File file2 = new File(rootPath + "/readme.txt");
try {
file.createNewFile();
file2.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
La classe de dossier temporaire avant l'extension a été définie sur la propriété système Java "java.io.tmpdir" Il prépare et libère uniquement les ressources directement sous le dossier, mais le traitement peut être étendu en créant des sous-classes.
Puisque TemporaryFolder est une sous-classe de org.junit.rules.ExternalResource Remplace les méthodes avant et après. Comme ils sont appelés avant et après l'exécution du test, le traitement à effectuer à ce moment peut être étendu.
Ce qui suit crée une sous-classe directement sous le dossier temporaire avant l'exécution, Un exemple d'extension du nom de dossier créé après l'exécution vers la sortie vers la console.
SubTemporaryFolder.java
public class SubTemporaryFolder extends TemporaryFolder {
//Sous-dossier
public File srcFolder;
public File testFolder;
@Override
protected void before() throws Throwable {
//Dossier temporaire avant la méthode(Obligatoire)
super.before();
srcFolder = newFolder("src");
testFolder = newFolder("test");
}
@Override
protected void after() {
//Dossier temporaire après la méthode(Obligatoire)
super.after();
System.out.println(srcFolder.getName());
System.out.println(testFolder.getName());
}
}
SubTemporaryFolderExampleTest.java
public class SubTemporaryFolderExampleTest {
@Rule
public SubTemporaryFolder tmpFolder = new SubTemporaryFolder();
@Test
public void mkFiles crée deux fichiers() throws Exception {
File folder = tmpFolder.getRoot();
File srcFolder = tmpFolder.srcFolder;
File testFolder = tmpFolder.testFolder;
assertThat(folder.list().length, is(2));
assertThat(srcFolder.list().length, is(0));
assertThat(testFolder.list().length, is(0));
}
}
La classe org.junit.rules.ExternalResource Une règle qui "prépare les ressources avant d'exécuter un test" et "libère les ressources après l'exécution d'un test". La classe ExternalResource étant une classe abstraite, implémentez une sous-classe lors de son utilisation.
Dans l'exemple, la sous-classe a été définie comme une classe imbriquée de la classe de test, Si elle est définie comme une classe indépendante, elle peut être utilisée par plusieurs classes de test.
python
public class ExternalResourceExampleTest() {
@Rule
public ServerResource resource = new ServerResource();
static class ServerResource extends ExternalResource {
ServerSocket server;
@Override
protected void before() throws Throwable {
server = new ServerSocket(8080);
server.accept();
}
@Override
protected void after() {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test() {
fail("Not yet implemented");
}
}
La classe org.junit.rules.Verifier est une règle qui valide les conditions post-test. Comme la classe ExternalResource, il s'agit d'une classe abstraite, créez donc une sous-classe et Remplacez et utilisez la méthode de vérification.
Des annotations après et après la classe ont été ajoutées pour vérifier la post-condition du test. Puisqu'il est également possible de le faire avec une méthode de post-traitement, le contenu de vérification est plus compliqué et je souhaite le faire sur plusieurs classes Dans ce cas, il est pratique d'utiliser correctement la classe Verifier.
L'ordre d'exécution est le suivant. Avant l'annotation → Tester l'annotation → Après l'annotation → vérifier la méthode
public class VerifierExampleTest {
//Implémenté en tant que classe anonyme jetable
@Rule
public Verifier verifier = new Verifier() {
protected void verify() throws Throwable {
assertThat("value should be 0.", sut.value, is(0));
}
};
VerifierExample sut;
@Before
public void setUp() throws Exception {
sut = new VerifierExample();
}
@After
public void tearDown() throws Exception {
// do nothing.
}
@Test
Initialisez le résultat du calcul avec la méthode publique void clear et définissez-le sur 0.() throws Exception {
sut.set(0);
sut.add(140);
sut.minus(5);
assertThat(sut.getValue(), is(135));
sut.clear();
}
}
VerifierExample.java
public class VerifierExample {
protected Integer value;
public void set(int i) {
value = i;
}
public void add(int i) {
value += i;
}
public void minus(int i) {
value -= i;
}
public Integer getValue() {
return value;
}
public void clear() {
value = 0;
}
}
Normalement, le traitement de la méthode de test dans JUnit se termine lorsqu'une erreur se produit. Cependant, si vous utilisez la classe org.junit.rules.ErrorCollector après avoir exécuté le test jusqu'à la fin, Produit des informations sur les erreurs collectivement.
public class ItemInfoTest {
@Rule
public ErrorCollector errCollector = new ErrorCollector();
@Test
public void Vérification lors de l'application des règles() throws Exception {
ItemInfo itemInfo = new ItemInfo();
errCollector.checkThat(itemInfo, is(nullValue())); /*Où l'erreur s'est produite*/
errCollector.checkThat(itemInfo.getId(), is(""));
errCollector.checkThat(itemInfo.getName(), is(not(""))); /*Où l'erreur s'est produite*/
errCollector.checkThat(itemInfo.getStockNum(), is(0));
}
@Test
public void Vérification normale() throws Exception {
ItemInfo itemInfo = new ItemInfo();
assertThat(itemInfo, is(nullValue())); /*Où l'erreur s'est produite*/
assertThat(itemInfo.getId(), is(""));
assertThat(itemInfo.getName(), is(not(""))); /*Où l'erreur s'est produite*/
assertThat(itemInfo.getStockNum(), is(0));
}
}
ItemInfo.java
public class ItemInfo {
private String id;
private String name;
private int stockNum;
public ItemInfo() {
this.id = "";
this.name = "";
this.stockNum = 0;
}
/*Ci-dessous, la méthode Getter et Setter de chaque membre*/
}
Rapport d'erreur
~ Vérification lors de l'application des règles ~
Expected: is null
but: was <com.example.junit.rules.errcollect.ItemInfo@506c589e>
java.lang.AssertionError:
Expected: is not ""
but: was ""
~ Vérification normale ~
Expected: is null
but: was <com.example.junit.rules.errcollect.ItemInfo@1698c449>
La classe org.junit.rules.ExpectedException valide l'exception levée C'est une règle qui fournit un mécanisme pour le faire simplement. Afin de comparer la méthode d'implémentation, le cas où la règle n'est pas utilisée et le cas où la règle est utilisée sont comparés ci-dessous.
public class ExpectExceptionExampleTest {
@Rule
public ExpectedException exException = ExpectedException.none();
@Test
public void Valider les messages d'exception à l'aide de règles() throws Exception {
exException.expect(ArithmeticException.class);
exException.expectMessage(containsString("by zero"));
int result = 1 / 0;
System.out.println(result);
}
@Test
public void Valide les messages d'exception de manière standard() throws Exception {
try {
int result = 1 / 0;
System.out.println(result);
//Détecter quand aucune exception ne se produit
fail("Pas exception");
} catch (ArithmeticException e) {
assertThat(e.getMessage(), is(containsString("by zero")));
}
}
}
Une règle qui vous permet de définir l'heure jusqu'à l'expiration du délai, tout comme la lecture. Vous pouvez également définir le délai d'expiration en passant une valeur à l'attribut timeout de l'annotation Test, Lors du réglage commun à toutes les méthodes de test de la classe de test La description peut être combinée en un seul endroit à l'aide de la classe Timeout.
public class TimeoutExampleTest {
@Rule
public Timeout timeout = new Timeout(1000);
@Test
public void sleep1() throws InterruptedException {
while(true) {
}
}
@Test
public void sleep2() throws InterruptedException {
while(true) {
}
}
}
La classe org.junit.rules.TestWatcher peut être utilisée avec les journaux C'est une règle qui permet de surveiller (suivre) l'état d'exécution du test. TestWatcher est une classe abstraite, utilisez-la donc en remplaçant les méthodes des sous-classes.
L'ordre d'exécution de TestWatcher lorsque le test réussit et échoue est le suivant.
[Au moment du succès] starting → succeeded → finished
[Au moment de l'échec] starting → failed → finished
public class TestWatcherExampleTest {
@Rule
public TestWatcher testWatcher = new TestWatcher() {
@Override
protected void starting(Description desc) {
Logger.getAnonymousLogger()
.info("start: " + desc.getMethodName());
}
@Override
protected void succeeded(Description desc) {
Logger.getAnonymousLogger()
.info("succeeded: " + desc.getMethodName());
}
@Override
protected void failed(Throwable e, Description desc) {
Logger.getAnonymousLogger()
.log(Level.WARNING, "failed: " + desc.getMethodName(), e);
}
@Override
protected void finished(Description desc) {
Logger.getAnonymousLogger()
.info("finished: " + desc.getMethodName());
}
};
@Test
public void Test réussi() throws Exception {
}
@Test
test public void qui échoue() throws Exception {
fail("NG");
}
}
Sortie de journal
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 starting
information: start:Tests qui échouent
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 failed
avertissement: failed:Tests qui échouent
java.lang.AssertionError: NG
at org.junit.Assert.fail(Assert.java:88)
at com.example.junit.rules.testwatcher.TestWatcherExampleTest.Tests qui échouent(TestWatcherExampleTest.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 finished
information: finished:Tests qui échouent
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 starting
information: start:Test réussi
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 succeeded
information: succeeded:Test réussi
4 23, 2020 7:11:08 h 00 com.example.junit.rules.testwatcher.TestWatcherExampleTest$1 finished
information: finished:Test réussi
La classe org.junit.rules.TestName est Une règle pour obtenir le nom de la méthode en cours d'exécution dans la méthode de test. En pouvant obtenir le nom de la méthode dans la méthode de test, le nom de la méthode et la ressource externe Il existe différentes manières de l'utiliser, par exemple en associant le nom du fichier.
public class TestNameExampleTest {
@Rule
public TestName testName = new TestName();
@Test
nom de la méthode de test public void() throws Exception {
fail(testName.getMethodName() + " is unimplements yet.");
}
}
Pour créer une règle personnalisée
Il existe deux méthodes.
Les méthodes suivantes sont définies dans l'interface org.junit.rules.TestRule.
Statement apply(Statement base, Description description);
La classe org.junit.runners.model.Statement est une classe qui contrôle l'exécution des tests. Le test est exécuté lorsque la méthode Statement.evaluate est appelée. L'objet Statement passé en tant qu'argument à la méthode apply se trouve dans la méthode d'évaluation. Les tests sont configurés pour s'exécuter dans l'ordre suivant:
La classe org.junit.runner.Description est une classe qui contient des méta-informations pour les cas de test. Vous pouvez obtenir des informations telles que la classe de test, le nom de la méthode de test et l'annotation attribuée.
Une implémentation courante d'une règle se trouve dans l'objet Statement de l'argument Créez un objet proxy (objet proxy) et renvoyez-le comme valeur de retour.
nom de règle personnalisé de classe abstraite publique implémente TestRule{
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
//Vous pouvez appeler votre propre prétraitement comme pré et post-traitement,
customedBySubClazz();
//Obtenir des méta-informations de test et une branche conditionnelle
if (description.getMethodName().equals("Nom de la méthode d'essai applicable")) {
//Selon le résultat du jugement, la méthode d'évaluation originale peut être exécutée ou non.
base.evaluate();
} else {
fail("La méthode d'essai est différente");
}
}
};
}
//Méthode abstraite pour implémenter un traitement unique lors de la définition d'une sous-classe
protected abstract void customedBySubClazz() throws Throwable;
}
Créez une règle personnalisée pour vérifier les conditions préalables du test. La différence par rapport au contrôle de la précondition par la méthode avec l'annotation Avant est Les extensions peuvent être fournies plus indépendamment de la classe de test. Accès facile aux méta-informations. Il y a une telle chose. Pour l'implémentation, reportez-vous à l'implémentation de la classe Verifier.
Classe de règles personnalisées
public abstract class PreProcess implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
//Prétraitement exclusif
verify();
//Méthode d'évaluation originale
base.evaluate();
}
};
}
//Implémentez votre propre prétraitement lors de la définition des sous-classes
protected abstract void verify() throws Throwable;
}
Classe de test avec PreProcess appliqué à la règle
public class PreProcessExampleTest {
@Rule
public PreProcess preProcess = new PreProcess() {
@Override
protected void verify() throws Throwable {
LocalDate now = LocalDate.now();
LocalDate limitDate = LocalDate.of(2015, 4, 1);
if (now.isAfter(limitDate)) {
throw new AssertionError();
}
}
};
@Test
public void Tests qui ne s'exécutent pas() {
assertThat(1, is(1));
}
}
Créez des règles personnalisées pour les tests dépendants du système d'exploitation. Lors de l'évaluation de l'objet proxy, déterminez le système d'exploitation de l'environnement d'exécution et Uniquement s'il correspond au système d'exploitation attendu (système d'exploitation spécifié par l'annotation RunOn de l'implémentation d'origine) Appelez la méthode d'évaluation d'origine.
Pour plus de détails sur les méta-informations utilisées lors de la création de l'annotation, voir Voir Articles publiés sur TECHSCORE.
Grosso modo
@Retention(RetentionPolicy.RUNTIME)→ Peut être consulté lors de l'exécution du test JUnit
#### **`@Target({ElementType.METHOD})→ C'est une annotation donnée à la méthode`**
```METHOD})→ C'est une annotation donnée à la méthode
Se sentir comme ça.
#### **`Annotation d'implémentation propriétaire`**
```java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RunOn {
public enum OS {
WINDOWS, MAC, LINUX
}
public OS value();
}
Dans la règle personnalisée, avec le système d'exploitation obtenu à partir de l'annotation RunOn Déterminez si le système d'exploitation de l'environnement d'exécution obtenu à partir des propriétés du système Java correspond, S'ils correspondent, appelez la méthode d'évaluation d'origine et Mettez en œuvre le processus consistant à ne rien faire s'ils ne correspondent pas.
public class OSDepend implements TestRule {
@Override
public Statement apply(Statement base, Description desc) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
//Obtenez l'annotation donnée à la méthode à partir de Description
RunOn env = desc.getAnnotation(RunOn.class);
//Déterminer si le système d'exploitation spécifié dans l'annotation correspond au système d'exploitation de l'environnement d'exécution
if (env == null || canEvaluate(env.value())) {
base.evaluate();
} else {
// don't evaluate
}
}
private boolean canEvaluate(OS os) {
/*Obtenez le système d'exploitation de l'environnement d'exécution à partir des propriétés système
Renvoie true s'il correspond au système d'exploitation spécifié dans l'annotation RunOn*/
String osName = System.getProperty("os.name");
if (osName == null) {
return false;
}
if (os == OS.WINDOWS && osName.startsWith("Windows")) {
return true;
}
if (os == OS.MAC && osName.startsWith("Mac OS X")) {
return true;
}
if (os == OS.LINUX && osName.startsWith("Linux")) {
return true;
}
return false;
}
};
}
}
Si vous exécutez la classe de test suivante, deux tests seront effectués, Seul Mac a une sortie console (car mon environnement est Mac) Vous pouvez voir que le scénario de test dans l'environnement Windows n'a pas été exécuté.
public class OSDependExampleTest {
@Rule
public OSDepend osDepend = new OSDepend();
@Test
//Annotation qui définit Enum pour déterminer le système d'exploitation
@RunOn(OS.WINDOWS)
public void Tests exécutés uniquement dans un environnement Windows() {
System.out.println("test: onlyWindows");
assertThat(File.separator, is("¥¥"));
}
@Test
@RunOn(OS.MAC)
public void Tests exécutés uniquement dans un environnement Mac() {
System.out.println("test: onlyMac");
assertThat(File.separator, is("/"));
}
}
↓ résultat de l'exécution JUnit
Sortie de la console
test: onlyMac
Lors du contrôle de ressources externes à l'aide de règles, il existe des cas où il est nécessaire de spécifier l'ordre d'exécution. Par exemple, s'il existe un scénario de test qui relie le serveur AP et le serveur DB, L'ordre d'exécution doit être démarrage du serveur de base de données → démarrage du serveur AP.
Dans ce cas, utilisez la classe org.junit.rules.RuleChain pour Vous pouvez contrôler l'ordre d'exécution sous la forme de ** exécutions de règles de chaînage **. (Modèle de chaîne de responsabilité: un type de modèle de conception)
Les méthodes suivantes sont implémentées dans la classe RuleChain.
static rulechain outerrule(testrule outerrule)
Spécifiez une règle qui s'initialise en premier et se termine ensuite
rulechain around(testrule enclosedrule)
Spécifiez une règle qui s'initialise plus tard et se termine en premier
S'il s'agit d'une lettre, elle n'est pas arrivée rapidement, elle est donc illustrée.
public class RuleChainExampleTest {
@Rule
public RuleChain ruleChain = RuleChain
.outerRule(new PreRule())
.around(new PostRule());
@Test
test du vide public() throws Exception {
}
}
class PreRule extends ExternalResource {
@Override
protected void before() throws Throwable {
System.out.println("1 start: PreRule");
}
@Override
protected void after() {
System.out.println("4 finish: PreRule");
}
}
class PostRule extends ExternalResource {
@Override
protected void before() throws Throwable {
System.out.println("2 start: PostRule");
}
@Override
protected void after() {
System.out.println("3 finish: PostRule");
}
}
Sortie de la console
1 start: PreRule
2 start: PostRule
3 finish: PostRule
4 finish: PreRule
Le démarrage et l'arrêt du serveur étant un processus lourd, des problèmes de test lents se produisent à mesure que le nombre de cas de test augmente. Par conséquent, si vous n'avez pas besoin de contrôle spécial au démarrage et à l'arrêt, utilisez l'annotation ClassRule. Il est possible d'économiser des ressources en démarrant et en arrêtant le serveur pour chaque classe de test.
Cet article a été rédigé en référence aux informations suivantes.
Recommended Posts