> Java > java지도 시간 > 본문

Java 단위 테스트 작성을 위한 7가지 팁

黄舟
풀어 주다: 2017-03-20 10:31:02
원래의
1483명이 탐색했습니다.

테스트는 개발에서 매우 중요한 측면이며 애플리케이션의 운명을 크게 결정할 수 있습니다. 좋은 테스트는 애플리케이션 충돌을 일으키는 문제를 조기에 포착할 수 있지만, 잘못된 테스트는 종종 실패와 가동 중지 시간으로 이어집니다.

소프트웨어 테스팅에는 유닛 테스팅, 기능 테스팅, 통합 테스팅의 세 가지 주요 유형이 있지만, 이번 블로그 게시물에서는 개발자 수준의 유닛 테스팅에 대해 논의하겠습니다. 세부 사항을 살펴보기 전에 이 세 가지 테스트 각각의 세부 사항을 검토해 보겠습니다.

소프트웨어 개발 테스트 유형

단위 테스트는 개별 코드 구성 요소를 테스트하고 코드가 예상대로 작동하는지 확인하는 데 사용됩니다. 단위 테스트는 개발자가 작성하고 실행합니다. 대부분의 경우 JUnit 또는 TestNG와 같은 테스트 프레임워크가 사용됩니다. 테스트 케이스는 일반적으로 메소드 수준에서 작성되고 자동화를 통해 실행됩니다. 통합 테스트는 시스템이 전체적으로 작동하는지 확인하는 것입니다. 통합 테스트도 개발자가 수행하지만 개별 구성 요소를 테스트하는 대신 구성 요소 전체를 테스트하도록 설계되었습니다. 시스템은 코드, 데이터베이스, 웹 서버 등과 같은 많은 개별 구성 요소로 구성됩니다. 통합 테스트를 통해 구성 요소 배선, 네트워크 액세스, 데이터베이스 문제 등과 같은 문제를 발견할 수 있습니다.

기능 테스트는 주어진 입력의 결과를 사양과 비교하여 각 기능이 올바르게 구현되었는지 확인합니다. 일반적으로 이는 개발자 수준이 아닙니다. 기능 테스트는 별도의 테스트 팀에서 수행됩니다. 사양에 따라 테스트 케이스를 작성하고 실제 결과를 예상 결과와 비교합니다. Selenium 및 QTP와 같이 자동화된 기능 테스트에 사용할 수 있는 여러 도구가 있습니다.

앞서 언급했듯이 단위 테스트는 개발자가 코드가 제대로 작동하는지 확인하는 데 도움이 됩니다. 이번 블로그 포스팅에서는 Java 단위 테스트에 대한 유용한 팁을 제공하겠습니다.

1. 단위 테스트를 위한 프레임워크 사용

Java는 단위 테스트를 위한 여러 프레임워크를 제공합니다. TestNG와 JUnit은 가장 널리 사용되는 테스트 프레임워크입니다. JUnit 및 TestNG의 몇 가지 중요한 기능:

    설정 및 실행이 쉽습니다.
  • 댓글을 응원합니다.
  • 특정 테스트를 무시하거나 그룹화하여 함께 실행할 수 있습니다.
  • 은 매개변수화된 테스트, 즉 런타임에 다른 값을 지정하여 단위 테스트를 실행하는 기능을 지원합니다.
  • Ant, Maven, Gradle 등의 빌드 도구와 통합하여 자동화된 테스트 실행을 지원합니다.
  • EasyMock은 JUnit 및 TestNG와 같은 단위 테스트 프레임워크를 보완하는 모의 프레임워크입니다. EasyMock 자체는 완전한 프레임워크가 아닙니다. 단지 더 쉬운 테스트를 위해 모의 객체를 생성하는 기능을 추가했을 뿐입니다. 예를 들어 테스트하려는 한 가지 메서드는 데이터베이스에서 데이터를 가져오는 DAO 클래스를 호출할 수 있습니다. 이 경우 EasyMock을 사용하여 하드코딩된 데이터를 반환하는 MockDAO를 만들 수 있습니다. 이를 통해 데이터베이스 액세스에 대해 걱정할 필요 없이 의도한 방법을 쉽게 테스트할 수 있습니다.

