Lors du test d’un plugin pour les IDE basés sur IntelliJ à l’aide d’un test d’interface utilisateur, le plugin peut fonctionner dans une instance complète de l’IDE. La plupart des tests nécessiteront donc un projet pour effectuer leurs actions. Cela ouvre une question : comment gérez-vous les modifications apportées aux projets de test par les tests ? Ajouter une logique d’annulation à la fin de chaque test ? Rétablir à l’aide d’un système de contrôle de version ? Ces options semblent être des moyens faciles de faire de nouvelles erreurs.
Nous avons rencontré le même problème lors de l’écriture de tests pour notre propre plugin, Symflower, pour IntelliJ IDEA et GoLand, mais nous avons opté pour une solution plus simple. Nous n’exécutons pas de tests sur la source canonique de nos projets de test, mais copions plutôt l’intégralité du répertoire du projet et utilisons cette copie à la place. Cela évite de s’appuyer sur une logique de nettoyage répétitive ou sur la restauration basée sur VCS qui pourrait annuler les modifications légitimes apportées aux fichiers source.
Configuration de répertoires de projet temporaires pour isoler les tests du plug-in IntelliJ
Nous avons écrit une classe qui encapsule les détails d’implémentation, tels que le répertoire de base pour les copies temporaires du projet, et gère la traduction des chemins relatifs.
package com.symflower.testing.ui;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
/** TestProject is a utility class to provide a temporary project copy and a cleanup method to be used after a test run. */
public class TestProject {
private File projectPath;
public TestProject(String name) {
var sourcePath = "test-projects/" + name; // TODO Replace with your own path for test projects.
// Copy the source path to a temporary directory where all the tests are run.
try {
projectPath = Files.createTempDirectory(Path.of("tmp-project-copies"), name).toFile(); // TODO Replace with your own base directory for temporary copies of projects.
projectPath.deleteOnExit();
FileUtils.copyDirectory(new File(sourcePath), projectPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** getProjectName returns the project's name. */
public String getProjectName() {
return projectPath.getName();
}
/** getProjectPath returns the path to the project's temporary directory. */
public String getProjectPath() {
return projectPath.getPath();
}
/** getFilePath returns the given relative file path prefixed with the project's directory path. */
public String getFilePath(String relativeFilePath) {
return Path.of(projectPath.getPath(), relativeFilePath).toString();
}
/** cleanup deletes the temporary directory of the project. */
public void cleanup() {
try {
FileUtils.deleteDirectory(projectPath);
} catch (IOException e) {}
}
}
L’exemple suivant vous montre comment vous pouvez utiliser la classe dans votre code de test :
@Test
void testSomething() {
// REMARK "editor" and "system" are helpers defined in the test suite.
var project = new TestProject("test-plain");
editor.openProject(project.getProjectPath());
system.assertFileExists(project.getFilePath("plain.java"));
}
Après le test, appelez le cleanup
méthode sur les instances de TestProject
pour se débarrasser du répertoire temporaire du projet. Nous avons encore automatisé cela en définissant une classe de base pour tous nos tests d’interface utilisateur qui permet aux tests de fournir l’instance de TestProject
qui représente le projet actuellement actif et nettoie le projet à la fin du test, quel que soit son résultat.
package com.symflower.testing.ui;
import static com.intellij.remoterobot.stepsProcessing.StepWorkerKt.step;
import com.intellij.remoterobot.RemoteRobot;
import com.intellij.remoterobot.utils.Keyboard;
import com.symflower.testing.ui.helpers.EditorHelper;
import com.symflower.testing.ui.helpers.SymflowerHelper;
import com.symflower.testing.ui.helpers.SystemHelper;
import com.symflower.testing.ui.utils.RemoteRobotExtension;
import com.symflower.testing.ui.utils.StepsLogger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;
/** BaseUITest is a base class for UI test suites that operate on a project. */
@ExtendWith(RemoteRobotExtension.class)
public abstract class BaseUITest {
// Provide basic tools to interact with the testing instance.
protected final RemoteRobot remoteRobot = new RemoteRobot("http://127.0.0.1:8082");
protected final Keyboard keyboard = new Keyboard(remoteRobot);
// Provide higher-level helpers.
protected final EditorHelper editor = new EditorHelper(remoteRobot);
protected final SymflowerHelper symflower = new SymflowerHelper(remoteRobot);
protected final SystemHelper system = new SystemHelper();
/** cleanup is a function which is run after each test to reset settings and perform special cleanups which is also called if a test failed. */
protected Runnable cleanup;
/** project holds the "TestProject" instance for a test. */
protected TestProject project;
@BeforeAll
public static void testSetup() throws IOException {
StepsLogger.init();
}
@AfterEach
public void cleanUp(final RemoteRobot remoteRobot) {
// The try/finally block ensures that project-level cleanups always run even if the test-level cleanup function throws.
try {
step("Run cleanup task", () -> {
if (cleanup != null) {
cleanup.run();
}
});
} finally {
cleanup = null; // Reset the cleanup function to avoid leaking in tests which do not define one.
editor.closeProject();
if (project != null) {
project.cleanup();
project = null;
}
}
}
}
N’hésitez pas à les utiliser dans vos propres projets en utilisant la licence MIT. Si vous avez besoin d’aide pour écrire vos tests, essayez le plugin Symflower par vous-même.