Skip to content

Commit 4fcb7ca

Browse files
authored
test: boost unit test coverage (batch 2) (#6)
* test: Deepen unit tests for Observer and Strategy patterns * test: Enhance Builder, Factory, Adapter, and Facade unit tests
1 parent 4f8ca8c commit 4fcb7ca

File tree

6 files changed

+433
-712
lines changed

6 files changed

+433
-712
lines changed

Tests/DesignAlgorithmsKitTests/Behavioral/ObserverTests.swift

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
//
2-
// ObserverTests.swift
3-
// DesignAlgorithmsKitTests
4-
//
5-
// Unit tests for Observer Pattern
6-
//
7-
81
import XCTest
92
@testable import DesignAlgorithmsKit
103

114
final class ObserverTests: XCTestCase {
5+
6+
// MARK: - Basic Functionality
7+
128
func testObserverPattern() {
139
// Given
1410
class TestObservable: BaseObservable {}
@@ -66,9 +62,7 @@ final class ObserverTests: XCTestCase {
6662
let id: String
6763
var receivedEvents: [Any] = []
6864

69-
init(id: String) {
70-
self.id = id
71-
}
65+
init(id: String) { self.id = id }
7266

7367
func didReceiveNotification(from observable: any Observable, event: Any) {
7468
receivedEvents.append(event)
@@ -88,5 +82,122 @@ final class ObserverTests: XCTestCase {
8882
XCTAssertEqual(observer1.receivedEvents.count, 1)
8983
XCTAssertEqual(observer2.receivedEvents.count, 1)
9084
}
85+
86+
// MARK: - Weak Reference Tests
87+
88+
func testWeakReference() {
89+
class TestObservable: BaseObservable {}
90+
class TestObserver: Observer {
91+
func didReceiveNotification(from observable: any Observable, event: Any) {}
92+
}
93+
94+
let observable = TestObservable()
95+
96+
// Create an observer in a local scope so it gets deallocated
97+
var observer: TestObserver? = TestObserver()
98+
weak var weakObserver = observer
99+
100+
observable.addObserver(observer!)
101+
XCTAssertNotNil(weakObserver)
102+
103+
// Remove strong reference
104+
observer = nil
105+
106+
// Verify deallocation
107+
XCTAssertNil(weakObserver, "Observer should have been deallocated")
108+
109+
// Verify notification doesn't crash
110+
observable.notifyObservers(event: "test")
111+
112+
// Verify cleanup on next add
113+
// The implementation cleans up on addObserver
114+
observable.addObserver(TestObserver())
115+
// Cannot easily inspect internal array count without reflection or subclassing exposing it,
116+
// but 'addObserver' is the trigger for cleanup in BaseObservable.
117+
}
118+
119+
// MARK: - Reentrancy and Concurrency
120+
121+
func testReentrancySafe() {
122+
// Test removing self during notification
123+
class TestObservable: BaseObservable {}
124+
class TestObserver: Observer {
125+
var observable: TestObservable?
126+
var receivedCount = 0
127+
128+
func didReceiveNotification(from observable: any Observable, event: Any) {
129+
receivedCount += 1
130+
if let obs = self.observable {
131+
obs.removeObserver(self)
132+
}
133+
}
134+
}
135+
136+
let observable = TestObservable()
137+
let observer = TestObserver()
138+
observer.observable = observable
139+
140+
observable.addObserver(observer)
141+
142+
// First notification triggers removal
143+
observable.notifyObservers(event: "1")
144+
XCTAssertEqual(observer.receivedCount, 1)
145+
146+
// Second notification should not reach observer
147+
observable.notifyObservers(event: "2")
148+
XCTAssertEqual(observer.receivedCount, 1)
149+
}
150+
151+
func testConcurrentAccess() {
152+
class TestObservable: BaseObservable {}
153+
class TestObserver: Observer {
154+
func didReceiveNotification(from observable: any Observable, event: Any) {
155+
// Do work
156+
}
157+
}
158+
159+
let observable = TestObservable()
160+
let iterations = 1000
161+
let expectation = self.expectation(description: "Concurrent observer access")
162+
expectation.expectedFulfillmentCount = iterations
163+
164+
DispatchQueue.concurrentPerform(iterations: iterations) { i in
165+
let observer = TestObserver()
166+
observable.addObserver(observer)
167+
168+
if i % 2 == 0 {
169+
observable.notifyObservers(event: i)
170+
}
171+
172+
if i % 3 == 0 {
173+
observable.removeObserver(observer)
174+
}
175+
176+
expectation.fulfill()
177+
}
178+
179+
waitForExpectations(timeout: 5.0)
180+
}
181+
182+
func testDuplicateAddition() {
183+
class TestObservable: BaseObservable {}
184+
class TestObserver: Observer {
185+
var count = 0
186+
func didReceiveNotification(from observable: any Observable, event: Any) {
187+
count += 1
188+
}
189+
}
190+
191+
let observable = TestObservable()
192+
let observer = TestObserver()
193+
194+
// Add twice
195+
observable.addObserver(observer)
196+
observable.addObserver(observer)
197+
198+
observable.notifyObservers(event: "test")
199+
200+
// Should only be notified once
201+
XCTAssertEqual(observer.count, 1)
202+
}
91203
}
92-
Lines changed: 56 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,20 @@
1-
//
2-
// StrategyTests.swift
3-
// DesignAlgorithmsKitTests
4-
//
5-
// Unit tests for Strategy Pattern
6-
//
7-
81
import XCTest
92
@testable import DesignAlgorithmsKit
103

114
final class StrategyTests: XCTestCase {
5+
6+
// MARK: - Basic Implementation
7+
128
func testStrategyPattern() {
139
// Given
1410
struct AdditionStrategy: Strategy {
1511
let strategyID = "addition"
16-
17-
func execute(_ a: Int, _ b: Int) -> Int {
18-
return a + b
19-
}
12+
func execute(_ a: Int, _ b: Int) -> Int { return a + b }
2013
}
2114

2215
struct MultiplicationStrategy: Strategy {
2316
let strategyID = "multiplication"
24-
25-
func execute(_ a: Int, _ b: Int) -> Int {
26-
return a * b
27-
}
17+
func execute(_ a: Int, _ b: Int) -> Int { return a * b }
2818
}
2919

3020
// When
@@ -39,92 +29,84 @@ final class StrategyTests: XCTestCase {
3929
XCTAssertEqual(result2, 15)
4030
}
4131

42-
func testStrategyContext() {
43-
// Given
44-
struct TestStrategy: Strategy {
45-
let strategyID = "test"
46-
}
47-
48-
// When
49-
let context = StrategyContext(strategy: TestStrategy())
50-
51-
// Then
52-
XCTAssertEqual(context.getStrategy().strategyID, "test")
53-
}
54-
55-
func testStrategyContextSetStrategy() {
32+
func testContextSwitching() {
5633
// Given
5734
struct TestStrategy: Strategy {
5835
let strategyID: String
36+
let val: Int
5937
}
6038

61-
// When
62-
let context = StrategyContext(strategy: TestStrategy(strategyID: "strategy1"))
63-
XCTAssertEqual(context.getStrategy().strategyID, "strategy1")
39+
let s1 = TestStrategy(strategyID: "s1", val: 1)
40+
let s2 = TestStrategy(strategyID: "s2", val: 2)
6441

65-
context.setStrategy(TestStrategy(strategyID: "strategy2"))
42+
let context = StrategyContext(strategy: s1)
43+
XCTAssertEqual(context.getStrategy().val, 1)
6644

67-
// Then
68-
XCTAssertEqual(context.getStrategy().strategyID, "strategy2")
45+
context.setStrategy(s2)
46+
XCTAssertEqual(context.getStrategy().val, 2)
47+
48+
context.setStrategy(s1)
49+
XCTAssertEqual(context.getStrategy().val, 1)
6950
}
7051

52+
// MARK: - Inheritance and Types
53+
7154
func testBaseStrategy() {
72-
// Given
7355
let baseStrategy = BaseStrategy(strategyID: "base-test")
74-
75-
// Then
7656
XCTAssertEqual(baseStrategy.strategyID, "base-test")
7757
}
7858

7959
func testBaseStrategyInheritance() {
80-
// Given
8160
class CustomStrategy: BaseStrategy {
82-
init() {
83-
super.init(strategyID: "custom")
84-
}
61+
init() { super.init(strategyID: "custom") }
62+
func op() -> String { return "op" }
8563
}
8664

87-
// When
8865
let strategy = CustomStrategy()
89-
90-
// Then
9166
XCTAssertEqual(strategy.strategyID, "custom")
67+
XCTAssertEqual(strategy.op(), "op")
9268
}
9369

94-
func testStrategyContextGetStrategy() {
95-
// Given
96-
struct TestStrategy: Strategy {
97-
let strategyID = "test"
98-
}
99-
100-
let context = StrategyContext(strategy: TestStrategy())
70+
// MARK: - Polymorphism
71+
72+
protocol MathStrategy: Strategy {
73+
func calculate(_ a: Int, _ b: Int) -> Int
74+
}
75+
76+
struct Add: MathStrategy {
77+
var strategyID = "add"
78+
func calculate(_ a: Int, _ b: Int) -> Int { a + b }
79+
}
80+
81+
struct Subtract: MathStrategy {
82+
var strategyID = "subtract"
83+
func calculate(_ a: Int, _ b: Int) -> Int { a - b }
84+
}
85+
86+
func testPolymorphicContext() {
87+
let context = StrategyContext<BoxedMathStrategy>(strategy: BoxedMathStrategy(Add()))
10188

102-
// When
103-
let retrievedStrategy = context.getStrategy()
89+
XCTAssertEqual(context.getStrategy().calculate(10, 5), 15)
10490

105-
// Then
106-
XCTAssertEqual(retrievedStrategy.strategyID, "test")
91+
context.setStrategy(BoxedMathStrategy(Subtract()))
92+
XCTAssertEqual(context.getStrategy().calculate(10, 5), 5)
10793
}
10894

109-
func testStrategyContextMultipleStrategies() {
110-
// Given
111-
struct TestStrategy: Strategy {
112-
let strategyID: String
95+
// Type erasure wrapper for the test because StrategyContext is generic over a specific concrete type
96+
// or a protocol if used as an existentials (but StrategyContext<P> requires P: Strategy).
97+
// Protocols conforming to protocols don't satisfy P: Strategy for generic constraints in Swift
98+
// without `any` or box, testing here verifies the usage pattern.
99+
struct BoxedMathStrategy: MathStrategy {
100+
let strategyID: String
101+
private let _calculate: (Int, Int) -> Int
102+
103+
init<S: MathStrategy>(_ strategy: S) {
104+
self.strategyID = strategy.strategyID
105+
self._calculate = strategy.calculate
113106
}
114107

115-
// When
116-
let context = StrategyContext(strategy: TestStrategy(strategyID: "strategy1"))
117-
context.setStrategy(TestStrategy(strategyID: "strategy2"))
118-
119-
// Then
120-
XCTAssertEqual(context.getStrategy().strategyID, "strategy2")
121-
}
122-
}
123-
124-
// Extension to make strategies executable for testing
125-
extension Strategy {
126-
func execute(_ a: Int, _ b: Int) -> Int {
127-
return 0 // Default implementation
108+
func calculate(_ a: Int, _ b: Int) -> Int {
109+
_calculate(a, b)
110+
}
128111
}
129112
}
130-

0 commit comments

Comments
 (0)