2. 테스트 중심 개발을 주의해서 사용하세요!

테스트 중심 개발(TDD)은 코딩을 시작하기 전에 요구 사항에 따라 테스트를 작성하는 소프트웨어 개발 프로세스입니다. 아직 코딩이 없으므로 테스트는 처음에는 실패합니다. 그런 다음 테스트를 통과하기 위한 최소한의 코드를 작성합니다. 그런 다음 최적화될 때까지 코드를 리팩터링합니다.

애초에 요구 사항을 충족하지 못할 수도 있는 코드를 작성하는 것보다 모든 요구 사항을 충족하는 테스트를 작성하는 것이 목표입니다. TDD는 유지 관리가 쉬운 간단한

모듈식

코드를 생성하므로 훌륭합니다. 전체적인 개발 속도가 빨라지고 결함도 쉽게 발견됩니다. 또한 단위 테스트는 TDD 접근 방식의 부산물로 생성됩니다. 그러나 TDD는 모든 상황에 적합하지 않을 수 있습니다. 디자인이 복잡한 프로젝트에서 미리 생각하지 않고 테스트 사례를 쉽게 통과할 수 있도록 가장 단순한 디자인에 집중하면 엄청난 코드 변경이 발생할 수 있습니다. 더욱이 TDD 방법은 레거시 시스템, GUI 애플리케이션 또는 데이터베이스와 작동하는 애플리케이션과 상호 작용하는 시스템에 사용하기 어렵습니다. 또한 코드가 변경되면 테스트도 업데이트해야 합니다.

따라서 TDD 접근 방식을 결정하기 전에 위의 요소를 고려하고 프로젝트의 성격에 따라 조치를 취해야 합니다.

3. 코드 커버리지 측정

코드 커버리지는 단위 테스트가 실행될 때 실행되는 코드의 양을 백분율로 표시합니다. 일반적으로 적용 범위가 높은 코드는 테스트 중에 더 많은 소스 코드가 실행되므로 감지되지 않은 버그가 포함될 가능성이 낮습니다. 코드 적용 범위를 측정하는 몇 가지 모범 사례는 다음과 같습니다.

    Clover, Corbetura, JaCoCo 또는 Sonar와 같은 코드 적용 도구를 사용합니다. 도구를 사용하면 테스트되지 않은 코드 영역을 지적하고 해당 영역을 포괄하는 추가 테스트를 개발할 수 있으므로 테스트 품질이 향상될 수 있습니다.
  • 새로운 기능이 작성될 때마다 즉시 새로운 테스트 커버리지를 작성하세요.
  • 코드의 모든 분기(예: if/else 문)를 포괄하는 테스트 사례가 있는지 확인하세요.

높은 코드 커버리지가 완벽한 테스트를 보장하지 않으니 주의하세요!

아래 concat 메서드는 부울 값을 입력으로 받아들이고 부울 값이 true인 경우에만 두 개의 문자열을 추가로 전달합니다.

public String concat(boolean append, String a,String b) {
        String result = null;
        If (append) {
            result = a + b;
                            }
        return result.toLowerCase();
}
로그인 후 복사

다음은 위의 방법입니다. 테스트 사례:

@Test
public void testStringUtil() {
     String result = stringUtil.concat(true, "Hello ", "World");
     System.out.println("Result is "+result);
}
로그인 후 복사

이 경우 실행된 테스트의 값은 true입니다. 테스트가 실행되면 통과됩니다. 코드 커버리지 도구를 실행하면 concat 메서드의 모든 코드가 실행되었기 때문에 100% 코드 커버리지를 표시합니다. 그러나 테스트가 false 값으로 실행되면 NullPointerException이 발생합니다. 따라서 100% 코드 적용 범위는 실제로 테스트가 모든 시나리오를 포괄한다는 의미는 아니며 테스트가 훌륭하다는 의미도 아닙니다.

