본문 바로가기
카테고리 없음

JMX란 ? ( JMeter-1 )

by techdebt 2025. 7. 29.
반응형

JMX란 무엇일까요? 

JMX = JMeter eXtensible Markup language

- JMeter가 이해할 수 있는 설정 파일

- XML 형식으로 작성됨

- 테스트 계획을 저장하는 파일

 

JMeter용 마크업 언어인데 쉽게 설명하자면 JMeter는 요리사고 JMX 는 레시피(요리법) 입니다. 

요리사(JMeter)는 레시피(JMX)를 보고 요리(부하테스트)를 한다고 생각하시면 됩니다.

그럼 Jmeter부터 설치하고 진행해보겠습니다


■ Jemter 설치

1. Apache JMeter - Download Apache JMeter 에서 Binary로 Download

 

2. 압축파일 해제 후, bin/ApacheJMeter.jar 실행

 

실행이 되셨다면 이제 JMeter를 사용할 준비가 된 겁니다.

그럼 JMX에 대해서 이어서 확인하겠습니다.

 

왜 JMX는 복잡할까요 

크게 아래 3가지 이유때문에 JMX는 복잡할 수밖에 없다고 생각합니다.

 

1. 모든 설정을 명시해야 함

  • 포트 번호, 프로토콜, 인코딩, 타임아웃 등
  • 기본값도 모두 XML에 써야 함

2. XML의 한계

  • 중첩 구조가 깊어짐
  • 같은 정보를 여러 번 반복
  • 사람이 읽기 어려운 형식

3. JMeter의 GUI를 XML로 표현

  • JMeter GUI의 모든 버튼, 체크박스, 텍스트 필드를 XML로 저장
  • GUI 설정이 복잡할수록 XML도 복잡해짐
<!-- "구글에 접속해서 검색하기" -->
<!--  실제 JMX로 표현하면... -->

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="완벽 호환 테스트" enabled="true">
      <stringProp name="TestPlan.comments">JMeter 5.6.3 완벽 호환 테스트</stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="사용자 그룹">
        <intProp name="ThreadGroup.num_threads">5</intProp>
        <intProp name="ThreadGroup.ramp_time">10</intProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
          <stringProp name="LoopController.loops">3</stringProp>
          <boolProp name="LoopController.continue_forever">false</boolProp>
        </elementProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP 요청">
          <stringProp name="TestPlan.comments">www.google.com</stringProp>
          <stringProp name="HTTPSampler.domain">google.com</stringProp>
          <stringProp name="HTTPSampler.port">443</stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.path">/</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="사용자 정의 변수들">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
        </HTTPSamplerProxy>
        <hashTree/>
        <UniformRandomTimer guiclass="UniformRandomTimerGui" testclass="UniformRandomTimer" testname="대기 시간">
          <stringProp name="ConstantTimer.delay">1000</stringProp>
          <stringProp name="RandomTimer.range">2000</stringProp>
        </UniformRandomTimer>
        <hashTree/>
      </hashTree>
      <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree">
        <boolProp name="ResultCollector.error_logging">false</boolProp>
        <objProp>
          <name>saveConfig</name>
          <value class="SampleSaveConfiguration">
            <time>true</time>
            <latency>true</latency>
            <timestamp>true</timestamp>
            <success>true</success>
            <label>true</label>
            <code>true</code>
            <message>true</message>
            <threadName>true</threadName>
            <dataType>true</dataType>
            <encoding>false</encoding>
            <assertions>true</assertions>
            <subresults>true</subresults>
            <responseData>false</responseData>
            <samplerData>false</samplerData>
            <xml>false</xml>
            <fieldNames>true</fieldNames>
            <responseHeaders>false</responseHeaders>
            <requestHeaders>false</requestHeaders>
            <responseDataOnError>false</responseDataOnError>
            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
            <assertionsResultsToSave>0</assertionsResultsToSave>
            <bytes>true</bytes>
            <sentBytes>true</sentBytes>
            <url>true</url>
            <threadCounts>true</threadCounts>
            <idleTime>true</idleTime>
            <connectTime>true</connectTime>
          </value>
        </objProp>
        <stringProp name="filename">test_results.jtl</stringProp>
      </ResultCollector>
      <hashTree/>
      <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report">
        <boolProp name="ResultCollector.error_logging">false</boolProp>
        <objProp>
          <name>saveConfig</name>
          <value class="SampleSaveConfiguration">
            <time>true</time>
            <latency>true</latency>
            <timestamp>true</timestamp>
            <success>true</success>
            <label>true</label>
            <code>true</code>
            <message>true</message>
            <threadName>true</threadName>
            <dataType>true</dataType>
            <encoding>false</encoding>
            <assertions>true</assertions>
            <subresults>true</subresults>
            <responseData>false</responseData>
            <samplerData>false</samplerData>
            <xml>false</xml>
            <fieldNames>true</fieldNames>
            <responseHeaders>false</responseHeaders>
            <requestHeaders>false</requestHeaders>
            <responseDataOnError>false</responseDataOnError>
            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
            <assertionsResultsToSave>0</assertionsResultsToSave>
            <bytes>true</bytes>
            <sentBytes>true</sentBytes>
            <url>true</url>
            <threadCounts>true</threadCounts>
            <idleTime>true</idleTime>
            <connectTime>true</connectTime>
          </value>
        </objProp>
        <stringProp name="filename"></stringProp>
      </ResultCollector>
      <hashTree/>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

 

