implemented with AspectJ 1.2, an aspect-oriented extension of Java. 1. Introduction. Aspect-Oriented ... cerns in software development. It is claimed that systems ...
Data Flow Integration Testing Criteria for Aspect-Oriented Programs Ot´avio Augusto Lazzarini Lemos∗, Jos´e Carlos Maldonado and Paulo Cesar Masiero Universidade de S˜ao Paulo, Instituto de Ciˆencias Matem´aticas e de Computac¸a˜ o, Av. do Trabalhador S˜ao-Carlense, 400, S˜ao Carlos, SP e-mail: [oall,jcmaldon,masiero]@icmc.usp.br Abstract Aspect-Oriented Programming (AOP) is a new technique proposed for improving separation of concerns in software development. Although it is claimed that AOP increases understandability and eases the maintenance burden, the technology cannot provide correctness by itself, and thus it also requires systematic verification, validation and testing (VV&T) approaches to improve software quality. One of the problems that can arise while using the AOP technology are the emergent data dependencies created by the integration of the primary abstractions – that implement the core functions – and aspects. Thus, an approach to exercise data flow interactions among primary abstractions and aspect enhancements is interesting for detecting faults related to these dependencies. For that purpose, a model for representing control and data flow of the integrated modules – along with four data flow testing criteria – is proposed based on the woven artifacts generated for programs implemented with AspectJ 1.2, an aspect-oriented extension of Java.
1.
Introduction
Aspect-Oriented Programming (AOP) is a new technique proposed for improving separation of concerns in software development. It is claimed that systems developed using the AOP technology tend to be more understandable and easier to maintain, based on the effective use of the separation of concerns principle. However, aspect-orientation cannot provide correctness by itself, since it does not prevent developers from introducing errors in the system during development [10]. Hence verification, validation and testing (VV&T) techniques are still important in the aspect-oriented software development process. One of the existing testing approaches is the structural testing technique (also known as white-box testing), which derives test cases from the logical structure of a program. The main idea of this technique is that one cannot trust in a piece of software if there are still certain parts of it that were never executed during testing. To the best of our knowledge, besides our work, Zhao [10, 11] is the only researcher so far that presented a structural testing approach for aspect-oriented programs. In his papers he proposed a data flow based unit testing approach to test aspect-oriented programs based on the AspectJ language [4]. At the time that Zhao proposed his work the underlying control and data flow models that could be derived from AspectJ programs made it more complex to apply the structural testing technique. The problem was that the solution adopted by the AspectJ compiler/weaver was to inline the pieces of advice on the affected points, therefore mixing the structure of aspect and non-aspect code in the woven artifacts. Although called “unit” testing, the approach of Zhao also considered the interactions among classes and aspects (because he considered clustered aspects and classes as the units to be tested). Such approach does not include testing criteria definitions, so far. Considering the testing of an aspect-oriented program, based on Sommerville [8] and Harrold and Rothermel [2], we could partition the activity in the following phases: 1. Unit Testing: The testing of individual method-like constructions. In our case, we consider the methods1 , pieces of advice and inter-type declared methods as the units to be tested. For this kind of testing – in a previous work – we defined nine structural testing criteria: allnodes (exception dependent and independent), all-crosscutting-nodes, all-edges (exception ∗
Supported by Conselho Nacional de Desenvolvimento Cient´ıfico e Tecnol´ogico (CNPq). In this paper, for simplification purposes we use the term “method” to indicate not only methods but also class constructors. 1
dependent and independent), all-crosscutting-edges, all-uses (exception dependent and independent) and all-crosscutting-uses [6]; 2. Module Testing: The testing of a collection of dependent units (units that interact with each other by calling methods or being affected by pieces of advice). This phase can be divided in the following levels (considering classes and aspects): • Inter-method testing: Consists in testing each public method together with other methods that it calls directly or indirectly (indirect calls are the ones that occur outside the scope of the method itself, inside a called method in any depth) [2]. • Advice-method testing: Consists in testing each piece of advice together with other methods that it calls directly or indirectly. • Method-advice testing: Consists in testing each public method together with the pieces of advice that affect it directly or indirectly (considering the fact that advices may also affect advices). In this level it is not considered the integration of the affected methods with other methods they call, neither with methods called inside affecting advices. • Inter-method-advice testing: Consists in testing each public method together with the pieces of advice that affect it directly or indirectly, and with other methods that it calls directly or indirectly. This type of testing includes the first three phases described above. • Intra-class testing: Consists in testing the interactions of public methods of a class when they are called in various sequences [2], considering or not aspects enhancements. • Inter-class testing: Consists in testing class interactions, considering or not aspects enhancements. 3. System Testing: The integration of clusters that form the whole system. For this level it is usually used the functional testing technique. In a previous work we proposed a structural unit testing approach for testing aspect-oriented programs based on the implementation strategy adopted by the AspectJ 1.2 compiler [6, 5]. The idea was to instrument the woven artifacts (resulting bytecode) in order to derive control and data flow models that could represent the structure of an aspect-oriented program. Based on these models, structural testing criteria were defined. In this paper we take a step ahead, focusing on the integration of aspects and classes that implement the primary functions (component classes). The idea is to extend the models presented for unit testing in order to support data flow testing of methods integrated with pieces of advice that affect them directly (i.e. the Method-advice testing described above, but only considering direct enhancements). We suppose that this is an important type of testing for aspect-oriented programs, as erroneous data interactions among classes and aspects can be a highly probable source of faults. Also, as it is considered only direct aspect enhancements, the models are generally not computational expensive to construct (as opposed to when it is considered, for instance, indirect method calls). Moreover, based on the different interactions of aspects and components and on the all-uses traditional data flow criterion, we define four aspect-oriented data flow integration testing criteria. The remaining of this paper is organized as follows. Section 2 briefly introduces the AspectJ language and related implementation strategy. Section 3 presents the underlying concepts of the data flow testing of aspect-oriented programs, providing the definitions needed in Section 4. Section 4 presents the method-advice integration testing approach, defining data flow based testing criteria and also presenting an example of the application of the approach. Section 5 presents some concluding remarks. 2.
The AspectJ Language and Implementation Strategy
AspectJ is an extension of the Java language to support aspect-oriented programming in this context. The basic new constructs are the aspect itself; the before, after and around advice, that are
used to define crosscutting behavior; and the pointcut designators which are used to define sets of join points in the program. Aspects are units that combine: join point specifications (points in the system where the behavior may be affected), pieces of advice which are the actual desired behavior to be added and methods, fields and inner classes. Also, aspects may declare members that cut across multiple classes, or change the inheritance relationship between classes using inter-type declarations. Before, after and around advice are method-like constructs that can be executed before, after and in place of the captured join points, respectively. These constructs can also pick context information from the join point that caused them to execute. The join point categories of AspectJ include: method call, method execution, field get, field set and others. Figure 1 lists the source code of an aspect-oriented application that will be used along this paper. The application has no meaning, it just shows several kinds of aspect-class control and data flow interactions that can occur in an aspect-oriented program. The affectedMethod of the Point class is affected by all pieces of advice of the AnAspect aspect. One of them runs before the execution of the method, catching a Point and two integer arguments that are passed to the method. Another one runs after the execution of the method catching the same arguments. Other two pieces of advice act in the affectedMethod, around the setting of the a attribute of the Point class and before the handling of exceptions of type AnException. It is important to notice that if a Point catched by a pieces of advice is changed in the advice scope, the change is propagated to the method. public class Point { public int x; public int y; public AClass a; public Point(int _x, int _y) { x = _x; y = _y; } public void affectedMethod(Point p, int _x, int _y) { try { if (p.x = 0) p.y = j + 4; } void around(AClass a) : settingA(a) { System.out.println("around settingA"); a.a = 20; a.b = 30; proceed(a); } after (Point p, int i, int j) returning(): exec(p, i, j) { System.out.println("after " + "returning exec"); if (i > 10) System.out.println("i > 10"); else System.out.println("i 10"); else System.out.println("p.x