토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

마이크로미터 트레이싱 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

Micrometer Tracing에는 micrometer-tracing-test, micrometer-tracing-integration-test 모듈이 포함돼 있다.

단위 테스트의 경우, Tracer의 테스트 전용 구현체인 SimpleTracer를 제공한다.

통합 테스트의 경우, 테스트할 코드에 SampleTestRunner를 연결해주면 된다. SampleTestRunner는 다음과 같은 메커니즘을 제공한다:

목차


Installing

다음은 Gradle에서 필요한 의존성을 나타낸 예시다 (Micrometer Tracing BOM을 추가했다고 가정한다):

testImplementation 'io.micrometer:micrometer-tracing-test' // for unit tests
testImplementation 'io.micrometer:micrometer-tracing-integration-test' // for integration tests

다음은 Maven에서 필요한 의존성을 나타낸 예시다 (Micrometer Tracing BOM을 추가했다고 가정한다):

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-test</artifactId> <!-- For unit tests -->
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-integration-test</artifactId> <!-- For integration tests -->
    <scope>test</scope>
</dependency>

Running Tracing Unit Tests

커스텀 핸들러의 단위 테스트 코드를 만들려면, 테스트 전용 Tracer 구현체인 SimpleTracer를 사용하면 된다. 아래와 같이 TracingObservationHandler를 커스텀했다고 가정해 보자:

static class MyTracingObservationHandler implements TracingObservationHandler<CustomContext> {

    private final Tracer tracer;

    MyTracingObservationHandler(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public void onStart(CustomContext context) {
        String databaseName = context.getDatabaseName();
        Span.Builder builder = this.tracer.spanBuilder().kind(Span.Kind.CLIENT).remoteServiceName(databaseName);
        getTracingContext(context).setSpan(builder.start());
    }

    @Override
    public void onError(CustomContext context) {
        getTracingContext(context).getSpan().error(context.getError());
    }

    @Override
    public void onStop(CustomContext context) {
        Span span = getRequiredSpan(context);
        span.name(context.getContextualName() != null ? context.getContextualName() : context.getName());
        tagSpan(context, span);
        span.end();
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
        return context instanceof CustomContext;
    }

    @Override
    public Tracer getTracer() {
        return this.tracer;
    }

}

span이 제대로 생성되었는지 검증하려면, 다음과 같이 SimpleTracer를 활용하면 된다:

class SomeComponentThatIsUsingMyTracingObservationHandlerTests {

    ObservationRegistry registry = ObservationRegistry.create();

    SomeComponent someComponent = new SomeComponent(registry);

    SimpleTracer simpleTracer = new SimpleTracer();

    MyTracingObservationHandler handler = new MyTracingObservationHandler(simpleTracer);

    @BeforeEach
    void setup() {
        registry.observationConfig().observationHandler(handler);
    }

    @Test
    void should_store_a_span() {
        // this code will call actual Observation API
        someComponent.doSthThatShouldCreateSpans();

        TracerAssert.assertThat(simpleTracer)
                .onlySpan()
                .hasNameEqualTo("insert user")
                .hasKindEqualTo(Span.Kind.CLIENT)
                .hasRemoteServiceNameEqualTo("mongodb-database")
                .hasTag("mongodb.command", "insert")
                .hasTag("mongodb.collection", "user")
                .hasTagWithKey("mongodb.cluster_id")
                .assertThatThrowable()
                .isInstanceOf(IllegalStateException.class)
                .backToSpan()
                .hasIpThatIsBlank()
                .hasPortThatIsNotSet();
    }

}

Running integration tests

다음은 작성한 코드로 통합 테스트를 진행하는 방법을 보여주는 예시다:

class ObservabilitySmokeTest extends SampleTestRunner {

    ObservabilitySmokeTest() {
        super(SampleRunnerConfig.builder().wavefrontApplicationName("my-app").wavefrontServiceName("my-service")
                .wavefrontToken("...")
                .wavefrontUrl("...")
                .zipkinUrl("...") // defaults to localhost:9411
                .build());
    }

    @Override
    public BiConsumer<BuildingBlocks, Deque<ObservationHandler<? extends Observation.Context>>> customizeObservationHandlers() {
        return (bb, handlers) -> {
            ObservationHandler defaultHandler = handlers.removeLast();
            handlers.addLast(new MyTracingObservationHandler(bb.getTracer()));
            handlers.addLast(defaultHandler);
        };
    }

    @Override
    public SampleTestRunnerConsumer yourCode() {
        return (bb, meterRegistry) -> {
            // here you would be running your code
            yourCode();

            SpansAssert.assertThat(bb.getFinishedSpans())
                    .haveSameTraceId()
                    .hasNumberOfSpansEqualTo(8)
                    .hasNumberOfSpansWithNameEqualTo("handle", 4)
                    .forAllSpansWithNameEqualTo("handle", span -> span.hasTagWithKey("rsocket.request-type"))
                    .hasASpanWithNameIgnoreCase("request_stream")
                    .thenASpanWithNameEqualToIgnoreCase("request_stream")
                    .hasTag("rsocket.request-type", "REQUEST_STREAM")
                    .backToSpans()
                    .hasASpanWithNameIgnoreCase("request_channel")
                    .thenASpanWithNameEqualToIgnoreCase("request_channel")
                    .hasTag("rsocket.request-type", "REQUEST_CHANNEL")
                    .backToSpans()
                    .hasASpanWithNameIgnoreCase("request_fnf")
                    .thenASpanWithNameEqualToIgnoreCase("request_fnf")
                    .hasTag("rsocket.request-type", "REQUEST_FNF")
                    .backToSpans()
                    .hasASpanWithNameIgnoreCase("request_response")
                    .thenASpanWithNameEqualToIgnoreCase("request_response")
                    .hasTag("rsocket.request-type", "REQUEST_RESPONSE");

            MeterRegistryAssert.assertThat(meterRegistry)
                    .hasTimerWithNameAndTags("rsocket.response", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_RESPONSE")))
                    .hasTimerWithNameAndTags("rsocket.fnf", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_FNF")))
                    .hasTimerWithNameAndTags("rsocket.request", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_RESPONSE")))
                    .hasTimerWithNameAndTags("rsocket.channel", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_CHANNEL")))
                    .hasTimerWithNameAndTags("rsocket.stream", Tags.of(Tag.of("error", "none"), Tag.of("rsocket.request-type", "REQUEST_STREAM")));
        };
    }

}

전체 목차는 여기에 있습니다.

<< >>

TOP