WL#7770: Develop GUNIT test framework and guidelines for DD API
Affects: Server-8.0 — Status: Complete
The goals of this WL task are 1) to develop GUNIT framework to make possible writing unit tests of DD API; 2) provide guidelines to writing DD API tests. This WL task has been extracted from WL#7284.
Unit test ========= For unit tests of the new DD, there are two main approaches: 1. Testing the upper layers in the new DD implementation based on using the dictionary client and the shared cache. 2. Testing the raw module and the interaction with the handler API. These two approaches are rather different, and will be elaborated below. 1. Testing the upper layers in the new DD implementation -------------------------------------------------------- This approach bypasses the entire handler API usage, and just uses main memory data structures to simulate the persistent storage of the dictionary objects. Pros: Provides a functional dictionary cache. Cons: Hard to test functionality bypassing the cache. Parts of the dictionary cache implementation is used to replace the usage of the raw module and the handler API for storing the dd objects. Thus, when an object is to be stored, instead of writing its fields into some record which is stored in a storage engine, the entire object is just put into a data structure. Similarly, when an object is to be acquired, a lookup is done the the data structure, instead of reading records from the storage engine. If an obejct is found, it is cloned and returned to the caller. This approach provides a fully functional dictionary cache, and allows testing high level use cases within a unit testing context. A templatized unit test has been implemented based on this approach (in unittest/gunit/dd_cache-t.cc). 2. Testing the raw module and the interaction with the handler API ------------------------------------------------------------------ This approach is based on faking some of the key server data structures (such as TABLE, Field, etc.) to allow mocking a handler interface. Pros: Allows testing the interaction between the new DD and the handler. Cons: Effort demanding to implement the tests. Hard to test more complex use cases. There are basically three layers in this part of the unit test framework: 1. The base layer, comprising classes that are of general interest, also for tests that are not related to the new dd. 2. The generic dd layer, comprising subclasses of (1), designed to be used for testing the new dd. 3. The individual unit tests, using instances of classes in (2). Each of the three layers are explained in more detail below. 2.1 Base layer .............. The base layer contains classes that are partially defined already in other units tests. Some refactoring has been done to extract the classes into separate header files. Additional refactoring should be done to utilize the base layer in tests outside of the new dd to avoid implementing numerous versions of similar classes. There are three main groups of classes: - The Base_mock_HANDLER class: Mocking the pure virtual handler functions. - The Fake_TABLE and Fake_TABLE_SHARE classes: Exists already, should factor out generic code into Base_fake_* classes, and then provide various subclasses depending on use. - The various Base_mock_field_* classes: As for the Fake_TABLE* classes, these should provide common functionality to be used by various subclasses depending on use. 2.2 Generic DD layer .................... The generic DD layer contains class definitions intended to be used by a variety of unit tests related to the new dd. - The Mock_HANDLER class: For the new dd, we need to mock write_row, update_row and index_read_idx_map. - The various Mock_field_* classes: For the new dd, we mock store and val_* methods. We also implement additional fake_store and fake_val_* methods that can be invoked by the mocked methods as side effects. This makes it possible to invoke methods to both fake and verify the behavior. - Functions creating instances of Fake_TABLE, Fake_TABLE_SHARE, the required Mock_field_* classes and the Mock_HANDLER representing a single dictionary table. These functions essentially fake opening a dictionary table. 2.3 Unit test layer ................... The unit test layer contains the individual unit tests, structured in the usual way. A test fixture class overrides the StartUp and TearDown methods, which do common tasks before and after each test, e.g. initializing the dictionary, starting a transaction, etc. The main task of each individual test is to set side effects and expectations regarding the faked and mocked server classes. E.g., for a test that provokes an insert of a new schema into the schemata dd table, the index_read_idx_map is set up to return 1 (no record found), and each Mock_field_*::store method is set up to invoke fake_store on the same instance, which stores the value in a separate data field in the instance. Additionally, expectations regarding the number of calls can be set. And finally, the Handler::write_row method is expected to be called once. After the setup, the new schema is created and stored. This will invoke the various side effects and expectation checking. After storing the new schema, it is verified that the contents of the various table fields, which are set by means of the fake_store method which is invoked when store is called, is as expected. For tests reading objects from the dd tables, the field contents is set in advance, and the val_* methods are set to have the side effect that they call the fake_val_* methods to return the preset field contents. A unit test for schema objects has been implemented (in unittest/gunit/dd_schema-t.cc), and may be used as an example for additional unit tests of other object types. 2.4 Future work ............... The following issues may be improved upon in future enhancements: - Extend T::TYPE dd classes to provide schema information enabling the unit tests to create tables with the correct schema automatically. At least provide a public enumeration of the fields, as well as the total number of fields. - Templatize the get_schema_table() function to take a dd table type argument like template
get_dd_table(), then use available schema information from the T::TYPE to create the dd table object. - Implement additional unit tests both for schemata and for other dictionary entities. - Better integration with other unit tests; separate out common code into common base classes. Particularly important for the Fake_TABLE and Mock_field_* classes.
Copyright (c) 2000, 2022, Oracle Corporation and/or its affiliates. All rights reserved.