4. 테스트 데이터를 최대한 외부화하세요

JUnit4 이전에는 테스트 케이스를 실행하기 위한 데이터를 테스트 케이스에 하드코딩해야 했습니다. 이로 인해 다른 데이터로 테스트를 실행하려면 테스트 케이스 코드를 수정해야 하는 제한 사항이 발생합니다. 그러나 JUnit4와 TestNG는 테스트 데이터 외부화를 지원하므로 소스 코드를 변경하지 않고도 다양한 데이터 세트에 대해 테스트 케이스를 실행할 수 있습니다.

다음 MathChecker 클래스에는 숫자가 홀수인지 확인하는 메서드가 있습니다.

public class MathChecker {
        public Boolean isOdd(int n) {
            if (n%2 != 0) {
                return true;
            } else {
                return false;
            }
        }
    }
로그인 후 복사

다음은 MathChecker 클래스에 대한 TestNG 테스트 사례입니다.

public class MathCheckerTest {
        private MathChecker checker;
        @BeforeMethod
        public void beforeMethod() {
          checker = new MathChecker();
        }
        @Test
        @Parameters("num")
        public void isOdd(int num) { 
          System.out.println("Running test for "+num);
          Boolean result = checker.isOdd(num);
          Assert.assertEquals(result, new Boolean(true));
        }
    }
로그인 후 복사

TestNG

다음은 테스트를 실행할 데이터가 포함된 testng.xml(TestNG용 구성 파일)입니다.

    <?xml version="1.0" encoding="UTF-8"?>
    <suite name="ParameterExampleSuite" parallel="false">
    <test name="MathCheckerTest">
    <classes>
      <parameter name="num" value="3"></parameter>
      <class name="com.stormpath.demo.MathCheckerTest"/>
    </classes>
     </test>
     <test name="MathCheckerTest1">
    <classes>
      <parameter name="num" value="7"></parameter>
      <class name="com.stormpath.demo.MathCheckerTest"/>
    </classes>
     </test>
    </suite>
로그인 후 복사

보시다시피 이 경우 테스트는 값 3과 7을 각각 한 번씩 두 번 실행합니다. XML 구성 파일을 통해 테스트 데이터를 지정하는 것 외에도 DataProvider 주석을 통해 클래스에 테스트 데이터를 제공할 수도 있습니다.

JUnit

TestNG와 유사하게 테스트 데이터를 JUnit과 함께 사용하기 위해 외부화할 수도 있습니다. 다음은 위와 동일한 MathChecker 클래스에 대한 JUnit 테스트 케이스입니다.

    @RunWith(Parameterized.class)
    public class MathCheckerTest {
     private int inputNumber;
     private Boolean expected;
     private MathChecker mathChecker;
     @Before
     public void setup(){
         mathChecker = new MathChecker();
     }
        // Inject via constructor
        public MathCheckerTest(int inputNumber, Boolean expected) {
            this.inputNumber = inputNumber;
            this.expected = expected;
        }
        @Parameterized.Parameters
        public static Collection<Object[]> getTestData() {
            return Arrays.asList(new Object[][]{
                    {1, true},
                    {2, false},
                    {3, true},
                    {4, false},
                    {5, true}
            });
        }
        @Test
        public void testisOdd() {
            System.out.println("Running test for:"+inputNumber);
            assertEquals(mathChecker.isOdd(inputNumber), expected);
        }
    }
로그인 후 복사

보시다시피 테스트를 수행할 테스트 데이터는 getTestData() 메서드에 의해 지정됩니다. 이 방법은 데이터를 하드코딩하는 대신 외부 파일에서 데이터를 읽도록 쉽게 수정할 수 있습니다.

5. Print 문 대신 어설션을 사용하세요

