]> git.proxmox.com Git - mirror_edk2.git/blob - UnitTestFrameworkPkg/ReadMe.md
64386941cb4ba0e791cbe16cc734d71600249a88
[mirror_edk2.git] / UnitTestFrameworkPkg / ReadMe.md
1 # Unit Test Framework Package
2
3 ## About
4
5 This package adds a unit test framework capable of building tests for multiple contexts including
6 the UEFI shell environment and host-based environments. It allows for unit test development to focus
7 on the tests and leave error logging, result formatting, context persistance, and test running to the framework.
8 The unit test framework works well for low level unit tests as well as system level tests and
9 fits easily in automation frameworks.
10
11 ### UnitTestLib
12
13 The main "framework" library. The core of the framework is the Framework object, which can have any number
14 of test cases and test suites registered with it. The Framework object is also what drives test execution.
15
16 The Framework also provides helper macros and functions for checking test conditions and
17 reporting errors. Status and error info will be logged into the test context. There are a number
18 of Assert macros that make the unit test code friendly to view and easy to understand.
19
20 Finally, the Framework also supports logging strings during the test execution. This data is logged
21 to the test context and will be available in the test reporting phase. This should be used for
22 logging test details and helpful messages to resolve test failures.
23
24 ### UnitTestPersistenceLib
25
26 Persistence lib has the main job of saving and restoring test context to a storage medium so that for tests
27 that require exiting the active process and then resuming state can be maintained. This is critical
28 in supporting a system reboot in the middle of a test run.
29
30 ### UnitTestResultReportLib
31
32 Library provides function to run at the end of a framework test run and handles formatting the report.
33 This is a common customization point and allows the unit test framework to fit its output reports into
34 other test infrastructure. In this package a simple library instances has been supplied to output test
35 results to the console as plain text.
36
37 ## Samples
38
39 There is a sample unit test provided as both an example of how to write a unit test and leverage
40 many of the features of the framework. This sample can be found in the `Test/UnitTest/Sample/SampleUnitTest`
41 directory.
42
43 The sample is provided in PEI, SMM, DXE, and UEFI App flavors. It also has a flavor for the HOST_APPLICATION
44 build type, which can be run on a host system without needing a target.
45
46 ## Usage
47
48 This section is built a lot like a "Getting Started". We'll go through some of the components that are needed
49 when constructing a unit test and some of the decisions that are made by the test writer. We'll also describe
50 how to check for expected conditions in test cases and a bit of the logging characteristics.
51
52 Most of these examples will refer to the SampleUnitTestUefiShell app found in this package.
53
54 ### Requirements - INF
55
56 In our INF file, we'll need to bring in the `UnitTestLib` library. Conveniently, the interface
57 header for the `UnitTestLib` is located in `MdePkg`, so you shouldn't need to depend on any other
58 packages. As long as your DSC file knows where to find the lib implementation that you want to use,
59 you should be good to go.
60
61 See this example in 'SampleUnitTestUefiShell.inf'...
62
63 ```
64 [Packages]
65 MdePkg/MdePkg.dec
66
67 [LibraryClasses]
68 UefiApplicationEntryPoint
69 BaseLib
70 DebugLib
71 UnitTestLib
72 PrintLib
73 ```
74
75 Also, if you want you test to automatically be picked up by the Test Runner plugin, you will need
76 to make sure that the module `BASE_NAME` contains the word `Test`...
77
78 ```
79 [Defines]
80 BASE_NAME = SampleUnitTestUefiShell
81 ```
82
83 ### Requirements - Code
84
85 Not to state the obvious, but let's make sure we have the following include before getting too far along...
86
87 ```c
88 #include <Library/UnitTestLib.h>
89 ```
90
91 Now that we've got that squared away, let's look at our 'Main()'' routine (or DriverEntryPoint() or whatever).
92
93 ### Configuring the Framework
94
95 Everything in the UnitTestPkg framework is built around an object called -- conveniently -- the Framework.
96 This Framework object will contain all the information about our test, the test suites and test cases associated
97 with it, the current location within the test pass, and any results that have been recorded so far.
98
99 To get started with a test, we must first create a Framework instance. The function for this is
100 `InitUnitTestFramework`. It takes in `CHAR8` strings for the long name, short name, and test version.
101 The long name and version strings are just for user presentation and relatively flexible. The short name
102 will be used to name any cache files and/or test results, so should be a name that makes sense in that context.
103 These strings are copied internally to the Framework, so using stack-allocated or literal strings is fine.
104
105 In the 'SampleUnitTestUefiShell' app, the module name is used as the short name, so the init looks like this.
106
107 ```c
108 DEBUG(( DEBUG_INFO, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION ));
109
110 //
111 // Start setting up the test framework for running the tests.
112 //
113 Status = InitUnitTestFramework( &Framework, UNIT_TEST_APP_NAME, gEfiCallerBaseName, UNIT_TEST_APP_VERSION );
114 ```
115
116 The `&Framework` returned here is the handle to the Framework. If it's successfully returned, we can start adding
117 test suites and test cases.
118
119 Test suites exist purely to help organize test cases and to differentiate the results in reports. If you're writing
120 a small unit test, you can conceivably put all test cases into a single suite. However, if you end up with 20+ test
121 cases, it may be beneficial to organize them according to purpose. You _must_ have at least one test suite, even if
122 it's just a catch-all. The function to create a test suite is `CreateUnitTestSuite`. It takes in a handle to
123 the Framework object, a `CHAR8` string for the suite title and package name, and optional function pointers for
124 a setup function and a teardown function.
125
126 The suite title is for user presentation. The package name is for xUnit type reporting and uses a '.'-separated
127 hierarchical format (see 'SampleUnitTestApp' for example). If provided, the setup and teardown functions will be
128 called once at the start of the suite (before _any_ tests have run) and once at the end of the suite (after _all_
129 tests have run), respectively. If either or both of these are unneeded, pass `NULL`. The function prototypes are
130 `UNIT_TEST_SUITE_SETUP` and `UNIT_TEST_SUITE_TEARDOWN`.
131
132 Looking at 'SampleUnitTestUefiShell' app, you can see that the first test suite is created as below...
133
134 ```c
135 //
136 // Populate the SimpleMathTests Unit Test Suite.
137 //
138 Status = CreateUnitTestSuite( &SimpleMathTests, Fw, "Simple Math Tests", "Sample.Math", NULL, NULL );
139 ```
140
141 This test suite has no setup or teardown functions. The `&SimpleMathTests` returned here is a handle to the suite and
142 will be used when adding test cases.
143
144 Great! Now we've finished some of the cruft, red tape, and busy work. We're ready to add some tests. Adding a test
145 to a test suite is accomplished with the -- you guessed it -- `AddTestCase` function. It takes in the suite handle;
146 a `CHAR8` string for the description and class name; a function pointer for the test case itself; additional, optional
147 function pointers for prerequisite check and cleanup routines; and and optional pointer to a context structure.
148
149 Okay, that's a lot. Let's take it one piece at a time. The description and class name strings are very similar in
150 usage to the suite title and package name strings in the test suites. The former is for user presentation and the
151 latter is for xUnit parsing. The test case function pointer is what is actually executed as the "test" and the
152 prototype should be `UNIT_TEST_FUNCTION`. The last three parameters require a little bit more explaining.
153
154 The prerequisite check function has a prototype of `UNIT_TEST_PREREQUISITE` and -- if provided -- will be called
155 immediately before the test case. If this function returns any error, the test case will not be run and will be
156 recorded as `UNIT_TEST_ERROR_PREREQUISITE_NOT_MET`. The cleanup function (prototype `UNIT_TEST_CLEANUP`) will be called
157 immediately after the test case to provide an opportunity to reset any global state that may have been changed in the
158 test case. In the event of a prerequisite failure, the cleanup function will also be skipped. If either of these
159 functions is not needed, pass `NULL`.
160
161 The context pointer is entirely case-specific. It will be passed to the test case upon execution. One of the purposes
162 of the context pointer is to allow test case reuse with different input data. (Another use is for testing that wraps
163 around a system reboot, but that's beyond the scope of this guide.) The test case must know how to interpret the context
164 pointer, so it could be a simple value, or it could be a complex structure. If unneeded, pass `NULL`.
165
166 In 'SampleUnitTestUefiShell' app, the first test case is added using the code below...
167
168 ```c
169 AddTestCase( SimpleMathTests, "Adding 1 to 1 should produce 2", "Addition", OnePlusOneShouldEqualTwo, NULL, NULL, NULL );
170 ```
171
172 This test case calls the function `OnePlusOneShouldEqualTwo` and has no prerequisite, cleanup, or context.
173
174 Once all the suites and cases are added, it's time to run the Framework.
175
176 ```c
177 //
178 // Execute the tests.
179 //
180 Status = RunAllTestSuites( Framework );
181 ```
182
183 ### A Simple Test Case
184
185 We'll take a look at the below test case from 'SampleUnitTestApp'...
186
187 ```c
188 UNIT_TEST_STATUS
189 EFIAPI
190 OnePlusOneShouldEqualTwo (
191 IN UNIT_TEST_FRAMEWORK_HANDLE Framework,
192 IN UNIT_TEST_CONTEXT Context
193 )
194 {
195 UINTN A, B, C;
196
197 A = 1;
198 B = 1;
199 C = A + B;
200
201 UT_ASSERT_EQUAL(C, 2);
202 return UNIT_TEST_PASSED;
203 } // OnePlusOneShouldEqualTwo()
204 ```
205
206 The prototype for this function matches the `UNIT_TEST_FUNCTION` prototype. It takes in a handle to the Framework
207 itself and the context pointer. The context pointer could be cast and interpreted as anything within this test case,
208 which is why it's important to configure contexts carefully. The test case returns a value of `UNIT_TEST_STATUS`, which
209 will be recorded in the Framework and reported at the end of all suites.
210
211 In this test case, the `UT_ASSERT_EQUAL` assertion is being used to establish that the business logic has functioned
212 correctly. There are several assertion macros, and you are encouraged to use one that matches as closely to your
213 intended test criterium as possible, because the logging is specific to the macro and more specific macros have more
214 detailed logs. When in doubt, there are always `UT_ASSERT_TRUE` and `UT_ASSERT_FALSE`. Assertion macros that fail their
215 test criterium will immediately return from the test case with `UNIT_TEST_ERROR_TEST_FAILED` and log an error string.
216 _Note_ that this early return can have implications for memory leakage.
217
218 At the end, if all test criteria pass, you should return `UNIT_TEST_PASSED`.
219
220 ### More Complex Cases
221
222 To write more advanced tests, first take a look at all the Assertion and Logging macros provided in the framework.
223
224 Beyond that, if you're writing host-based tests and want to take a dependency on the UnitTestFrameworkPkg, you can
225 leverage the `cmocka.h` interface and write tests with all the features of the Cmocka framework.
226
227 Documentation for Cmocka can be found here:
228 https://api.cmocka.org/
229
230 ## Development
231
232 When using the EDK2 Pytools for CI testing, the host-based unit tests will be built and run on any build that includes
233 the `NOOPT` build target.
234
235 If you are trying to iterate on a single test, a convenient pattern is to build only that test module. For example,
236 the following command will build only the SafeIntLib host-based test from the MdePkg...
237
238 ```bash
239 stuart_ci_build -c .pytool/CISettings.py TOOL_CHAIN_TAG=VS2017 -p MdePkg -t NOOPT BUILDMODULE=MdePkg/Test/UnitTest/Library/BaseSafeIntLib/TestBaseSafeIntLib.inf
240 ```
241
242 ## Known Limitations
243
244 ### PEI, DXE, SMM
245
246 While sample tests have been provided for these execution environments, only cursory build validation
247 has been performed. Care has been taken while designing the frameworks to allow for execution during
248 boot phases, but only UEFI Shell and host-based tests have been thoroughly evaluated. Full support for
249 PEI, DXE, and SMM is forthcoming, but should be considered beta/staging for now.
250
251 ### Host-Based Support vs Other Tests
252
253 The host-based test framework is powered internally by the Cmocka framework. As such, it has abilities
254 that the target-based tests don't (yet). It would be awesome if this meant that it was a super set of
255 the target-based tests, and it worked just like the target-based tests but with more features. Unfortunately,
256 this is not the case. While care has been taken to keep them as close a possible, there are a few known
257 inconsistencies that we're still ironing out. For example, the logging messages in the target-based tests
258 are cached internally and associated with the running test case. They can be saved later as part of the
259 reporting lib. This isn't currently possible with host-based. Only the assertion failures are logged.
260
261 We will continue trying to make these as similar as possible.
262
263 ## Unit Test Location/Layout Rules
264
265 Code/Test | Location
266 --------- | --------
267 Host-Based Unit Tests for a Library/Protocol/PPI/GUID Interface | If what's being tested is an interface (e.g. a library with a public header file, like DebugLib), the test should be scoped to the parent package.<br/>Example: `MdePkg/Test/UnitTest/[Library/Protocol/Ppi/Guid]/`<br/><br/>A real-world example of this is the BaseSafeIntLib test in MdePkg.<br/>`MdePkg/Test/UnitTest/Library/BaseSafeIntLib/TestBaseSafeIntLibHost.inf`
268 Host-Based Unit Tests for a Library/Driver (PEI/DXE/SMM) implementation | If what's being tested is a specific implementation (e.g. BaseDebugLibSerialPort for DebugLib), the test should be scoped to the implementation directory itself, in a UnitTest subdirectory.<br/><br/>Module Example: `MdeModulePkg/Universal/EsrtFmpDxe/UnitTest/`<br/>Library Example: `MdePkg/Library/BaseMemoryLib/UnitTest/`
269 Host-Based Tests for a Functionality or Feature | If you're writing a functional test that operates at the module level (i.e. if it's more than a single file or library), the test should be located in the package-level Tests directory under the HostFuncTest subdirectory.<br/>For example, if you were writing a test for the entire FMP Device Framework, you might put your test in:<br/>`FmpDevicePkg/Test/HostFuncTest/FmpDeviceFramework`<br/><br/>If the feature spans multiple packages, it's location should be determined by the package owners related to the feature.
270 Non-Host-Based (PEI/DXE/SMM/Shell) Tests for a Functionality or Feature | Similar to Host-Based, if the feature is in one package, should be located in the `*Pkg/Test/[Shell/Dxe/Smm/Pei]Test` directory.<br/><br/>If the feature spans multiple packages, it's location should be determined by the package owners related to the feature.<br/><br/>USAGE EXAMPLES<br/>PEI Example: MP_SERVICE_PPI. Or check MTRR configuration in a notification function.<br/> SMM Example: a test in a protocol callback function. (It is different with the solution that SmmAgent+ShellApp)<br/>DXE Example: a test in a UEFI event call back to check SPI/SMRAM status. <br/> Shell Example: the SMM handler audit test has a shell-based app that interacts with an SMM handler to get information. The SMM paging audit test gathers information about both DXE and SMM. And the SMM paging functional test actually forces errors into SMM via a DXE driver.
271
272 ### Example Directory Tree
273
274 ```text
275 <PackageName>Pkg/
276 ComponentY/
277 ComponentY.inf
278 ComponentY.c
279 UnitTest/
280 ComponentYHostUnitTest.inf # Host-Based Test for Driver Module
281 ComponentYUnitTest.c
282
283 Library/
284 GeneralPurposeLibBase/
285 ...
286
287 GeneralPurposeLibSerial/
288 ...
289
290 SpecificLibDxe/
291 SpecificLibDxe.c
292 SpecificLibDxe.inf
293 UnitTest/ # Host-Based Test for Specific Library Implementation
294 SpecificLibDxeHostUnitTest.c
295 SpecificLibDxeHostUnitTest.inf
296 Test/
297 <Package>HostTest.dsc # Host-Based Test Apps
298 UnitTest/
299 InterfaceX
300 InterfaceXHostUnitTest.inf # Host-Based App (should be in Test/<Package>HostTest.dsc)
301 InterfaceXPeiUnitTest.inf # PEIM Target-Based Test (if applicable)
302 InterfaceXDxeUnitTest.inf # DXE Target-Based Test (if applicable)
303 InterfaceXSmmUnitTest.inf # SMM Target-Based Test (if applicable)
304 InterfaceXShellUnitTest.inf # Shell App Target-Based Test (if applicable)
305 InterfaceXUnitTest.c # Test Logic
306
307 GeneralPurposeLib/ # Host-Based Test for any implementation of GeneralPurposeLib
308 GeneralPurposeLibTest.c
309 GeneralPurposeLibHostUnitTest.inf
310
311 <Package>Pkg.dsc # Standard Modules and any Target-Based Test Apps (including in Test/)
312
313 ```
314
315 ### Future Locations in Consideration
316
317 We don't know if these types will exist or be applicable yet, but if you write a support library or module that matches the following, please make sure they live in the correct place.
318
319 Code/Test | Location
320 --------- | --------
321 Host-Based Library Implementations | Host-Based Implementations of common libraries (eg. MemoryAllocationLibHost) should live in the same package that declares the library interface in its .DEC file in the `*Pkg/HostLibrary` directory. Should have 'Host' in the name.
322 Host-Based Mocks and Stubs | Mock and Stub libraries should live in the `UefiTestFrameworkPkg/StubLibrary` with either 'Mock' or 'Stub' in the library name.
323
324 ### If still in doubt...
325
326 Hop on GitHub and ask @corthon, @mdkinney, or @spbrogan. ;)
327
328 ## Copyright
329
330 Copyright (c) Microsoft Corporation.
331 SPDX-License-Identifier: BSD-2-Clause-Patent