Android
Tests unitaires sous Android avec JUnit
Recherche…
Remarques
- Vogella: Tests unitaires avec JUnit
- Annotations Junit: java2novice.com
- Classe Assert : junit.org
- JUnit Api: tutorialspoint.com
- Anroid testing Medium.com posts
Créer des tests unitaires locaux
Placez vos classes de test ici: /src/test/<pkg_name>/
Exemple de classe de test
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
int a=4, b=5, c;
c = a + b;
assertEquals(9, c); // This test passes
assertEquals(10, c); //Test fails
}
}
Panne
public class ExampleUnitTest {
...
}
La classe de test, vous pouvez créer plusieurs classes de test et les placer dans le package de test.
@Test
public void addition_isCorrect() {
...
}
La méthode de test, plusieurs méthodes de test peuvent être créées dans une classe de test.
Notez l'annotation @Test
.
L'annotation Test indique à JUnit que la méthode vide publique à laquelle elle est attachée peut être exécutée en tant que scénario de test.
Il existe plusieurs autres annotations utiles comme @Before
, @After
etc. Cette page serait un bon point de départ.
assertEquals(9, c); // This test passes
assertEquals(10, c); //Test fails
Ces méthodes sont membres de la classe Assert
. Quelques autres méthodes utiles sont assertFalse()
, assertNotNull()
, assertTrue
etc. Voici une explication détaillée.
Informations d'annotation pour le test JUnit:
@Test: L'annotation Test indique à JUnit que la méthode vide publique à laquelle elle est attachée peut être exécutée en tant que scénario de test. Pour exécuter la méthode, JUnit construit d'abord une nouvelle instance de la classe, puis appelle la méthode annotée.
@Before: Lors de l'écriture des tests, il est courant de constater que plusieurs tests nécessitent des objets similaires créés avant de pouvoir être exécutés. Annoter une méthode vide publique avec @Before
provoque l' @Before
cette méthode avant la méthode Test.
@Après: Si vous allouez des ressources externes dans une méthode Before, vous devez les libérer après le test. Annoter une méthode vide publique avec @After
provoque l' @After
cette méthode après la méthode Test. Toutes les méthodes @After
sont garanties pour s'exécuter même si une méthode Before ou Test émet une exception
Astuce Créez rapidement des classes de test dans Android Studio
- Placez le curseur sur le nom de la classe pour laquelle vous souhaitez créer une classe de test.
- Appuyez sur Alt + Entrée (Windows).
- Sélectionnez Créer un test, appuyez sur Retour.
- Sélectionnez les méthodes pour lesquelles vous souhaitez créer des méthodes de test, cliquez sur OK.
- Sélectionnez le répertoire dans lequel vous souhaitez créer la classe de test.
- Vous avez terminé, ce que vous obtenez est votre premier test.
Astuce Exécuter facilement des tests dans Android Studio
- Faites un clic droit pour tester le paquet.
- Sélectionnez Run 'Tests in ...
- Tous les tests du package seront exécutés immédiatement.
Déplacement de la logique métier hors des composants Android
Une grande partie de la valeur des tests unitaires JVM locaux provient de la façon dont vous concevez votre application. Vous devez le concevoir de telle sorte que vous puissiez découpler votre logique métier de vos composants Android. Voici un exemple d'utilisation du modèle Model-View-Presenter . Permet de s'exercer en mettant en place un écran d'inscription de base qui ne prend qu'un nom d'utilisateur et un mot de passe. Notre application Android est chargée de valider que le nom d'utilisateur fourni par l'utilisateur n'est pas vide et que le mot de passe comporte au moins huit caractères et au moins un chiffre. Si le nom d'utilisateur / mot de passe est valide, nous effectuons notre appel api d'inscription, sinon nous affichons un message d'erreur.
Exemple où la logique métier est fortement couplée au composant Android.
public class LoginActivity extends Activity{
...
private void onSubmitButtonClicked(){
String username = findViewById(R.id.username).getText().toString();
String password = findViewById(R.id.password).getText().toString();
boolean isUsernameValid = username != null && username.trim().length() != 0;
boolean isPasswordValid = password != null && password.trim().length() >= 8 && password.matches(".*\\d+.*");
if(isUsernameValid && isPasswordValid){
performSignUpApiCall(username, password);
} else {
displayInvalidCredentialsErrorMessage();
}
}
}
Exemple où la logique métier est découplée du composant Android.
Nous définissons ici dans une seule classe, LoginContract, les différentes interactions entre nos différentes classes.
public interface LoginContract {
public interface View {
performSignUpApiCall(String username, String password);
displayInvalidCredentialsErrorMessage();
}
public interface Presenter {
void validateUserCredentials(String username, String password);
}
}
Notre LoginActivity est pour la plupart identique, sauf que nous avons supprimé la responsabilité de savoir comment valider le formulaire d'inscription d'un utilisateur (notre logique métier). LoginActivity s'appuiera désormais sur notre nouveau LoginPresenter pour effectuer la validation.
public class LoginActivity extends Activity implements LoginContract.View{
private LoginContract.Presenter presenter;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = new LoginPresenter(this);
....
}
...
private void onSubmitButtonClicked(){
String username = findViewById(R.id.username).getText().toString();
String password = findViewById(R.id.password).getText().toString();
presenter.validateUserCredentials(username, password);
}
...
}
Votre logique métier réside désormais dans votre nouvelle classe LoginPresenter.
public class LoginPresenter implements LoginContract.Presenter{
private LoginContract.View view;
public LoginPresenter(LoginContract.View view){
this.view = view;
}
public void validateUserCredentials(String username, String password){
boolean isUsernameValid = username != null && username.trim().length() != 0;
boolean isPasswordValid = password != null && password.trim().length() >= 8 && password.matches(".*\\d+.*");
if(isUsernameValid && isPasswordValid){
view.performSignUpApiCall(username, password);
} else {
view.displayInvalidCredentialsErrorMessage();
}
}
}
Et maintenant, nous pouvons créer des tests unitaires JVM locaux par rapport à votre nouvelle classe LoginPresenter.
public class LoginPresenterTest {
@Mock
LoginContract.View view;
private LoginPresenter presenter;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
presenter = new LoginPresenter(view);
}
@Test
public void test_validateUserCredentials_userDidNotEnterUsername_displayErrorMessage() throws Exception {
String username = "";
String password = "kingslayer1";
presenter.validateUserCredentials(username, password);
Mockito.verify(view). displayInvalidCredentialsErrorMessage();
}
@Test
public void test_validateUserCredentials_userEnteredFourLettersAndOneDigitPassword_displayErrorMessage() throws Exception {
String username = "Jaime Lanninster";
String password = "king1";
presenter.validateUserCredentials(username, password);
Mockito.verify(view). displayInvalidCredentialsErrorMessage();
}
@Test
public void test_validateUserCredentials_userEnteredNineLettersButNoDigitsPassword_displayErrorMessage() throws Exception {
String username = "Jaime Lanninster";
String password = "kingslayer";
presenter.validateUserCredentials(username, password);
Mockito.verify(view). displayInvalidCredentialsErrorMessage();
}
@Test
public void test_validateUserCredentials_userEnteredNineLettersButOneDigitPassword_performApiCallToSignUpUser() throws Exception {
String username = "Jaime Lanninster";
String password = "kingslayer1";
presenter.validateUserCredentials(username, password);
Mockito.verify(view).performSignUpApiCall(username, password);
}
}
Comme vous pouvez le constater, lorsque nous avons extrait notre logique métier de LoginActivity et l’avons placée dans le POJO LoginPresenter. Nous pouvons maintenant créer des tests unitaires JVM locaux en fonction de notre logique métier.
Il convient de noter que notre changement d’architecture a plusieurs autres implications, comme l’adhésion à chaque classe ayant une responsabilité unique, des classes supplémentaires, etc. Ce ne sont que des effets secondaires de la manière dont je choisis découplage via le style MVP. MVP n’est qu’une façon d’y parvenir, mais vous pouvez également envisager d’autres solutions, telles que MVVM . Il vous suffit de choisir le meilleur système qui fonctionne pour vous.
Démarrer avec JUnit
Installer
Pour lancer l'unité de test de votre projet Android à l'aide de JUnit, vous devez ajouter la dépendance JUnit à votre projet et créer un ensemble de sources de test qui contiendra le code source des tests unitaires. Les projets créés avec Android Studio incluent souvent déjà la dépendance JUnit et le jeu de sources de test
Ajoutez la ligne suivante à votre fichier build.gradle
module dans les dépendances Closure
:
testCompile 'junit:junit:4.12'
Les classes de test JUnit se trouvent dans un ensemble de sources nommé test
. Si cet ensemble de sources n'existe pas, vous devez créer un nouveau dossier vous-même. La structure de dossiers d'un projet Android Studio (basé sur Gradle) par défaut ressemble à ceci:
<project-root-folder>
/app (module root folder)
/build
/libs
/src
/main (source code)
/test (unit test source code)
/androidTest (instrumentation test source code)
build.gradle (module gradle file)
/build
/gradle
build.gradle (project gradle file)
gradle.properties
gradlew
gradlew.bat
local.properties
settings.gradle (gradle settings)
Si votre projet ne dispose pas du dossier /app/src/test
, vous devez le créer vous-même. Dans le dossier de test
, vous avez également besoin d'un dossier java
(créez-le s'il n'existe pas). Le dossier Java dans le jeu de sources de test
doit contenir la même structure de package que votre jeu de sources main
.
Si la configuration est correcte, la structure de votre projet (dans la vue Android d’Android Studio) devrait ressembler à ceci:
Remarque: Vous n'avez pas nécessairement besoin du androidTest
sources androidTest
, cet ensemble de sources se trouve souvent dans les projets créés par Android Studio et est inclus ici pour référence.
Écrire un test
Créez une nouvelle classe dans le jeu de sources de
test
.
Cliquez avec le bouton droit de la souris sur le jeu de sources de test dans la vue du projet, choisissezNew
>Java class
.
Le modèle de nommage le plus utilisé consiste à utiliser le nom de la classe que vous allez tester avecTest
ajouté. Donc,StringUtilities
devientStringUtilitiesTest
.Ajouter l'annotation
@RunWith
L'annotation@RunWith
est nécessaire pour que JUnit exécute les tests que nous allons définir dans notre classe de test. Le runner JUnit par défaut (pour JUnit 4) est leBlockJUnit4ClassRunner
mais, au lieu d'utiliser directement cette exécution, il est plus pratique d'utiliser l'aliasJUnit4
qui est un raccourci pour le runner JUnit par défaut.@RunWith(JUnit4.class) public class StringUtilitiesTest { }
Créer un test
Un test unitaire est essentiellement une méthode qui, dans la plupart des cas, ne doit pas échouer si elle est exécutée. En d'autres termes, il ne devrait pas lancer d'exception. Dans une méthode de test, vous trouverez presque toujours des assertions qui vérifient si des conditions spécifiques sont remplies. Si une assertion échoue, elle génère une exception qui entraîne l'échec de la méthode / du test. Une méthode de test est toujours annotée avec l'annotation@Test
. Sans cette annotation, JUnit ne lancera pas automatiquement le test.@RunWith(JUnit4.class) public class StringUtilitiesTest { @Test public void addition_isCorrect() throws Exception { assertEquals("Hello JUnit", "Hello" + " " + "JUnit"); } }
Remarque: contrairement à la méthode Java standard, les noms de méthode de test des unités de convention contiennent souvent des traits de soulignement.
Lancer un test
Méthode
Pour exécuter une méthode de test unique, vous pouvez cliquer avec le bouton droit sur la méthode et cliquer surRun 'addition_isCorrect()'
ou utiliser le raccourci clavierctrl+shift+f10
.Si tout est configuré correctement, JUnit commence à exécuter la méthode et vous devriez voir l'interface suivante dans Android Studio:
Classe
Vous pouvez également exécuter tous les tests définis dans une seule classe, en cliquant avec le bouton droit sur la classe dans la vue du projet et en cliquant surRun 'StringUtilitiesTest '
ou utilisez le raccourci clavierctrl+shift+f10
si vous avez sélectionné la classe dans la vue du projet.Package (tout)
Si vous ne voulez pas exécuter tous les tests définis dans le projet ou dans un package, vous pouvez cliquer avec le bouton droit sur le package et cliquer surRun ...
comme si vous exécutiez tous les tests définis dans une seule classe.
Des exceptions
JUnit peut également être utilisé pour tester si une méthode renvoie une exception spécifique pour une entrée donnée.
Dans cet exemple, nous allons tester si la méthode suivante renvoie vraiment une exception si le format booléen (input) n'est pas reconnu / inconnu:
public static boolean parseBoolean(@NonNull String raw) throws IllegalArgumentException{
raw = raw.toLowerCase().trim();
switch (raw) {
case "t": case "yes": case "1": case "true":
return true;
case "f": case "no": case "0": case "false":
return false;
default:
throw new IllegalArgumentException("Unknown boolean format: " + raw);
}
}
En ajoutant le paramètre expected
à l'annotation @Test
, vous pouvez définir quelle exception est susceptible d'être lancée. Le test unitaire échouera si cette exception ne se produit pas et réussit si l'exception est bien levée:
@Test(expected = IllegalArgumentException.class)
public void parseBoolean_parsesInvalidFormat_throwsException(){
StringUtilities.parseBoolean("Hello JUnit");
}
Cela fonctionne bien, cependant, cela vous limite à un seul cas de test dans la méthode. Parfois, vous pouvez vouloir tester plusieurs cas dans une même méthode. Une technique souvent utilisée pour surmonter cette limitation consiste à utiliser try-catch
blocs try-catch
et la méthode Assert.fail()
:
@Test
public void parseBoolean_parsesInvalidFormats_throwsException(){
try {
StringUtilities.parseBoolean("Hello!");
fail("Expected IllegalArgumentException");
} catch(IllegalArgumentException e){
}
try {
StringUtilities.parseBoolean("JUnit!");
fail("Expected IllegalArgumentException");
} catch(IllegalArgumentException e){
}
}
Remarque: certaines personnes considèrent que tester un seul cas à l’intérieur d’un test unitaire est une mauvaise pratique.
Import statique
JUnit définit quelques méthodes assertEquals
au moins une pour chaque type de primitive et une autre pour les objets. Ces méthodes ne sont pas directement disponibles par défaut pour être appelées et doivent être appelées comme ceci: Assert.assertEquals
. Mais comme ces méthodes sont utilisées, les utilisateurs utilisent souvent une importation statique, de sorte que la méthode peut être utilisée directement comme si elle faisait partie de la classe elle-même.
Pour ajouter une importation statique pour la méthode assertEquals
, utilisez l'instruction d'importation suivante:
import static org.junit.Assert.assertEquals;
Vous pouvez également importer de manière statique toutes les méthodes d'assert, y compris assertArrayEquals
, assertNotNull
et assertFalse
utilisant l'importation statique suivante:
import static org.junit.Assert.*;
Sans importation statique:
@Test
public void addition_isCorrect(){
Assert.assertEquals(4 , 2 + 2);
}
Avec importation statique:
@Test
public void addition_isCorrect(){
assertEquals(4 , 2 + 2);
}