or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

content.mdhelpers.mdindex.mdmatchers.mdtest-cases.mdtest-execution.mdtest-results.mdtwisted-support.md

helpers.mddocs/

0

# Helper Utilities

1

2

Utility functions for common testing tasks including safe imports, dictionary manipulation, monkey patching, and test organization helpers.

3

4

## Capabilities

5

6

### Safe Import Utilities

7

8

Functions for safely importing modules with fallback handling.

9

10

```python { .api }

11

def try_import(name, alternative=None, error_callback=None):

12

"""

13

Attempt to import a module, with a fallback.

14

15

Safely attempts to import a module or attribute, returning

16

an alternative value if the import fails. Useful for optional

17

dependencies and cross-version compatibility.

18

19

Args:

20

name (str): The name of the object to import (e.g., 'os.path.join')

21

alternative: The value to return if import fails (default: None)

22

error_callback (callable): Function called with ImportError if import fails

23

24

Returns:

25

The imported object or the alternative value

26

27

Example:

28

# Try to import optional dependency

29

numpy = try_import('numpy', alternative=None)

30

if numpy is not None:

31

# Use numpy functionality

32

pass

33

34

# Import specific function with fallback

35

json_loads = try_import('orjson.loads', json.loads)

36

"""

37

```

38

39

### Dictionary Manipulation

40

41

Utility functions for common dictionary operations in testing contexts.

42

43

```python { .api }

44

def map_values(function, dictionary):

45

"""

46

Map function across the values of a dictionary.

47

48

Applies a function to each value in a dictionary,

49

preserving the key-value structure.

50

51

Args:

52

function (callable): Function to apply to each value

53

dictionary (dict): Dictionary to transform

54

55

Returns:

56

dict: New dictionary with transformed values

57

58

Example:

59

data = {'a': 1, 'b': 2, 'c': 3}

60

doubled = map_values(lambda x: x * 2, data)

61

# {'a': 2, 'b': 4, 'c': 6}

62

"""

63

64

def filter_values(function, dictionary):

65

"""

66

Filter dictionary by its values using a predicate function.

67

68

Returns a new dictionary containing only the key-value pairs

69

where the value satisfies the predicate function.

70

71

Args:

72

function (callable): Predicate function returning bool

73

dictionary (dict): Dictionary to filter

74

75

Returns:

76

dict: Filtered dictionary

77

78

Example:

79

data = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

80

evens = filter_values(lambda x: x % 2 == 0, data)

81

# {'b': 2, 'd': 4}

82

"""

83

84

def dict_subtract(a, b):

85

"""

86

Return the part of dictionary a that's not in dictionary b.

87

88

Creates a new dictionary containing key-value pairs from 'a'

89

whose keys are not present in 'b'.

90

91

Args:

92

a (dict): Source dictionary

93

b (dict): Dictionary whose keys to exclude

94

95

Returns:

96

dict: Dictionary with keys from a not in b

97

98

Example:

99

a = {'x': 1, 'y': 2, 'z': 3}

100

b = {'y': 5, 'w': 6}

101

result = dict_subtract(a, b)

102

# {'x': 1, 'z': 3}

103

"""

104

105

def list_subtract(a, b):

106

"""

107

Return a list with elements of a not in b.

108

109

Creates a new list containing elements from 'a' that are

110

not present in 'b'. If an element appears multiple times

111

in 'a' and once in 'b', it will appear n-1 times in result.

112

113

Args:

114

a (list): Source list

115

b (list): List of elements to remove

116

117

Returns:

118

list: List with elements from a not in b

119

120

Example:

121

a = [1, 2, 3, 2, 4]

122

b = [2, 5]

123

result = list_subtract(a, b)

124

# [1, 3, 2, 4] (one instance of 2 removed)

125

"""

126

```

127

128

### Test Organization Utilities

129

130

Functions for working with test cases and test organization.

131

132

