Testing object-oriented software systems

Lecture



.one. Features of testing object-oriented software systems

Testing is the process of finding errors by means of a series of program test runs with a previously prepared set of input data and comparing the results of execution with the specifications for the program. Testing as it is known does not guarantee the correctness of the program, but allows you to get at least some evaluation of this correctness. Various testing methods have been developed, and some of them, for example, the method of testing all branches, provide high reliability when errors are detected.

Methods of testing structural programs for a long time and well developed (nevertheless, even for structural programs, testing is not a completely closed topic). Object-oriented programs inevitably add specific problems to the testing technology. These problems are defined by two main aspects [11]:

  • The control graph of the program is no longer the fully defining element of this program, due to the fact that classes must be compiled taking into account the properties of the subject area and the ability to solve other problems.
  • The element of testing is not a subprogram whose structure is well represented both by interface and control column, but by a class that has a set of attributes and behavioral methods that are not rigidly connected with each other.

Object-oriented approach does not guarantee the creation of correct programs. Therefore, testing is also necessary for object-oriented programs as for structural ones. The advantages of this approach, which are expressed in the possibility of reuse, lead to the need for more thorough testing. Classes are usually created in advance with the ability to use in various tasks, and therefore one task can not fully ensure the testing of the interaction of methods of classes.

The basic properties of objects (encapsulation, inheritance, polymorphism) add new aspects of testing.

Encapsulation creates data visibility problems, as they are only available through operations. During testing, this can create certain problems with the output of values.

Inheritance raises questions about repeating testing of inherited operations. Suppose operation A belongs to the base class, and it has been tested. Operation B belongs to the derived class and invokes operation A. Should operation A be retested?

Polymorphism leads to ambiguity with calling operations, which can be resolved only at the execution stage. Therefore, it is impossible to build and plan a test suite in advance.

Object-oriented technology brings its own characteristics to the system testing process. Several questions are formulated that need to be resolved for successful testing:

  • What part of the inherited properties should be re-tested.
  • When and how can you check class status information?
  • How can you check the behavior of the system, depending on the state, when there is no single mechanism for managing the states in the program.
  • How to test class integration and which testing strategies to apply.

The solution of such issues is carried out by developing new approaches and modernizing old ones, especially for testing object-oriented systems.

2. Methods of testing object-oriented systems. Choosing a basic component for testing.

The basic unit of testing should be a class (object). It is useless to consider separate class methods in isolation from the class itself, while other components are usually an aggregation of classes. The purpose of a class in a system (for example, an abstract class) determines the characteristics of its testing. A class is a set of attributes and methods (usually some of them are hidden) and, therefore, the control graph is not applicable. Note the main features of the new testing module:

  • no global data (or they are minimized as constants),
  • the class is not a testable element, only objects can be tested, i.e. class instances
  • It is impossible to test class behavior methods in isolation from each other, since they use common attributes, the values ​​of which may vary depending on the set and sequence of methods being called.

Testing an instance of a class (object) can be carried out in isolation, but the class can be considered fully tested only after its instance has been created and verified within the system.

Testing inheritance. Inherited methods must be re-tested when redefining or supplementing the base method. This is due to the fact that a new context of attributes, new aspects of behavior may add new errors. Thus, if class A is basic for class B. Then all methods of class A are tested first, then all methods of class B, so that all methods of class A called from B are tested again.

Encapsulation , by itself, is not a source of errors, but it is an obstacle for testing, since testing requires obtaining complete information about the state of a class, including all its attributes, many of which can be declared as "invisible" from the outside. The solution to this problem can be found in the definition of special debugging methods that return information about the state of the class or in the use of low-level debuggers of program code.

Polymorphism testing Each possible variant of a polymorphic method call must be tested. An example of such a case is class A, which contains the method MA, which in turn calls the polymorphic method MAV. Then, for any derived class B that overrides the MAV method, a test should be performed on calling the MA method.

Testing based on internal structure. Such a method is commonly referred to as "white-box testing" (transparent box method). Testing is performed by reviewing the structure and identifying possible test sequences. There are two options for such testing: testing based on the class interface and testing based on the class methods. In the first case, the test suite is designed to test the functioning of the interface properties of the object, such as messages and exceptions. The main methods for compiling tests are the analysis of the control transfer graph within the framework of the object methods and the analysis of the data flow.

Testing without internal structure. The so-called black box testing ("Black-box testing"). The test suite is compiled based on the specification and requirements for the class. The class structure is not analyzed. It is necessary to take into account that any specification does not reflect (and should not reflect) all implementation details, therefore, analysis of the program code is still required. Therefore, the differences between white-box and black-box testing are reduced.

