0
# Testing Utilities
1
2
Comprehensive testing framework extending Python's unittest with additional assertions, parameterized testing, temporary file management, flag state management, and enhanced test discovery and execution capabilities.
3
4
## Capabilities
5
6
### Enhanced Test Case
7
8
Extended test case class with additional assertions and utilities for absl-based applications.
9
10
```python { .api }
11
class TestCase(unittest.TestCase):
12
"""
13
Enhanced test case class extending unittest.TestCase.
14
15
Provides additional assertions, temporary file management,
16
and integration with absl flags and logging systems.
17
"""
18
19
def setUp(self):
20
"""Set up test fixtures before each test method."""
21
22
def tearDown(self):
23
"""Clean up after each test method."""
24
25
def create_tempdir(self, name=None, cleanup=TempFileCleanup.ALWAYS):
26
"""
27
Create a temporary directory for the test.
28
29
Args:
30
name (str): Optional name for the directory
31
cleanup (TempFileCleanup): When to clean up the directory
32
33
Returns:
34
_TempDir: Temporary directory object
35
"""
36
37
def create_tempfile(self, file_path=None, content=None, mode='w', encoding='utf-8',
38
errors='strict', cleanup=TempFileCleanup.ALWAYS):
39
"""
40
Create a temporary file for the test.
41
42
Args:
43
file_path (str): Optional path within temp directory
44
content (str): Optional content to write to file
45
mode (str): File open mode
46
encoding (str): File encoding
47
errors (str): Error handling mode
48
cleanup (TempFileCleanup): When to clean up the file
49
50
Returns:
51
_TempFile: Temporary file object
52
"""
53
54
# Additional assertion methods
55
def assertEmpty(self, container, msg=None):
56
"""Assert that a container is empty."""
57
58
def assertNotEmpty(self, container, msg=None):
59
"""Assert that a container is not empty."""
60
61
def assertLen(self, container, expected_len, msg=None):
62
"""Assert that a container has the expected length."""
63
64
def assertStartsWith(self, actual, expected_start, msg=None):
65
"""Assert that a string starts with expected prefix."""
66
67
def assertEndsWith(self, actual, expected_end, msg=None):
68
"""Assert that a string ends with expected suffix."""
69
70
def assertSequenceStartsWith(self, prefix, whole, msg=None):
71
"""Assert that a sequence starts with expected prefix sequence."""
72
73
def assertContainsSubset(self, expected_subset, actual_container, msg=None):
74
"""Assert that actual_container contains all elements in expected_subset."""
75
76
def assertNoCommonElements(self, expected_seq, actual_seq, msg=None):
77
"""Assert that two sequences have no common elements."""
78
79
def assertItemsEqual(self, expected_seq, actual_seq, msg=None):
80
"""Assert that two sequences have the same elements (ignoring order)."""
81
82
def assertCountEqual(self, expected_seq, actual_seq, msg=None):
83
"""Assert that two sequences have the same elements (ignoring order)."""
84
85
def assertSameElements(self, expected_seq, actual_seq, msg=None):
86
"""Assert that two sequences have the same elements (ignoring order)."""
87
88
def assertSetEqual(self, expected_set, actual_set, msg=None):
89
"""Assert that two sets are equal."""
90
91
def assertListEqual(self, expected_list, actual_list, msg=None):
92
"""Assert that two lists are equal."""
93
94
def assertTupleEqual(self, expected_tuple, actual_tuple, msg=None):
95
"""Assert that two tuples are equal."""
96
97
def assertSequenceEqual(self, expected_seq, actual_seq, msg=None, seq_type=None):
98
"""Assert that two sequences are equal."""
99
100
def assertDictEqual(self, expected_dict, actual_dict, msg=None):
101
"""Assert that two dictionaries are equal."""
102
103
def assertDictContainsSubset(self, expected_subset, actual_dict, msg=None):
104
"""Assert that actual_dict contains all key-value pairs in expected_subset."""
105
106
def assertUrlEqual(self, expected_url, actual_url, msg=None):
107
"""Assert that two URLs are equal (handling URL encoding differences)."""
108
109
def assertSameStructure(self, expected, actual, abridge=True, msg=None):
110
"""Assert that two nested structures have the same shape."""
111
112
def assertJsonEqual(self, expected, actual, msg=None):
113
"""Assert that two JSON-serializable objects are equal."""
114
115
def assertBetween(self, value, lower_bound, upper_bound, msg=None):
116
"""Assert that a value is between lower_bound and upper_bound (inclusive)."""
117
118
def assertLoggedLines(self, expected_logs, stream=None, min_level=None):
119
"""Assert that specific log lines were logged."""
120
121
def assertRaisesWithLiteralMatch(self, expected_exception, expected_exception_message, callable_obj=None, *args, **kwargs):
122
"""Assert that an exception is raised with exact message match."""
123
124
def assertRaisesWithPredicateMatch(self, exception_predicate, callable_obj=None, *args, **kwargs):
125
"""Assert that an exception is raised matching a predicate function."""
126
```
127
128
### Test Runner
129
130
Main entry point for running absl-based tests with enhanced features.
131
132
```python { .api }
133
def main(*args, **kwargs):
134
"""
135
Main test runner function.
136
137
Automatically discovers and runs tests with absl integration,
138
flag parsing, and enhanced output formatting.
139
140
Args:
141
*args: Arguments passed to unittest.main
142
**kwargs: Keyword arguments passed to unittest.main
143
"""
144
145
def run_tests(test_runner=None, argv=None, args=None, **kwargs):
146
"""
147
Run tests with custom test runner and arguments.
148
149
Args:
150
test_runner: Custom test runner class
151
argv: Command line arguments
152
args: Additional arguments
153
**kwargs: Additional keyword arguments
154
155
Returns:
156
unittest.TestResult: Test execution results
157
"""
158
159
class TestLoader(unittest.TestLoader):
160
"""
161
Enhanced test loader with additional discovery capabilities.
162
"""
163
164
def loadTestsFromModule(self, module, *args, **kwargs):
165
"""Load tests from a module with enhanced discovery."""
166
```
167
168
### Temporary File Management
169
170
Utilities for managing temporary files and directories in tests.
171
172
```python { .api }
173
class TempFileCleanup(enum.Enum):
174
"""Enumeration for temporary file cleanup behavior."""
175
176
ALWAYS = 'always' # Clean up after every test
177
SUCCESS = 'success' # Clean up only on test success
178
FAILURE = 'failure' # Clean up only on test failure
179
180
class _TempDir:
181
"""Temporary directory manager for tests."""
182
183
def __init__(self, path, cleanup):
184
"""Initialize temporary directory."""
185
186
@property
187
def full_path(self):
188
"""Get the full path to the temporary directory."""
189
190
def create_file(self, file_path, content='', mode='w'):
191
"""Create a file within the temporary directory."""
192
193
def mkdir(self, dir_path):
194
"""Create a subdirectory within the temporary directory."""
195
196
class _TempFile:
197
"""Temporary file manager for tests."""
198
199
def __init__(self, path, cleanup):
200
"""Initialize temporary file."""
201
202
@property
203
def full_path(self):
204
"""Get the full path to the temporary file."""
205
206
def read_text(self, encoding='utf-8'):
207
"""Read the file contents as text."""
208
209
def write_text(self, content, encoding='utf-8'):
210
"""Write text content to the file."""
211
```
212
213
### Test Utilities
214
215
Additional utility functions for testing.
216
217
```python { .api }
218
def expectedFailureIf(condition, reason):
219
"""
220
Decorator to mark a test as expected failure under certain conditions.
221
222
Args:
223
condition (bool): If True, mark test as expected failure
224
reason (str): Reason for expected failure
225
226
Returns:
227
Decorator function
228
"""
229
230
def skipThisClass(reason):
231
"""
232
Decorator to skip an entire test class.
233
234
Args:
235
reason (str): Reason for skipping the class
236
237
Returns:
238
Decorator function
239
"""
240
241
def get_default_test_random_seed():
242
"""
243
Get the default random seed for tests.
244
245
Returns:
246
int: Default random seed value
247
"""
248
249
def get_default_test_srcdir():
250
"""
251
Get the default test source directory.
252
253
Returns:
254
str: Path to test source directory
255
"""
256
257
def get_default_test_tmpdir():
258
"""
259
Get the default test temporary directory.
260
261
Returns:
262
str: Path to test temporary directory
263
"""
264
```
265
266
## Parameterized Testing
267
268
Utilities for creating parameterized tests with multiple test cases from a single test method.
269
270
### Parameterized Test Case
271
272
```python { .api }
273
class parameterized.TestCase(absltest.TestCase):
274
"""
275
Test case class with parameterized testing support.
276
277
Inherits from absltest.TestCase and adds parameterized test generation.
278
"""
279
280
class parameterized.TestGeneratorMetaclass(type):
281
"""Metaclass for generating parameterized tests."""
282
```
283
284
### Parameterization Decorators
285
286
```python { .api }
287
def parameterized.parameters(*testcases):
288
"""
289
Decorator to parameterize a test method.
290
291
Args:
292
*testcases: Test case parameters, each can be:
293
- Single value
294
- Tuple/list of values
295
- Dictionary of keyword arguments
296
297
Returns:
298
Decorator function
299
"""
300
301
def parameterized.named_parameters(*testcases):
302
"""
303
Decorator to parameterize a test method with named test cases.
304
305
Args:
306
*testcases: Named test cases, each should be a tuple/list where
307
the first element is the test name and remaining elements
308
are the test parameters
309
310
Returns:
311
Decorator function
312
"""
313
314
def parameterized.product(*kwargs_seqs, **testgrid):
315
"""
316
Generate test parameters from the Cartesian product of parameter sets.
317
318
Args:
319
*kwargs_seqs: Sequences of keyword argument dictionaries
320
**testgrid: Named parameter sequences for grid generation
321
322
Returns:
323
Generator of parameter combinations
324
"""
325
```
326
327
### Parameterization Utilities
328
329
```python { .api }
330
def parameterized.CoopTestCase(other_base_class):
331
"""
332
Create a cooperative test case class that inherits from both
333
parameterized.TestCase and another base class.
334
335
Args:
336
other_base_class (type): Other base class to inherit from
337
338
Returns:
339
type: New test case class
340
"""
341
```
342
343
## Flag State Management
344
345
Utilities for saving and restoring flag states during testing.
346
347
### Flag Saver Decorators
348
349
```python { .api }
350
def flagsaver.flagsaver(func=None, **overrides):
351
"""
352
Decorator to save and restore flag values around test execution.
353
354
Can be used as @flagsaver.flagsaver or @flagsaver.flagsaver(flag_name=value)
355
356
Args:
357
func (callable): Function to wrap (when used without arguments)
358
**overrides: Flag values to override during test execution
359
360
Returns:
361
Decorator function or wrapped function
362
"""
363
364
def flagsaver.as_parsed(*args, **kwargs):
365
"""
366
Decorator to set flag values as if parsed from command line.
367
368
Args:
369
*args: Tuples of (flag_holder, value) or (flag_holder, [values])
370
**kwargs: Flag name to value mappings
371
372
Returns:
373
Decorator function
374
"""
375
```
376
377
### Flag State Management Functions
378
379
```python { .api }
380
def flagsaver.save_flag_values(flag_values, **overrides):
381
"""
382
Save current flag values and optionally override some values.
383
384
Args:
385
flag_values (FlagValues): Flag values container to save
386
**overrides: Flag values to override
387
388
Returns:
389
dict: Saved flag state
390
"""
391
392
def flagsaver.restore_flag_values(saved_flag_values, flag_values):
393
"""
394
Restore previously saved flag values.
395
396
Args:
397
saved_flag_values (dict): Previously saved flag state
398
flag_values (FlagValues): Flag values container to restore
399
"""
400
```
401
402
## Usage Examples
403
404
### Basic Test Case
405
406
```python
407
from absl.testing import absltest
408
409
class MyTest(absltest.TestCase):
410
411
def test_basic_functionality(self):
412
"""Test basic functionality."""
413
result = my_function('input')
414
self.assertEqual(result, 'expected_output')
415
416
def test_with_temp_files(self):
417
"""Test using temporary files."""
418
temp_dir = self.create_tempdir()
419
temp_file = temp_dir.create_file('test.txt', 'content')
420
421
# Use temp_file.full_path for file operations
422
result = process_file(temp_file.full_path)
423
self.assertIsNotNone(result)
424
425
if __name__ == '__main__':
426
absltest.main()
427
```
428
429
### Parameterized Testing
430
431
```python
432
from absl.testing import absltest
433
from absl.testing import parameterized
434
435
class ParameterizedTest(parameterized.TestCase):
436
437
@parameterized.parameters(
438
(1, 2, 3),
439
(4, 5, 9),
440
(10, 20, 30),
441
)
442
def test_addition(self, a, b, expected):
443
"""Test addition with multiple parameter sets."""
444
result = add(a, b)
445
self.assertEqual(result, expected)
446
447
@parameterized.named_parameters(
448
('positive_numbers', 5, 3, 8),
449
('negative_numbers', -2, -3, -5),
450
('mixed_numbers', -1, 4, 3),
451
)
452
def test_named_addition(self, a, b, expected):
453
"""Test addition with named parameter sets."""
454
result = add(a, b)
455
self.assertEqual(result, expected)
456
457
@parameterized.product(
458
x=[1, 2, 3],
459
y=[10, 20],
460
operation=['add', 'multiply']
461
)
462
def test_operations_grid(self, x, y, operation):
463
"""Test operations using parameter grid."""
464
if operation == 'add':
465
result = x + y
466
else:
467
result = x * y
468
self.assertGreater(result, 0)
469
470
if __name__ == '__main__':
471
absltest.main()
472
```
473
474
### Flag Testing
475
476
```python
477
from absl import flags
478
from absl.testing import absltest
479
from absl.testing import flagsaver
480
481
FLAGS = flags.FLAGS
482
flags.DEFINE_string('test_flag', 'default', 'Test flag.')
483
484
class FlagTest(absltest.TestCase):
485
486
@flagsaver.flagsaver
487
def test_with_flag_override(self):
488
"""Test with flag value override."""
489
FLAGS.test_flag = 'test_value'
490
result = function_that_uses_flag()
491
self.assertEqual(result, 'expected_with_test_value')
492
# Flag is automatically restored after test
493
494
@flagsaver.flagsaver(test_flag='override_value')
495
def test_with_decorator_override(self):
496
"""Test with decorator-specified flag override."""
497
result = function_that_uses_flag()
498
self.assertEqual(result, 'expected_with_override_value')
499
500
@flagsaver.as_parsed(('test_flag', 'parsed_value'))
501
def test_with_parsed_flag(self):
502
"""Test with flag set as if parsed from command line."""
503
result = function_that_uses_flag()
504
self.assertEqual(result, 'expected_with_parsed_value')
505
506
if __name__ == '__main__':
507
absltest.main()
508
```
509
510
### Temporary File Testing
511
512
```python
513
from absl.testing import absltest
514
import os
515
516
class FileTest(absltest.TestCase):
517
518
def test_file_processing(self):
519
"""Test file processing with temporary files."""
520
# Create temporary directory
521
temp_dir = self.create_tempdir()
522
523
# Create test files
524
input_file = temp_dir.create_file('input.txt', 'test content')
525
output_file_path = os.path.join(temp_dir.full_path, 'output.txt')
526
527
# Test file processing
528
process_file(input_file.full_path, output_file_path)
529
530
# Verify output
531
self.assertTrue(os.path.exists(output_file_path))
532
with open(output_file_path, 'r') as f:
533
content = f.read()
534
self.assertIn('processed', content)
535
536
if __name__ == '__main__':
537
absltest.main()
538
```
539
540
### Test Discovery and Execution
541
542
```python
543
# test_runner.py
544
from absl.testing import absltest
545
546
if __name__ == '__main__':
547
# Automatically discover and run all tests
548
absltest.main()
549
```
550
551
Command line usage:
552
```bash
553
# Run all tests
554
python test_runner.py
555
556
# Run specific test class
557
python test_runner.py MyTest
558
559
# Run specific test method
560
python test_runner.py MyTest.test_method
561
562
# Run with verbose output
563
python test_runner.py --verbose
564
565
# Run with specific flags
566
python test_runner.py --test_flag=value
567
```