absl::Time mocking — 테스트 친화 시간
#absl::Now()를 직접 부르면 안 된다
absl::Now()를 라이브러리 코드에서 직접 부르면 테스트 가능성이 죽는다. 시간 의존 코드는 외부에서 주입한 시계를 통해 시간을 얻어야 한다.
// 회피 — 테스트 불가class Cache {public: void Put(const Key& k, Value v) { entries_[k] = {std::move(v), absl::Now() + ttl_}; // ❌ }};Put이 호출된 시각을 테스트가 통제할 수 없다. expiry 로직을 검증하려면 sleep을 써야 하고, 그러면 테스트가 느려지고 flaky해진다.
#Clock 추상
가장 단순한 해법은 시계 함수 객체를 받는 것이다.
// Good — clock 주입class Cache {public: using Clock = std::function<absl::Time()>;
explicit Cache(absl::Duration ttl, Clock clock = absl::Now) : ttl_(ttl), clock_(std::move(clock)) {}
void Put(const Key& k, Value v) { entries_[k] = {std::move(v), clock_() + ttl_}; }
absl::optional<Value> Get(const Key& k) { auto it = entries_.find(k); if (it == entries_.end()) return absl::nullopt; if (it->second.expiry < clock_()) { entries_.erase(it); return absl::nullopt; } return it->second.value; }
private: absl::Duration ttl_; Clock clock_; struct Entry { Value value; absl::Time expiry; }; absl::flat_hash_map<Key, Entry> entries_;};기본값이 absl::Now라 production 코드는 변화 없다. 테스트는 시계를 교체한다.
TEST(CacheTest, EntryExpiresAfterTtl) { absl::Time now = absl::FromUnixSeconds(1'000'000); Cache cache(absl::Seconds(10), [&now]() { return now; });
cache.Put("k", "v"); EXPECT_THAT(cache.Get("k"), Optional(std::string("v")));
now += absl::Seconds(15); EXPECT_EQ(cache.Get("k"), absl::nullopt);}#SimulatedClock 헬퍼
매번 [&now](){ return now; }를 쓰면 번거롭다. 헬퍼 클래스를 두면 깔끔하다.
class SimulatedClock {public: explicit SimulatedClock(absl::Time start = absl::UnixEpoch()) : now_(start) {}
absl::Time Now() const { return now_; } void Advance(absl::Duration d) { now_ += d; } void SetTime(absl::Time t) { now_ = t; }
// std::function<absl::Time()>로 자동 변환 operator std::function<absl::Time()>() const { return [this]() { return now_; }; }
private: absl::Time now_;};
TEST(CacheTest, EntryExpires) { SimulatedClock clock(absl::FromUnixSeconds(1'000'000)); Cache cache(absl::Seconds(10), [&]() { return clock.Now(); });
cache.Put("k", "v"); clock.Advance(absl::Seconds(15)); EXPECT_EQ(cache.Get("k"), absl::nullopt);}GoogleTest와 결합 시 fixture에 SimulatedClock을 두면 여러 테스트에서 재사용한다.
#sleep도 대체 가능
대기 코드도 같은 추상을 거치게 한다.
// 회피absl::SleepFor(absl::Seconds(1)); // ❌ 테스트가 실제로 1초 잠
// Good — Sleeper 주입class TokenBucket {public: using Sleeper = std::function<void(absl::Duration)>;
TokenBucket(int rate, Sleeper sleeper = &absl::SleepFor) : rate_(rate), sleeper_(std::move(sleeper)) {}
void Acquire() { if (TryAcquire()) return; sleeper_(WaitTime()); }};
// 테스트absl::Duration total_slept = absl::ZeroDuration();TokenBucket tb(10, [&](absl::Duration d) { total_slept += d; });tb.Acquire();EXPECT_GT(total_slept, absl::ZeroDuration());#absl::Mutex의 condition timeout
Mutex::AwaitWithTimeout/LockWhenWithTimeout은 내부적으로 system clock에 의존한다. 이 영역만큼은 시뮬레이션이 어렵다(Abseil 자체가 mock 인터페이스를 공개하지 않음). 두 가지 우회:
- 타임아웃을 작게 잡고 실시간 테스트 — 50ms 정도면 flaky 없이 동작.
- 상위 계층 추상화 —
Notification이나BlockingCounter로 감싸 mock 가능한 인터페이스만 노출.
#시계가 단조롭게 가는지
absl::Now()는 wall clock이라 NTP 보정으로 뒤로 갈 수 있다. timeout 측정에는 monotonic이 안전하다.
// 회피 — wall clock 차이absl::Time start = absl::Now();DoWork();absl::Duration elapsed = absl::Now() - start; // 음수 가능
// Good — 시작/끝 모두 monotonicauto start = std::chrono::steady_clock::now();DoWork();auto elapsed = std::chrono::steady_clock::now() - start;Abseil은 monotonic clock을 직접 노출하지 않으므로 std::chrono::steady_clock을 그대로 쓴다. 짧은 elapsed 측정에는 이쪽이 안전.
#빠른 패턴 — 한 줄 fake
코드 한 곳에서만 시간을 통제하면 되는 경우, lambda 한 줄로 충분하다.
TEST(ExpiryTest, JustOver) { absl::Time t = absl::FromUnixSeconds(123); Expiry e(absl::Seconds(10), [&]() { return t; });
EXPECT_FALSE(e.HasExpired()); t += absl::Seconds(11); EXPECT_TRUE(e.HasExpired());}#absl 외부 옵션 — gtest_mock / Folly
GoogleTest 자체에는 시계 mock이 없다. 대안:
| 도구 | 특징 |
|---|---|
SimulatedClock (직접) | 가장 단순. function 주입. |
folly::ManualClock | std::chrono 기반, advance 가능. Folly와 함께 사용. |
std::chrono::utc_clock (C++20) | 표준이지만 mock 인터페이스 부재. |
Abseil 친화 코드는 함수 객체 주입 이 가장 잘 어울린다. 추가 의존성 없음, 인터페이스 한 줄.
#작은 예시 — Retry with backoff
class RetryPolicy {public: using Clock = std::function<absl::Time()>; using Sleeper = std::function<void(absl::Duration)>;
RetryPolicy(int max_attempts, absl::Duration base, Clock clock = absl::Now, Sleeper sleeper = &absl::SleepFor) : max_attempts_(max_attempts), base_(base), clock_(std::move(clock)), sleeper_(std::move(sleeper)) {}
template <typename Op> absl::Status Run(Op op) { absl::Time start = clock_(); for (int attempt = 0; attempt < max_attempts_; ++attempt) { absl::Status s = op(); if (s.ok()) return s; sleeper_(base_ * (1 << attempt)); // exponential if (clock_() - start > absl::Minutes(1)) { return absl::DeadlineExceededError("retry budget"); } } return absl::DeadlineExceededError("max attempts"); }};
TEST(RetryPolicyTest, GivesUpAfterDeadline) { SimulatedClock clock; absl::Duration slept = absl::ZeroDuration(); RetryPolicy p(100, absl::Seconds(1), [&]() { return clock.Now(); }, [&](absl::Duration d) { slept += d; clock.Advance(d); });
int calls = 0; auto s = p.Run([&]() { ++calls; return absl::UnknownError("nope"); }); EXPECT_EQ(s.code(), absl::StatusCode::kDeadlineExceeded); EXPECT_LT(calls, 100); EXPECT_GT(slept, absl::Minutes(1));}#정리
absl::Now()직접 호출은 테스트 가능성을 죽인다. 시계 함수를 주입한다.- 기본값을
absl::Now로 두면 production 코드는 변화 없음. SimulatedClock헬퍼로 boilerplate 축소.SleepFor도 동일하게 주입 가능 —Sleeper함수 객체.- monotonic 측정에는
std::chrono::steady_clock사용.absl::Now()는 NTP로 역행 가능.
#다음 장 예고
Part 8-01: BitGen — Abseil random engine.
#관련 항목
- Part 7-01: Time / Duration overview
- Part 6-03: Notification — 동기화 타임아웃과 시계 mock
- Part 8-03: Mocking random — 같은 정신의 random mock
- 원문 — Test time
Abseil Code Review · 44 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
Abseil Mocking Random — 테스트 결정성
MockingBitGen — 분포 호출의 결과값을 결정적으로 강제. flaky random 테스트를 끝낸다.
같은 시리즈에서 이어 읽기
absl::time_zone 분석
absl::TimeZone — IANA TZ database 위에 얹은 안전한 변환 layer. UTC가 아닌 시각을 다루는 모든 코드가 거쳐야 할 관문.
같은 시리즈에서 이어 읽기
absl::CivilTime 분석
absl::CivilDay와 가족들 — 시간대 없는 달력 산술. 윤년·월말을 직접 처리할 필요가 없게 한다.
같은 시리즈에서 이어 읽기