많은 초보 개발자는 코드가 올바르게 실행되는지 확인하기 위해 각 코드 줄 뒤에 System.out.println 문을 작성하는 데 익숙합니다. 이 관행은 종종 단위 테스트로 확장되어 테스트 코드가 복잡해집니다. 혼란 외에도 테스트가 성공적으로 실행되었는지 확인하기 위해 콘솔에 인쇄된 출력을 확인하려면 개발자의 수동 개입이 필요합니다. 더 나은 접근 방식은 테스트 결과를 자동으로 나타내는 어설션을 사용하는 것입니다.

다음 StringUti 클래스는 두 개의 입력 문자 를 연결하고 결과를 반환하는 메서드를 가진 간단한 클래스입니다.

public class StringUtil {
        public String concat(String a,String b) {
            return a + b;
        }
    }
로그인 후 복사

다음은 위의 두 가지 메서드입니다. 메서드 단위 테스트:

@Test
    public void testStringUtil_Bad() {
         String result = stringUtil.concat("Hello ", "World");
         System.out.println("Result is "+result);
    }
    @Test
    public void testStringUtil_Good() {
         String result = stringUtil.concat("Hello ", "World");
         assertEquals("Hello World", result);
    }
로그인 후 복사

testStringUtil_Bad에는 어설션이 없기 때문에 항상 전달됩니다. 개발자는 콘솔에서 테스트 출력을 수동으로 확인해야 합니다. 메서드가 잘못된 결과를 반환하고 개발자 개입이 필요하지 않으면 testStringUtil_Good이 실패합니다.

6. 결정적인 결과로 테스트 구축

일부 방법에는 결정적인 결과가 없습니다. 즉, 방법의 출력은 미리 알려지지 않으며 매번 변경될 수 있습니다. 예를 들어, 복잡한 함수 와 복잡한 함수를 실행하는 데 걸리는 시간(밀리초)을 계산하는 메서드가 있는 다음 코드를 고려해 보세요.

   public class DemoLogic {
    private void veryComplexFunction(){
        //This is a complex function that has a lot of database access and is time consuming
        //To demo this method, I am going to add a Thread.sleep for a random number of milliseconds
        try {
            int time = (int) (Math.random()*100);
            Thread.sleep(time);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public long calculateTime(){
        long time = 0;
        long before = System.currentTimeMillis();
        veryComplexFunction();
        long after = System.currentTimeMillis();
        time = after - before;
        return time;
    }
    }
로그인 후 복사

이 경우 각 다음에 calculateTime 메서드가 실행되면 다른 값이 반환됩니다. 이 방법의 출력은 가변적이므로 이 방법에 대한 테스트 사례를 작성하는 것은 아무 소용이 없습니다. 따라서 테스트 메서드는 특정 실행의 출력을 확인할 수 없습니다.

7. 긍정적인 시나리오 외에도 부정적인 시나리오와 극단적인 사례를 테스트하세요.

일반적으로 개발자는 애플리케이션이 예상대로 작동하는지 확인하기 위해 테스트 사례를 작성하는 데 많은 시간과 노력을 쏟습니다. 그러나 부정적인 테스트 사례를 테스트하는 것도 중요합니다. 부정적인 테스트 케이스는 시스템이 유효하지 않은 데이터를 처리할 수 있는지 여부를 테스트하는 테스트 케이스를 의미합니다. 예를 들어, 사용자가 입력한 길이 8의 영숫자 값을 읽는 간단한 함수를 생각해 보세요. 영숫자 값 외에도 다음과 같은 부정적인 테스트 사례를 테스트해야 합니다.

  • 특수 문자와 같은 영숫자가 아닌 사용자 지정 값.

  • 사용자가 지정한 null 값입니다.

  • 8자보다 크거나 작은 사용자 지정 값입니다.

마찬가지로 경계 테스트 사례에서는 시스템이 극단값에 적합한지 여부를 테스트합니다. 예를 들어, 사용자가 1부터 100까지의 숫자 값을 입력하려는 경우 1과 100이 경계 값이며 이러한 값에 대해 시스템을 테스트하는 것이 매우 중요합니다.

위 내용은 Java 단위 테스트 작성을 위한 7가지 팁의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!