À propos des tests avec simulation dans JUnit. Ceci est le deuxième article à diffuser du code de test sur le lieu de travail. Ce n'est pas une histoire très récente, car elle se concentre sur EasyMock.
Un objet simulé est un type de stub de module inférieur qui remplace les tests logiciels, en particulier dans le développement piloté par les tests et le développement piloté par le comportement. Module à inspecter par rapport au stub Est utilisé pour vérifier qu'il utilise correctement ses sous-modules.
Source d'objet simulé: Encyclopédie gratuite "Wikipedia"
Pour l'utiliser, par exemple, supposons que vous ayez créé une classe appelée HelloJunit comme indiqué ci-dessous. Lorsque j'essaye de tester cette classe, si la classe GreetDao n'est pas créée (et que la base de données référencée par Dao n'est pas préparée), si je la teste normalement, une erreur se produira. Je pense que ça finira.
HelloJunit
public class HelloJunit {
@Binding
GreetDao greetDao;
public String sayGreeting() {
return greetDao.getGreet();
}
À l'origine, je veux juste tester la classe HelloJunit, donc je devrais pouvoir la tester même si le code de la dépendance (GreetDao) n'est pas fait, alors préparons une classe Haribote pour qu'elle puisse fonctionner pour le moment. C'est le rôle du simulacre.
Il existe les méthodes suivantes.
Les bibliothèques simulées incluent «Mockito», «JMockit» et «EasyMock», «MockInterceptor».
J'ai l'impression que «Mockito» est souvent utilisé de nos jours.
Seasar2 inclut «EasyMock» et «MockInterceptor» par défaut. Je pense qu'il est difficile d'installer une nouvelle bibliothèque dans un environnement où Seasar2 est encore utilisé, donc j'aimerais continuer avec EasyMock cette fois.
Le code produit a été légèrement modifié par rapport au contenu présenté ci-dessus.
Code produit
public class HelloJunit {
GreetDao greetDao;
public HelloJunit(GreetDao greetDao) {
this.greetDao = greetDao;
}
public String sayGreeting() {
return greetDao.getGreet();
}
}
Voici le code de test.
Code de test
import static org.easymock.EasyMock.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.seasar.framework.unit.annotation.EasyMock;
import org.seasar.sastruts.example.dao.GreetDao;
public class HelloJunitTest {
HelloJunit helloJunit;
@EasyMock
GreetDao greetDao;
@Before
public void setUp() {
greetDao = createMock(GreetDao.class);
helloJunit = new HelloJunit(greetDao);
}
@Test
public void dire une salutation() throws Exception {
//installer
expect(greetDao.getGreet()).andReturn("Hello");
replay(greetDao);
//exercice
String actual = helloJunit.sayGreeting();
//Vérifier
String expected = "Hello";
assertThat(actual, is(expected));
verify(greetDao);
//démolir (post-traitement)
//si vous avez des problèmes ···
}
}
Je pense que vous pouvez le comprendre d'une manière ou d'une autre, mais je vais l'expliquer.
J'ai GreetDao comme variable membre, mais il a une annotation EasyMock. En joignant ceci, vous pouvez utiliser EasyMock.
createMock
La méthode setUp utilise la méthode createMock
pour créer un greetDao.
Le simulacre sera défini en passant le greetDao créé ici lors de la création de l'instance de helloJunit, qui est la méthode cible du test.
Dans la configuration des commentaires, nous utilisons la méthode ʻexpect`. C'est la partie la plus importante qui détermine le comportement du simulacre.
Lorsque greetDao.getGreet () est appelé comme ceci, il est configuré pour retourner ~.
Spécifiez comment appeler la méthode avec ʻexpected () . Puis définissez la valeur de retour avec ʻandReturn ()
.
Dans ce cas, lorsque greetDao.getGreet () sans argument est appelé, "Hello" est renvoyé.
La méthode appelée replay
est une image qui se souvient du comportement du jeu de simulation immédiatement avant.
Assurez-vous de rejouer après avoir exécuté attendu ().
exercise Ici, la méthode que vous souhaitez tester est exécutée et stockée dans une variable. On dit qu'il est facile de comprendre si les variables stockées sont unifiées en «réelles» quel que soit le type.
verify
Ici, nous comparons la valeur de retour et la valeur attendue de helloJunit.sayGreeting ();
.
De plus, verify (greetDao);
vérifie que le comportement de la maquette spécifiée dans la configuration a été appelé comme prévu.
Si helloJunit.sayGreeting ();
n'a jamais été appelé, une erreur sera renvoyée.
tear down Il s'agit du post-traitement post-test. Par exemple, si des données de test ont été entrées dans le DB, ces données seront supprimées.
Cette fois, vérifiez si la méthode findDreet de greetDao est appelée comme prévu.
Code à tester
public String sayGreeting() {
return greetDao.findGreet(1);
}
Dans ʻexpect () , écrivez la méthode d'appel de méthode attendue telle quelle. Dans ce cas, on suppose que 1 est inclus dans l'argument de
findGreet, donc décrivez simplement
findGreet (1)`.
Code de test
@Test
test d'argument public void() throws Exception {
//installer
expect(greetDao.findGreet(1)).andReturn("Hello");
replay(greetDao);
//exercice
String actual = helloJunit.sayGreeting();
//Vérifier
verify(greetDao);
}
À propos, s'il est appelé par findGreet (2);
, l'erreur suivante sera générée.
Référence en cas d'erreur
java.lang.AssertionError:
Unexpected method call findGreet(2):
findGreet(1): expected: 1, actual: 0
at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:32)
at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:61)
at com.sun.proxy.$Proxy4.findGreet(Unknown Source)
at org.seasar.sastruts.example.service.HelloJunit.sayGreeting(HelloJunit.java:17)
at org.seasar.sastruts.example.service.HelloJunitTest.Test d'argument(HelloJunitTest.java:52)
--Omis par la suite--
J'ai essayé d'insérer une branche par la valeur de retour de findGreet
.
Dans ce test, je voudrais tester le modèle dans lequel les pâtes sont retournées.
Code de production
public String sayGreeting() {
String greet = greetDao.findGreet(1);
if (greet.contentEquals("Hello")) {
return "Pâtes";
} else {
return "ramen";
}
}
Pour définir la valeur de retour, spécifiez la valeur de retour dans ʻandReturn`. Dans ce cas, "Hello" sera retourné.
Code de test
@Test
test d'argument public void() throws Exception {
//installer
expect(greetDao.findGreet(1)).andReturn("Hello");
replay(greetDao);
//exercice
String actual = helloJunit.sayGreeting();
//Vérifier
String expected = "Pâtes";
assertThat(actual, is(expected));
verify(greetDao);
}
Comment tester une méthode de type void.
Code de production (GreetDao)
public interface GreetDao {
public void insertGreet(String greet);
}
Code de production (HelloJunit)
public String addGreet() {
greetDao.insertGreet("Good Night");
return "OK";
}
Si vous souhaitez tester le type void, exécutez en fait la méthode void dans votre code de test. Puis appelez ʻexpectLastCall () `. Cela vous permet de vous souvenir et de vérifier la dernière exécution de la méthode.
Par ailleurs, lorsque la même méthode void est exécutée deux fois, on vérifie si elle a été exécutée le nombre de fois en ajoutant times () comme ʻexpectLastCall (). Time (2); `.
Code de test
@Test
test de la méthode public void void() throws Exception {
//installer
greetDao.insertGreet("Good Night");
expectLastCall();
replay(greetDao);
//exercice
String actual = helloJunit.sayGreeting();
//Vérifier
verify(greetDao);
}
EasyMock ne peut simuler que des classes d'interface. Si vous voulez vous moquer d'une classe concrète, essayez la méthode d'héritage ci-dessous.
Si la classe utilisée dans la méthode testée n'est pas une interface ou si elle n'est pas pratique à tester, vous pouvez en hériter pour la tester et préparer votre propre classe fictive.
Par exemple, si les beans retournés par la méthode ne remplacent pas la méthode equals et qu'il est difficile de vérifier chaque champ, héritez de equals et testez comme suit.
Code de production à tester
public TransferResultDto transfer(String payerAccountId,
String payeeAccountId,
String payerName,
long transferAmount) {
Balance payerBalance = balanceDao.findByAccountId(payerAccountId);
if (payerBalance == null)
throw new BusinessLogicException("Je ne peux pas trouver l'équilibre");
if (transferAmount > payerBalance.amount)
throw new BusinessLogicException("Pas assez d'équilibre");
Balance payeeBalance = balanceDao.findByAccountId(payeeAccountId);
if (payeeBalance == null)
throw new BusinessLogicException("Le compte de transfert n'existe pas");
LocalDateTime transferDate = LocalDateTime.now();
Transfer peyerTransaction = new Transfer();
peyerTransaction.accountId = payerAccountId;
peyerTransaction.name = payerBalance.name;
peyerTransaction.transferAmount = -transferAmount;
peyerTransaction.transferDate = transferDate;
Transfer payeeTransaction = new Transfer();
payeeTransaction.accountId = payeeAccountId;
payeeTransaction.name = payeeBalance.name;
payeeTransaction.transferAmount = transferAmount;
payeeTransaction.transferDate = transferDate;
transferDao.insertTransfer(peyerTransaction);
transferDao.insertTransfer(payeeTransaction);
balanceDao.updateAmount(payerAccountId, -transferAmount);
balanceDao.updateAmount(payeeAccountId, transferAmount);
Balance updatedPayerBalance = balanceDao.findByAccountId(payerAccountId);
return new TransferResultDto(payerAccountId, payeeAccountId,
payerBalance.name, payeeBalance.name,
transferAmount,
updatedPayerBalance.amount);
}
}
Retour Bean
public class TransferResultDto {
private final String payerAccountId;
private final String payeeAccountId;
private final String payerName;
private final String payeeName;
private final long transferAmount;
private final long amount;
public TransferResultDto(String payerAccountId,
String payeeAccountId,
String payerName,
String payeeName,
long transferAmount,
long amount) {
this.payerAccountId = payerAccountId;
this.payeeAccountId = payeeAccountId;
this.payerName = payerName;
this.payeeName = payeeName;
this.transferAmount = transferAmount;
this.amount = amount;
}
public String getPayerAccountId() {
return payerAccountId;
}
public String getPayeeAccountId() {
return payeeAccountId;
}
public String getPayerName() {
return payerName;
}
public String getPayeeName() {
return payeeName;
}
public long getTransferAmount() {
return transferAmount;
}
public long getAmount() {
return amount;
}
}
Pour tester cela, créez une classe TransferResultDtoMock
qui hérite de TransferResultDto
dans la classe de test.
Dans cette classe TransferResultDtoMock
, préparez une méthode equals qui compare tous les champs, et lors du test, vous pouvez comparer avec est comme ʻassertThat (réel, est (attendu));`.
python
public class TransferServiceTest {
@Test
public void essayer l'héritage simulé() {
// setup
TransferResultDtoMock expected =
new TransferResultDtoMock("1", "2",
ACCOUNT1_BEFORE_BALANCE.name, ACCOUNT2_BALANCE.name,
2000, ACCOUNT1_AFTER_BALANCE.amount);
TransferResultDto transferResultDto = transferService.transfer("1", "2", "Taro Tanaka", 1000);
TransferResultDtoMock actual = new TransferResultDtoMock(transferResultDto);
assertThat(actual, is(expected));
}
@EqualsAndHashCode(callSuper = true)
private class TransferResultDtoMock extends TransferResultDto {
public TransferResultDtoMock(String payerAccountId,
String payeeAccountId,
String payerName,
String payeeName,
long transferAmount,
long amount) {
super(payerAccountId, payeeAccountId, payerName,
payeeName, transferAmount, amount);
}
public TransferResultDtoMock(TransferResultDto transferResultDto) {
super(transferResultDto.getPayerAccountId(),
transferResultDto.getPayeeAccountId(),
transferResultDto.getPayerName(),
transferResultDto.getPayeeName(),
transferResultDto.getTransferAmount(),
transferResultDto.getAmount());
}
}
}
Tout d'abord, je pense que vous avez changé la logique en DI GreetDao dans HelloJunit de l'annotation au type de constructeur. La raison en est que lorsqu'il est exprimé par des annotations, HelloJunit devient une forme ** dépendante </ font> ** en fonction du framework (Seasar2).
Code produit
public class HelloJunit {
GreetDao greetDao;
public HelloJunit(GreetDao greetDao) {
this.greetDao = greetDao;
}
public String sayGreeting() {
return greetDao.getGreet();
}
}
Par exemple, si vous décidez de passer à Spring à l'avenir, vous utiliserez @ Autowired to DI avec des annotations. Par conséquent, il est facile de modifier le framework par DI dans le constructeur.
Aussi, du point de vue du code de test, s'il est réalisé par annotation, il faut spécifier @ RunWith (Seasar2.class)
lors de la déclaration de la classe, mais si c'est DI dans le constructeur, nouveau Si vous le faites, vous pouvez facilement tester.
Un code facile à écrire est important pour les tests.
Par exemple, le code suivant crée une branche conditionnelle en fonction du résultat de la méthode statique TimeJunit.getTime ()
, mais il est difficile de remplacer la méthode statique par une simulation.
Utilisation de la méthode statique
public class ExampleJunit {
public String sayGreeting() {
int term = TimeJunit.getTime();
switch(term) {
case 1:
return "Pâtes";
case 2:
return "ramen";
default:
return "riz";
}
}
}
Le reste du code est difficile à tester même s'il est hérité et utilise des méthodes de superclasse. Par exemple, ce type de logique qui est souvent utilisé dans les systèmes plus anciens.
Super classe
public abstract class AbstractJunit {
public String sayGreeting() {
return this.getMessage();
}
abstract protected String getMessage();
}
Classe d'héritage
public class ExampleJunit extends AbstractJunit {
protected String getMessage() {
int term = 1;
switch(term) {
case 1:
return "Pâtes";
case 2:
return "ramen";
default:
return "riz";
}
}
}
Dans cet exemple, il peut être testé, mais dans le cas où l'héritage est réellement utilisé dans les affaires, la super classe est une classe divine (une classe pratique qui met de la logique dans n'importe quoi), et c'est une source de spaghetti. C'est gênant car je dois faire une réflexion dans le test.
Demandez-vous si vous devez vraiment utiliser l'héritage et l'implémenter.
Le code qui est facile à écrire des tests est un bon code
http://s2container.seasar.org/2.4/ja/easyMock.html
Recommended Posts