```python { .api }

133

def clone_test_with_new_id(test, new_id):

134

"""

135

Clone a test with a new test ID.

136

137

Creates a copy of a test case with a different identifier,

138

useful for parameterized testing or test case variations.

139

140

Args:

141

test: Original TestCase instance

142

new_id (str): New test identifier

143

144

Returns:

145

TestCase: Cloned test with new ID

146

147

Example:

148

original_test = MyTest('test_function')

149

variant_test = clone_test_with_new_id(original_test, 'test_function_variant')

150

"""

151

152

def iterate_tests(test_suite_or_case):

153

"""

154

Iterate through all individual tests in a test suite.

155

156

Recursively flattens test suites to yield individual

157

test cases, useful for test discovery and analysis.

158

159

Args:

160

test_suite_or_case: TestSuite or TestCase to iterate

161

162

Yields:

163

TestCase: Individual test cases

164

165

Example:

166

suite = TestSuite()

167

# ... add tests to suite ...

168

for test in iterate_tests(suite):

169

print(f"Found test: {test.id()}")

170

"""

171

172

def unique_text_generator():

173

"""

174

Generate unique text strings for test isolation.

175

176

Creates a generator that yields unique text strings,

177

useful for generating unique identifiers in tests.

178

179

Yields:

180

str: Unique text strings

181

182

Example:

183

generator = unique_text_generator()

184

unique1 = next(generator) # "0"

185

unique2 = next(generator) # "1"

186

unique3 = next(generator) # "2"

187

"""

188

```

189

190

### Monkey Patching

191

192

Classes and functions for runtime patching during tests.

193

194

```python { .api }

195

class MonkeyPatcher:

196

"""

197

Apply and remove multiple patches as a coordinated group.

198

199

Manages multiple monkey patches with automatic cleanup,

200

ensuring all patches are properly restored after use.

201

"""

202

203

def __init__(self):

204

"""Create a new MonkeyPatcher instance."""

205

206

def add_patch(self, obj, attribute, new_value):

207

"""

208

Add a patch to be applied.

209

210

Args:

211

obj: Object to patch

212

attribute (str): Attribute name to patch

213

new_value: New value for the attribute

214

"""

215

216

def patch(self):

217

"""

218

Apply all registered patches.

219

220

Applies all patches that have been registered with add_patch().

221

Should be called before the code that needs the patches.

222

"""

223

224

def restore(self):

225

"""

226

Restore all patched attributes to their original values.

227

228

Undoes all patches applied by patch(), restoring the

229

original attribute values.

230

"""

231

232

def __enter__(self):

233

"""

234

Context manager entry - applies patches.

235

236

Returns:

237

MonkeyPatcher: Self for context manager protocol

238

"""

239

self.patch()

240

return self

241

242

def __exit__(self, exc_type, exc_val, exc_tb):

243

"""

244

Context manager exit - restores patches.

245

246

Args:

247

exc_type: Exception type (if any)

248

exc_val: Exception value (if any)

249

exc_tb: Exception traceback (if any)

250

"""

251

self.restore()

252

253

def patch(obj, attribute, new_value):

254

"""

255

Apply a simple monkey patch.

256

257

Convenience function for applying a single patch.

258

For multiple patches, use MonkeyPatcher class.

259

260

Args:

261

obj: Object to patch

262

attribute (str): Attribute name to patch

263

new_value: New value for the attribute

264

265

Returns:

266

The original value that was replaced

267

268

Example:

269

# Patch a function temporarily

270

original = patch(os, 'getcwd', lambda: '/fake/path')

271

try:

272

# Code that uses os.getcwd()

273

pass

274

finally:

275

# Restore original

276

setattr(os, 'getcwd', original)

277

"""

278

```

279

280

### Standalone Assertions

281

282

Assertion functions that can be used outside of TestCase.

283

284

