PCPP2 study materials
PCPP2 study resources

PCPP2 common mistakes

PCPP2 · Understand the traps and how to avoid them.

Mistake 1

Writing test methods that call assertEqual but naming the class without inheriting from unittest.TestCase, then wondering why pytest collects nothing or why setUp never runs.

Candidates mix pytest-style plain classes with unittest idioms. pytest only collects unittest fixtures and assertion helpers when TestCase is in the MRO; a plain class named Test* is collected as a pytest class but setUp/tearDown are ignored and self.assertEqual is undefined.

What to do instead

Decide the framework per file. For unittest, subclass unittest.TestCase and use setUp/tearDown with self.assert* methods. For pytest, use plain functions or plain classes with fixtures injected as parameters and bare assert statements. Verify collection with pytest --collect-only before debugging failures.

Mistake 2

Using mutable class-level or module-level state in tests (a shared list, a cached connection, a singleton instance) so tests pass alone but fail in a suite or in random order.

Test isolation is rarely exercised during development because tests are run one at a time. Order dependence and leaked state only surface in full-suite runs, and candidates then blame the framework instead of the shared fixture.

What to do instead

Create fresh state per test via setUp or a function-scoped fixture, use tmp_path for files, and reset singletons or module globals in teardown. Run pytest with -p no:randomly removed or with a random-order plugin to prove independence, and treat any order-dependent test as a bug.

Mistake 3

Mocking the wrong seam: patching a name in the module where it is defined rather than where it is looked up, or asserting on call counts without checking arguments.

Candidates learn mock.patch syntax but not Python's late-binding lookup. Patching original_module.func has no effect when the code under test did from original_module import func, because the reference was bound at import time.

What to do instead

Patch the attribute on the module that the code under test imports it into (patch('app.services.requests')). Use autospec=True so signature mismatches fail, and assert with assert_called_once_with(...) rather than assert_called_once() so wrong arguments are caught.

Mistake 4

Treating a Singleton as a global variable: implementing __new__ without guarding __init__, or relying on module-level instantiation that breaks under tests and multiprocessing.

The pattern is memorized as 'one instance' but candidates miss re-initialization on every call, thread safety, and the fact that forked or spawned processes get separate copies, so state silently diverges.

What to do instead

Guard initialization with a flag or use a metaclass/class attribute holding the instance, add a lock for thread safety, and expose a reset method for tests. Document that the singleton is per-process, and pass explicit state to child processes instead of assuming shared memory.

Mistake 5

Choosing Factory, Facade, or Proxy interchangeably: writing a class that both creates objects and wraps them, or calling a wrapper a Factory when it returns the same instance.

The patterns look similar in code shape. Candidates match on surface structure (a class with a method returning an object) instead of intent: object creation vs. simplified interface vs. controlled access.

What to do instead

State the intent first, then pick. Factory: caller should not know the concrete class. Facade: collapse several subsystems behind one simple API with no added control. Proxy: same interface as the wrapped object but adds access control, lazy loading, or logging. If the wrapper changes the interface, it is an Adapter, not a Proxy.

Mistake 6

Implementing Observer with strong references and no unsubscribe path, then hitting memory leaks or callbacks firing on dead objects; or notifying observers while iterating the list that a callback mutates.

Candidates focus on the notify loop and skip lifecycle. Mutation during iteration raises RuntimeError or skips observers, and strong references keep observers alive forever.

What to do instead

Store observers in a collection, iterate over a copy (for obs in list(self._observers)), provide attach/detach methods, and use weak references (weakref.ref or WeakSet) for long-lived subjects. Detach in teardown of tests and assert that a detached observer receives no further events.

Mistake 7

Assuming multiprocessing shares memory like threading: mutating a list or dict in a child process and expecting the parent to see it, or relying on a global counter without a Lock.

Threads share an address space, processes do not. Candidates carry threading intuition into multiprocessing and are surprised when changes vanish after join(), and unsynchronized increments lose updates even with shared Value.

What to do instead

Use multiprocessing.Queue, Pipe, Manager, or Value/Array for cross-process data, always wrap read-modify-write on shared values in a Lock, and call join() plus close()/join_thread() on queues. Remember spawn platforms (Windows, macOS default) re-import the module, so guard entry points with if name == 'main'.

Mistake 8

Managing subprocesses by string concatenation with shell=True, or reading stdout before the process finishes and deadlocking on a full pipe buffer.

Shell syntax feels convenient and candidates do not consider that large output fills the OS pipe buffer, blocking the child until the parent reads, while the parent is blocked in wait().

What to do instead

Pass an argument list with shell=False, use subprocess.run(capture_output=True, text=True, timeout=...) for simple cases, and for streaming use Popen with communicate() rather than manual read/wait ordering. Always set a timeout and check returncode instead of assuming success.

Mistake 9

Writing socket code that ignores partial sends and assumes recv returns the whole message, then hanging or corrupting data on larger payloads.

Small test messages fit in one segment, so the bug is invisible locally. TCP is a byte stream: send may send fewer bytes and recv returns whatever is available, not a message boundary.

What to do instead

Use sendall for sending, loop on recv until the expected byte count or terminator arrives, and define an application framing protocol (length prefix or delimiter). Set timeouts with settimeout, close sockets with context managers, and handle ConnectionResetError explicitly.

Mistake 10

Building SQL by string formatting user input and managing cursors/connections without context managers or parameterization, causing injection risk and leaked connections.

String interpolation is the shortest code path and works in demos. Candidates also forget commit/rollback semantics and that an unclosed cursor holds resources and can leave transactions open.

What to do instead

Always use parameterized queries (cursor.execute('SELECT * FROM t WHERE id = %s', (id,)) or the driver's qmark/named style), never f-strings for values. Use with connection: for transaction scope and with closing(conn.cursor()) as cur: for cursors, and commit explicitly where the driver requires it. For ORMs, prefer session context managers and verify generated SQL with echo=True.