Unit testing is a software testing method by which individual units of source code […] are tested to determine whether they are fit for use.
One can view a unit as the smallest testable part of an application. In OOP, a unit is often an entire interface, such as a class, but could be an individual method.
public class Car {
public CarStatus start() {
Engine engine = new CarEngine();
if (engine.start()) {
return CarStatus.STARTED;
} else {
return CarStatus.NOT_STARTED;
}
}
}
new
introduit un couplage fort entre l’appelant et l’appelé
public class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public CarStatus start() {
if (engine.start()) {
return CarStatus.STARTED;
} else {
return CarStatus.NOT_STARTED;
}
}
}
Car car = new Car(
new CarEngine()
);
public class DummyEngine() extends Engine{
@Override
public boolean start() { return true; }
}
public class Test {
public void shouldStart() {
Car car = new Car(
new DummyEngine()
);
CarStatus status = car.start();
// Check status
}
IoC is a design principle in which custom-written portions of a computer program receive the flow of control from a generic framework.
Design pattern
Factory
Service Locator
Dependency Injection
Dependency injection is a technique whereby one object supplies the dependencies of another object.
JSR | Nom | Fonctionnalités |
---|---|---|
250 | Common Annotations for the Java Platform |
|
330 | Dependency Injection for Java |
|
299 | Java Contexts and Dependency Injection | Décorateurs, intercepteurs, etc. |
Injection de ressources du serveur d’apps
Permet de préciser l’emplacement de la ressource
Injection sur le champ
@Stateless
public class MyComponent {
@Resource(lookup = "java:comp/env/jdbc/myDataSource")
private DataSource dataSource;
}
Injection de n’importe quel bean (ou EJB)
Injection par type
Injection via le champ ou le constructeur
Almost every concrete Java class that has a constructor with no parameters (or a constructor designated with the annotation @Inject) is a bean.
Activation via fichier META-INF/beans.xml
Crée un bean pour chaque classe sur le classpath
Lié au cycle de vie du bean injecté
@Dependent
Scope | Annotation |
---|---|
Requête |
|
Session |
|
Application |
|
public class Dependency {}
@WebServlet
public class MyServlet extends HttpServlet {
@Inject
private Dependency dependency;
}
Note | Aucune possibilité de test unitaire |
public class Dependency {}
@WebServlet
public class MyServlet extends HttpServlet {
@Inject
public MyServlet(Dependency dependency) {
// Do stuff
}
}
public class TestDependency extends Dependency {}
public class Test {
public void testServlet() {
Dependency dep = new Dependency();
MyServlet servlet = new MyServlet(dep);
// Test the servlet
}
}
public interface Dependency {}
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}
@WebServlet
public class MyServlet extends HttpServlet {
@Inject
public MyServlet(Dependency dependency) { }
}
javax.enterprise.inject.AmbiguousResolutionException:
There is more than one Bean with type Dependency
Injection par nom
Annotation @Named
public interface Dependency {}
@Named("A")
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}
@WebServlet
public class MyServlet extends HttpServlet {
@Inject
public MyServlet(@Named("A") Dependency dep) { }
}
Manque de typage
Basé sur une chaîne de caractères
Possibilité de créer une annotation
Mais toujours une injection par nom
Permet de restreindre les candidats à l’injection
Via la création d’annotations
@Qualifier @Retention(RUNTIME) @Target({TYPE, PARAMETER})
public @interface ThisOne {}
public interface Dependency {}
@ThisOne
public class DependencyA implements Dependency {}
public class DependencyB implements Dependency {}
@WebServlet
public class MyServlet extends HttpServlet {
@Inject
public MyServlet(@ThisOne Dependency dep) { }
}
Exposer une classe externe (JDK ou librairie) en bean
Exposer un bean dont l’implémentation dépend du contexte
Exposer plusieurs beans avec la même implémentation
public interface TaxStrategy {}
public class SwissTaxStrategy implements TaxStrategy {}
public class FrenchTaxStrategy implements TaxStrategy {}
public class TaxStrategyFactory {
@Produces
public TaxStrategy getTaxStrategy() {
if ("ch".equals(Locale.getDefault().getCountry()) {
return new SwissTaxStrategy();
}
return new FrenchTaxStrategy();
}
}
public class TaxComputationServlet {
@Inject private TaxStrategy taxStrategy;
}
Gérer de la logique métier déterminée à l’exécution
Spécifier des beans pour un scénario de déploiement
Créer des beans utilisés uniquement pour les tests
Un bean principal (pas annoté)
D’autres beans annotés avec @Alternative
Bean injecté | Exemple |
---|---|
Bean principal | |
Bean configuré |
|
Possibilité d’exécuter du code :
Juste après l’instanciation
Juste avant la recollection par le ramasse-miettes
Sur une méthode :
@PostConstruct
@PreDestroy
Une méthode par annotation
Pas d’arguments
Pas d’exception checked
Type de retour void
Méthode d’instance
Peut être final
Aucune contrainte sur la visibilité
An interceptor is a class used to interpose in method invocations or lifecycle events that occur in an associated target class. The interceptor performs tasks, such as logging or auditing, that are separate from the business logic of the application and are repeated often within an application.
Tracer l’exécution d’une méthode
Tracer le temps d’exécution d’une méthode
etc.
@InterceptorBinding
@Target({ TYPE, METHOD })
@Retention(RUNTIME)
public @interface Logged { }
@Interceptor
@Logged
public class LoggingInterceptor {
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
long start = System.currentTimeMillis();
Object value = ctx.proceed();
System.out.println(
"[" + ctx.getMethod().getName() + "]: " +
(System.currentTimeMillis() - start) + " ms");
return value;
}
}
<beans>
<interceptors>
<class>
ch.frankel.LoggingInterceptor
</class>
</interceptors>
</beans>
@Logged
@Stateless
public class FooService {
public void bar() {
}
}
Decorators are outwardly similar to interceptors. However, they actually perform tasks complementary to those performed by interceptors. […] Decorators, on the other hand, do perform business logic by intercepting business methods of beans.
Appliquer un taux de TVA
Le décorateur doit implémenter la même interface que le bean décoré
Si plusieurs décorateurs s’appliquent, ils sont invoqués dans l’ordre du beans.xml
interface HasPrice {
double getPrice();
}
public class Product implements HasPrice {
private final String name;
private final double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override public double getPrice() {
return price;
}
}
@Decorator
public class ProductWithVat implements HasPrice {
private final Product product;
@Inject
public ProductWithVat(@Delegate Product product) {
this.product = product;
}
@Override public double getPrice() {
return product.getPrice() * 1.077;
}
}
CDI implémente le pattern Observer
Injection du générateur d’évènements via CDI
Basé sur le type Event<T>
Aucune restriction sur le type de l'Observer
Méthode configurée via @Observe
Aucune restriction sur le type d’évènement
public class Subject {
private Event<String> event;
@Inject
public Subject(Event<String> event) {
this.event = event;
}
public sendTimestampEvent(String message) {
event.fire("[" + System.currentTimeMillis() + "]: "
+ message);
}
}
public class Observer {
public receiveEvent(@Observe String message) {
System.out.println("Received " + message);
}
}
Il est possible, mais déconseillé, de créer des messages à partir de types simples
Il est possible, mais déconseillé, de créer des annotations pour discriminer les évènements auxquels on veut s’abonner
Il est conseillé de plutôt créer des types dédiés