```python { .api }

285

def assert_that(matchee, matcher, message='', verbose=False):

286

"""

287

Assert that matchee matches the given matcher.

288

289

Standalone assertion function using testtools matchers,

290

can be used outside of TestCase instances for utility

291

functions and general-purpose assertions.

292

293

Args:

294

matchee: Object to be matched

295

matcher: Matcher instance to apply

296

message (str): Optional failure message

297

verbose (bool): Include detailed mismatch information

298

299

Raises:

300

MismatchError: If matcher does not match matchee

301

302

Example:

303

from testtools.assertions import assert_that

304

from testtools.matchers import Equals, GreaterThan

305

306

# Use in utility functions

307

def validate_config(config):

308

assert_that(config['timeout'], GreaterThan(0))

309

assert_that(config['host'], Contains('.'))

310

"""

311

```

312

313

### Compatibility Utilities

314

315

Functions for cross-Python version compatibility.

316

317

```python { .api }

318

def reraise(exc_type, exc_value, traceback):

319

"""

320

Re-raise an exception with its original traceback.

321

322

Properly re-raises exceptions preserving the original

323

traceback information across Python versions.

324

325

Args:

326

exc_type: Exception type

327

exc_value: Exception instance

328

traceback: Exception traceback

329

"""

330

331

def text_repr(obj):

332

"""

333

Get text representation with proper escaping.

334

335

Returns a properly escaped text representation of an object,

336

handling unicode and special characters correctly.

337

338

Args:

339

obj: Object to represent

340

341

Returns:

342

str: Escaped text representation

343

"""

344

345

def unicode_output_stream(stream):

346

"""

347

Get unicode-capable output stream.

348

349

Wraps a stream to ensure it can handle unicode output

350

properly across different Python versions and platforms.

351

352

Args:

353

stream: Original output stream

354

355

Returns:

356

Stream: Unicode-capable stream wrapper

357

"""

358

```

359

360

## Usage Examples

361

362

### Safe Import Patterns

363

364

```python

365

import testtools

366

from testtools.helpers import try_import

367

368

class MyTest(testtools.TestCase):

369

370

def setUp(self):

371

super().setUp()

372

373

# Try to import optional dependencies

374

self.numpy = try_import('numpy')

375

self.pandas = try_import('pandas')

376

self.requests = try_import('requests', error_callback=self._log_import_error)

377

378

def _log_import_error(self, error):

379

"""Log import errors for debugging."""

380

self.addDetail('import_error',

381

testtools.content.text_content(str(error)))

382

383

def test_with_optional_numpy(self):

384

if self.numpy is None:

385

self.skip("NumPy not available")

386

387

# Use numpy functionality

388

arr = self.numpy.array([1, 2, 3])

389

self.assertEqual(len(arr), 3)

390

391

def test_with_fallback_json(self):

392

# Use fast JSON library if available, fall back to standard

393

json_loads = try_import('orjson.loads', alternative=json.loads)

394

json_dumps = try_import('orjson.dumps', alternative=json.dumps)

395

396

data = {'test': True, 'value': 42}

397

serialized = json_dumps(data)

398

deserialized = json_loads(serialized)

399

400

self.assertEqual(deserialized, data)

401

```

402

403

### Dictionary Operations

404

405

