Abseil Mocking Random — 테스트 결정성
#random 테스트의 두 가지 함정
랜덤이 들어간 코드를 테스트하는 두 가지 흔한 시도가 있고 둘 다 좋지 않다.
// 함정 1 — 시드 고정absl::BitGen bg(absl::SeedSeq{42});int x = absl::Uniform(bg, 0, 100);EXPECT_EQ(x, 73); // ❌ Abseil 버전 업그레이드 시 깨짐absl::BitGen의 정확한 출력 시퀀스는 ABI가 아니다. 라이브러리 버전이 바뀌면 같은 시드도 다른 값을 낸다.
// 함정 2 — 통계적 검증for (int i = 0; i < 1000; ++i) total += absl::Uniform(bg, 0, 100);EXPECT_NEAR(total / 1000.0, 50.0, 1.0); // ⚠️ 가끔 깨짐평균 검증은 언젠가 깨진다. flaky test의 단골이다.
#MockingBitGen — 분포별 결과 강제
Abseil은 분포 호출 자체를 mock할 수 있는 인터페이스를 제공한다.
#include "absl/random/mocking_bit_gen.h"#include "absl/random/mock_distributions.h"
TEST(MyClassTest, RetriesOnRare) { absl::MockingBitGen bg;
// 다음 호출에서 Bernoulli(0.01)을 true로 강제 EXPECT_CALL(absl::MockBernoulli(), Call(bg, 0.01)) .WillOnce(testing::Return(true));
MyClass m(&bg); m.MaybeRetry(); // 내부적으로 Bernoulli(bg, 0.01) 호출}MockingBitGen은 URBG 컨셉을 만족하지만, Uniform/Bernoulli/Gaussian 호출이 들어오면 gmock matcher로 가로챈다.
#분포별 Mock 매크로
각 분포마다 짝지어진 Mock 클래스가 있다.
| 분포 | Mock |
|---|---|
Uniform | MockUniform<T>() |
Bernoulli | MockBernoulli() |
Gaussian | MockGaussian<T>() |
Exponential | MockExponential<T>() |
Poisson | MockPoisson<T>() |
Zipf | MockZipf<T>() |
LogUniform | MockLogUniform<T>() |
Beta | MockBeta<T>() |
사용 패턴은 동일하다.
absl::MockingBitGen bg;
EXPECT_CALL(absl::MockUniform<int>(), Call(bg, 0, 100)) .WillOnce(testing::Return(42));
int x = absl::Uniform(bg, 0, 100);EXPECT_EQ(x, 42);#ON_CALL vs EXPECT_CALL
EXPECT_CALL은 호출이 반드시 일어남을 검증. ON_CALL은 호출이 일어나면 이렇게 답한다는 행동 정의(검증 없음).
// 호출 보장 + 응답EXPECT_CALL(absl::MockBernoulli(), Call(bg, 0.01)) .WillOnce(testing::Return(true));
// 호출 여부 무관, 호출되면 응답ON_CALL(absl::MockBernoulli(), Call(bg, testing::_)) .WillByDefault(testing::Return(false));코드 리뷰에서 자주 보는 패턴:
- 핵심 분기를 결정짓는 호출 →
EXPECT_CALL - 기본 행동만 정의(호출 횟수 무관) →
ON_CALL
#Matcher로 인자 캡처
분포 파라미터에 matcher를 쓸 수 있다.
// p가 무엇이든 true 반환EXPECT_CALL(absl::MockBernoulli(), Call(bg, testing::_)) .WillRepeatedly(testing::Return(true));
// p가 특정 범위면 trueEXPECT_CALL(absl::MockBernoulli(), Call(bg, testing::Lt(0.5))) .WillOnce(testing::Return(true));Sequence로 호출 순서를 지정하거나 Times로 횟수를 박을 수도 있다.
testing::InSequence seq;EXPECT_CALL(absl::MockUniform<int>(), Call(bg, 0, 10)) .Times(3) .WillRepeatedly(testing::Return(5));#통과시키기 — DefaultBitGenRef
mock하지 않은 분포 호출은 진짜 random 으로 통과시키는 옵션도 있다.
EXPECT_CALL(absl::MockBernoulli(), Call(bg, 0.01)) .WillOnce(testing::Return(true));
// Uniform 호출은 mock 안 함 → 실제 BitGen으로 fallbackint x = absl::Uniform(bg, 0, 10);MockingBitGen은 내부적으로 실제 BitGen을 들고 있어 mock되지 않은 호출은 그쪽으로 보낸다.
#작은 예시 — A/B 테스트 분기
class FeatureFlag {public: explicit FeatureFlag(absl::BitGenRef bg) : bg_(bg) {}
bool IsEnabled(absl::string_view user_id) { // hash(user_id)를 [0, 1)로 매핑 — 결정적 // 그 위에 1% 트래픽을 신기능에 라우팅 double h = HashToUnit(user_id); if (h < 0.01) return true; return absl::Bernoulli(bg_, 0.5); // 5050 분기 }
private: absl::BitGenRef bg_;};
TEST(FeatureFlagTest, RoutesBy5050) { absl::MockingBitGen bg;
EXPECT_CALL(absl::MockBernoulli(), Call(bg, 0.5)) .WillOnce(testing::Return(true)) .WillOnce(testing::Return(false));
FeatureFlag ff(bg); EXPECT_TRUE(ff.IsEnabled("user_a")); // Bernoulli → true EXPECT_FALSE(ff.IsEnabled("user_b")); // Bernoulli → false}#BitGenRef — 인터페이스 추상
absl::BitGenRef는 URBG 컨셉의 type-erased reference. mock과 실제 BitGen 모두 받는다.
class Sampler {public: explicit Sampler(absl::BitGenRef bg) : bg_(bg) {} int Pick() { return absl::Uniform(bg_, 0, 100); }
private: absl::BitGenRef bg_; // ref-like, 가벼움};
// Productionabsl::BitGen real;Sampler s(real);
// Testabsl::MockingBitGen mock;Sampler s_test(mock);BitGenRef로 받으면 호출자 측에서 BitGen 종류를 자유롭게 바꾼다.
#회피 패턴
// 회피 — 시드 고정으로 출력 검증absl::BitGen bg(absl::SeedSeq{42});EXPECT_EQ(absl::Uniform(bg, 0, 100), 73); // ❌ ABI 의존
// Good — Mocking으로 결과 강제absl::MockingBitGen mbg;EXPECT_CALL(absl::MockUniform<int>(), Call(mbg, 0, 100)) .WillOnce(testing::Return(73));// 회피 — random을 의존성으로 못 주입class MyClass { absl::BitGen bg_; // ❌ 내부 생성 — 테스트 불가};
// Good — BitGenRef로 주입class MyClass {public: explicit MyClass(absl::BitGenRef bg) : bg_(bg) {}private: absl::BitGenRef bg_;};#정리
- 시드 고정으로 출력값 검증은 안 된다 — ABI가 아니다.
MockingBitGen+MockXxx()로 분포 호출 결과를 강제.- 코드는
absl::BitGenRef로 주입받게 설계 — production·테스트 동일 인터페이스. - mock 안 한 호출은 내부 실제 BitGen으로 통과.
- gmock matcher(
_,Lt,Ge)로 파라미터 조건 표현.
#다음 장 예고
Part 8-04: Seeding & entropy — 시드 관리와 보안 고려.
#관련 항목
- Part 8-01: BitGen
- Part 7-05: Time mocking — 같은 정신의 clock mock
- 원문 — Random Mocking
Abseil Code Review · 47 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 Random Seeding·Entropy
BitGen 시드 관리 — OS entropy, SeedSeq, 결정적 재현성. 그리고 보안 한계.
같은 시리즈에서 이어 읽기
Abseil Random Distributions — Uniform·Exponential
absl::Uniform, Gaussian, Exponential, Poisson 등 — std::*_distribution의 클래스 객체를 함수 호출로 평탄화.
같은 시리즈에서 이어 읽기
absl::BitGen — 모던 난수 생성기
absl::BitGen — std::mt19937 + std::uniform_int_distribution의 길고 verbose한 조합을 단일 호출로 압축한 random engine.
같은 시리즈에서 이어 읽기