자 그럼 google.com 말고 naver.com 으로 요청을 하는 테스트케이스를 하나 더 넣어보겠습니다.

 

고작 http request 하나 넣었을뿐인데 수많은 옵션 범벅이 된 XML이 추가 되었습니다.  
XML을 사용한다면 피할 수 없는 숙명이죠.. 그리고 중첩이 깊으면 깊을수록 더 복잡할 겁니다. 

        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP 요청2">
          <stringProp name="HTTPSampler.domain">naver.com</stringProp>
          <stringProp name="HTTPSampler.port">443</stringProp>
          <stringProp name="HTTPSampler.protocol">https</stringProp>
          <stringProp name="HTTPSampler.path">/</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.postBodyRaw">false</boolProp>
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="사용자 정의 변수들">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
        </HTTPSamplerProxy>

 

 

 JMX 간단 구조 설명

그렇다면 이 복잡한 JMX구조를 조금 더 간단하게 설명해보겠습니다.

"10명이 10초에 걸쳐 우리 웹사이트에 접속해서 로그인하고 상품을 검색하는 테스트"를 만든다고 가정했을때.

 

1. 10명이 10초에 걸쳐서 작성

<ThreadGroup testname="사용자들">
  <stringProp name="ThreadGroup.num_threads">10</stringProp>
  <stringProp name="ThreadGroup.ramp_time">30</stringProp>
  <longProp name="ThreadGroup.duration">10</longProp>
  <elementProp name="ThreadGroup.main_controller" elementType="LoopController">
    <boolProp name="LoopController.continue_forever">false</boolProp>
    <stringProp name="LoopController.loops">1</stringProp>
  </elementProp>
  <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
  <boolProp name="ThreadGroup.scheduler">false</boolProp>
  <stringProp name="ThreadGroup.duration"></stringProp>
  <stringProp name="ThreadGroup.delay"></stringProp>
  <!-- ... 더 많은 설정들 ... -->
</ThreadGroup>

 

2. 웹사이트에  접속해서 작성

<HTTPSamplerProxy testname="웹사이트 접속">
  <stringProp name="HTTPSampler.domain">mywebsite.com</stringProp>
  <stringProp name="HTTPSampler.port">443</stringProp>
  <stringProp name="HTTPSampler.protocol">https</stringProp>
  <stringProp name="HTTPSampler.path">/</stringProp>
  <stringProp name="HTTPSampler.method">GET</stringProp>
  <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
  <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
  <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
    <collectionProp name="Arguments.arguments"/>
  </elementProp>
  <!-- ... 더 많은 설정들 ... -->
</HTTPSamplerProxy>

 

3. 로그인하고 작성

<HTTPSamplerProxy testname="로그인">
  <stringProp name="HTTPSampler.domain">mywebsite.com</stringProp>
  <stringProp name="HTTPSampler.path">/login</stringProp>
  <stringProp name="HTTPSampler.method">POST</stringProp>
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
    <collectionProp name="Arguments.arguments">
      <elementProp name="username" elementType="HTTPArgument">
        <stringProp name="Argument.name">username</stringProp>
        <stringProp name="Argument.value">testuser</stringProp>
        <boolProp name="HTTPArgument.always_encode">false</boolProp>
        <stringProp name="Argument.metadata">=</stringProp>
        <boolProp name="HTTPArgument.use_equals">true</boolProp>
      </elementProp>
      <elementProp name="password" elementType="HTTPArgument">
        <stringProp name="Argument.name">password</stringProp>
        <stringProp name="Argument.value">testpass</stringProp>
        <boolProp name="HTTPArgument.always_encode">false</boolProp>
        <stringProp name="Argument.metadata">=</stringProp>
        <boolProp name="HTTPArgument.use_equals">true</boolProp>
      </elementProp>
    </collectionProp>
  </elementProp>
  <!-- ... 더 많은 설정들 ... -->
</HTTPSamplerProxy>

 

4. 상품을 검색 작성

<HTTPSamplerProxy testname="상품 검색">
  <stringProp name="HTTPSampler.path">/search</stringProp>
  <stringProp name="HTTPSampler.method">GET</stringProp>
  <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
    <collectionProp name="Arguments.arguments">
      <elementProp name="keyword" elementType="HTTPArgument">
        <stringProp name="Argument.name">keyword</stringProp>
        <stringProp name="Argument.value">노트북</stringProp>
        <!-- ... 더 많은 설정들 ... -->
      </elementProp>
    </collectionProp>
  </elementProp>
  <!-- ... 더 많은 설정들 ... -->
</HTTPSamplerProxy>

 

이렇듯 UI가 아닌 xml로 신규 작성을 하는 경우에는 굉장히 손이 많이간다고 봅니다.

수정도 마찬가지죠.

가능하면 UI로 작성하고 부분적으로 수정하거나, GPT의 힘을 빌리는게 현명한 선택으로 보입니다 :)