```python

406

import testtools

407

from testtools.helpers import map_values, filter_values, dict_subtract

408

409

class DataProcessingTest(testtools.TestCase):

410

411

def test_data_transformation(self):

412

# Original data

413

raw_data = {

414

'temperature': 20,

415

'humidity': 65,

416

'pressure': 1013,

417

'wind_speed': 15

418

}

419

420

# Transform all values (Celsius to Fahrenheit)

421

fahrenheit_data = map_values(lambda c: c * 9/5 + 32,

422

{'temperature': raw_data['temperature']})

423

self.assertEqual(fahrenheit_data['temperature'], 68.0)

424

425

# Filter for specific conditions

426

high_values = filter_values(lambda x: x > 50, raw_data)

427

self.assertIn('humidity', high_values)

428

self.assertIn('pressure', high_values)

429

self.assertNotIn('temperature', high_values)

430

431

def test_configuration_merge(self):

432

# Base configuration

433

base_config = {

434

'host': 'localhost',

435

'port': 8080,

436

'debug': False,

437

'timeout': 30

438

}

439

440

# Environment-specific overrides

441

production_overrides = {

442

'host': 'prod.example.com',

443

'debug': False,

444

'ssl': True

445

}

446

447

# Get settings that are only in base config

448

base_only = dict_subtract(base_config, production_overrides)

449

self.assertEqual(base_only, {'port': 8080, 'timeout': 30})

450

451

# Merge configurations

452

final_config = {**base_config, **production_overrides}

453

self.assertEqual(final_config['host'], 'prod.example.com')

454

self.assertTrue(final_config['ssl'])

455

```

456

457

### Monkey Patching in Tests

458

459

```python

460

import testtools

461

from testtools.helpers import MonkeyPatcher

462

import os

463

import time

464

465

class MonkeyPatchTest(testtools.TestCase):

466

467

def test_with_context_manager(self):

468

# Use MonkeyPatcher as context manager

469

with MonkeyPatcher() as patcher:

470

patcher.add_patch(os, 'getcwd', lambda: '/fake/working/dir')

471

patcher.add_patch(time, 'time', lambda: 1234567890.0)

472

473

# Test code that uses patched functions

474

current_dir = os.getcwd()

475

current_time = time.time()

476

477

self.assertEqual(current_dir, '/fake/working/dir')

478

self.assertEqual(current_time, 1234567890.0)

479

480

# Outside context, original functions are restored

481

self.assertNotEqual(os.getcwd(), '/fake/working/dir')

482

483

def test_manual_patching(self):

484

patcher = MonkeyPatcher()

485

486

# Mock file system operations

487

patcher.add_patch(os.path, 'exists', lambda path: path == '/fake/file')

488

patcher.add_patch(os.path, 'isfile', lambda path: path.endswith('.txt'))

489

490

try:

491

patcher.patch()

492

493

# Test with mocked file system

494

self.assertTrue(os.path.exists('/fake/file'))

495

self.assertFalse(os.path.exists('/other/file'))

496

self.assertTrue(os.path.isfile('document.txt'))

497

self.assertFalse(os.path.isfile('directory'))

498

499

finally:

500

patcher.restore()

501

502

def test_method_patching(self):

503

# Patch methods on test objects

504

class MockService:

505

def get_data(self):

506

return "original data"

507

508

service = MockService()

509

patcher = MonkeyPatcher()

510

patcher.add_patch(service, 'get_data', lambda: "mocked data")

511

512

with patcher:

513

result = service.get_data()

514

self.assertEqual(result, "mocked data")

515

516

# Method restored after context

517

result = service.get_data()

518

self.assertEqual(result, "original data")

519

```

520

521

### Test Organization and Discovery

522

523

```python

524

import testtools

525

from testtools.helpers import iterate_tests, clone_test_with_new_id

526

527

class TestOrganizationExample(testtools.TestCase):

528

529

def test_suite_analysis(self):

530

# Create a test suite

531

suite = testtools.TestSuite()

532

suite.addTest(TestOrganizationExample('test_method_1'))

533

suite.addTest(TestOrganizationExample('test_method_2'))

534

535

# Analyze all tests in suite

536

test_count = 0

537

test_names = []

538

539

for test in iterate_tests(suite):

540

test_count += 1

541

test_names.append(test.id())

542

543

self.assertEqual(test_count, 2)

544

self.assertIn('TestOrganizationExample.test_method_1', test_names)

545

546

def test_method_1(self):

547

self.assertTrue(True)

548

549

def test_method_2(self):

550

self.assertTrue(True)

551

552

def create_parameterized_tests():

553

"""Create multiple test variants from a base test."""

554

base_test = TestOrganizationExample('test_method_1')

555

556

# Create variants with different parameters

557

variants = []

558

for i, param in enumerate(['param1', 'param2', 'param3']):

559

variant = clone_test_with_new_id(base_test, f'test_method_1_variant_{i}')

560

variant.test_param = param # Add parameter to test

561

variants.append(variant)

562

563

return variants

564

565

class TestWithParameters(testtools.TestCase):

566

567

def test_parameter_injection(self):

568

# Use parameter if available

569

param = getattr(self, 'test_param', 'default')

570

self.assertIsNotNone(param)

571

572

# Test behavior varies by parameter

573

if param == 'param1':

574

self.assertEqual(param, 'param1')

575

else:

576

self.assertNotEqual(param, 'param1')

577

```