Testing based on object states. In state-based testing, the test suite is determined based on the simulation of the class as a finite state machine. The results of the execution of the cash methods are considered as transitions between class states. The automaton class model determines the sequence of state changes, which should be checked during testing. Usage diagrams can serve as the basis for compiling an automaton class model; it is also permissible to determine the admissible states by the white box method (ie, based on the analysis of program code). Such testing is not without difficulties. The class can be oriented to any sequence of method calls and in this case state testing will be ineffective. Managing the change of state of the object can be dispersed throughout the application. Such "joint" management makes isolated class testing very difficult. The solution may be to compile a global automaton model of the application, in order to determine the interaction of classes in it.

Problems of adequacy and coverage when testing. Testing of each class, taking into account the hierarchy tree, can be performed multiple times. This ensures overall compliance with testing requirements, but does not guarantee coverage of all possible causes of errors. In addition to testing all classes, you should perform checks on operations implemented in the system, since their execution can be associated with multiple invocation of methods of various classes. It is recommended to conduct testing based on possible errors. The sources of information in this case may be the specifications of the software (project, user documentation, etc.) or the source text of the program.

The integration of classes to create an application system is closely intertwined with the general approach to development. There are two integration strategies: task-based and use-based. The first one defines the whole chain of classes, starting with the main (managing) class and ending with the classes "performers". If each class is verified, then the entire task is considered tested. Use-based integration is used when there are several or no control classes at all. Individual classes use other groups of classes to perform their tasks. Testing of this type of integration is carried out by selecting individual groups of classes, and testing each class using this group. The object-oriented approach is distinguished by the fact that with the transition to a higher level of the class hierarchy, the amount of work to create a class and, accordingly, the testing capabilities are reduced. The problem in this case is determining when to test the inherited properties of a class, the integration of classes. In each case, you should make a test plan.

A software project written in accordance with an object-oriented approach will have a GMF that is significantly different from the traditional GMP "procedural" program. The development of the project itself is based on a different principle - from defining the classes used in the program, building the class tree to implementing the project code. With proper use of classes that accurately reflect an application's application area, this method provides shorter, understandable, and easily controlled programs.

Object-oriented software is event-driven. The transfer of control within the program is carried out not only by explicitly indicating the sequence of calls of some functions of the program to others, but also by generating messages to various objects, parsing messages with the appropriate handler and transmitting them to the objects for which these messages are intended. Considered GMF in this case becomes inapplicable. This model, at a minimum, requires adaptation to the requirements introduced by an object-oriented approach to writing software. In this case, a transition from a model that describes the structure of the program to a model that describes the behavior of the program occurs, which for testing can be classified as a positive feature of this transition. A negative aspect of the performed transition for the application of the models considered earlier is the loss of explicitly defined links between the program modules.

Before proceeding to the description of the graph model of an object-oriented program, let us dwell separately on one significant aspect of software development in the object-oriented programming language (OOP), for example, C ++ or C #. Development of high quality software for MS Windows or any other operating system using the standard "look and feel", using only newly created classes is practically impossible. The programmer will have to spend a lot of time on solving standard tasks for creating a user interface. To avoid working on long-resolved issues, all modern compilers provide special class libraries. Such libraries include almost the entire software interface of the operating system and allow you to use tools of a higher level when programming than just function calls. Basic constructions and classes can be reused when developing a new software project. This significantly reduces application development time. An example of such a system is the Microsoft Foundation Class library for the MS Visual C ++ compiler [21]. "

The job of testing the application should not include testing the functionality of library elements that have become the de facto industry standard for software development, but only checking the code written directly by the developer of the software project. Testing an object-oriented program should include the same levels as testing a procedural program — modular, integration, and system. Within the class, separately taken methods have the temporal character of execution. All OOP languages ​​return control to the caller when the message is processed. Therefore, each method (function - a member of the class) must pass the traditional unit testing according to the selected criterion C (as a rule, C1). In accordance with the above notation, we call the Mod i method, and the testing complexity is V (Mod i , C). All the results obtained in lecture 5 for testing modules are certainly suitable for testing class methods. Each class must be considered as a subject of integration testing. Integration for all class methods is performed using an incremental bottom-up strategy. At the same time, we can reuse tests for parent classes of the tested class [22], which follows from the principle of inheritance - from base classes with no parents to the highest levels of classes

in object-oriented programming, a Mock object is often used (from the English. mock object , literally: “parody object”, “imitation object”, and also “stand”) - the type of objects that implement specified aspects of the simulated program environment. for example internet connection emulation or work with databases


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Quality Assurance

Terms: Quality Assurance