578

579

### Standalone Assertions

580

581

```python

582

from testtools.assertions import assert_that

583

from testtools.matchers import Equals, GreaterThan, Contains, MatchesDict

584

585

def validate_user_data(user_data):

586

"""Validate user data using testtools assertions."""

587

# Use standalone assertions for validation

588

assert_that(user_data, MatchesDict({

589

'id': GreaterThan(0),

590

'name': Contains('@'), # Assuming email format

591

'age': GreaterThan(0)

592

}))

593

594

def process_configuration(config_file):

595

"""Process configuration with validation."""

596

config = load_config(config_file)

597

598

# Validate required fields

599

assert_that(config.get('database_url'), Contains('://'))

600

assert_that(config.get('port'), GreaterThan(1000))

601

assert_that(config.get('debug'), Equals(False))

602

603

return config

604

605

# Usage in non-test code

606

try:

607

user = {'id': 123, 'name': 'user@example.com', 'age': 25}

608

validate_user_data(user)

609

print("User data is valid")

610

except MismatchError as e:

611

print(f"Validation failed: {e}")

612

```

613

614

### Utility Integration

615

616

```python

617

import testtools

618

from testtools.helpers import *

619

from testtools.matchers import *

620

621

class IntegratedUtilityTest(testtools.TestCase):

622

623

def setUp(self):

624

super().setUp()

625

626

# Set up test environment with utilities

627

self.patcher = MonkeyPatcher()

628

self.unique_id_gen = unique_text_generator()

629

630

# Mock external dependencies

631

self.requests = try_import('requests')

632

if self.requests:

633

self.patcher.add_patch(self.requests, 'get', self._mock_http_get)

634

self.patcher.patch()

635

636

def tearDown(self):

637

if hasattr(self, 'patcher'):

638

self.patcher.restore()

639

super().tearDown()

640

641

def _mock_http_get(self, url):

642

"""Mock HTTP GET responses."""

643

class MockResponse:

644

def __init__(self, url):

645

self.url = url

646

self.status_code = 200

647

self.text = f"Response from {url}"

648

649

def json(self):

650

return {"url": self.url, "status": "ok"}

651

652

return MockResponse(url)

653

654

def test_integrated_workflow(self):

655

# Generate unique test data

656

unique_name = next(self.unique_id_gen)

657

test_data = {

658

'name': f'test_user_{unique_name}',

659

'email': f'user_{unique_name}@example.com'

660

}

661

662

# Process data with filtering

663

valid_data = filter_values(lambda v: '@' in v, test_data)

664

self.assertIn('email', valid_data)

665

666

# Transform data

667

formatted_data = map_values(str.upper,

668

{'name': test_data['name']})

669

670

# Make HTTP request (mocked)

671

if self.requests:

672

response = self.requests.get('http://api.example.com/users')

673

self.assertEqual(response.status_code, 200)

674

675

# Validate response with standalone assertion

676

assert_that(response.json(), MatchesDict({

677

'url': Contains('api.example.com'),

678

'status': Equals('ok')

679

}))

680

```