Technical Note/TEST AUTOMATION

TestNG는 단위테스트를 비롯하여 기능테스트, 통합테스트, 인수테스트 등 모든 범주의 테스트를 지원하는 것을 추구한다. JUnit보다 더욱 유연하고 쉬운 테스트가 가능하며, 효율적인 테스트를 위한 많은 기능들을 제공하고 있다. 차세대 자바 테스팅 엔진으로서 손색이 없는 TestNG의 사용법을 예제를 통해 알아본다.

1. 기본 테스트 예제

import org.testng.Assert;
import org.testng.annotations.Test;

public class SimpleTest {
	private int x = 2;
	private int y = 3;

	@Test
	public void testAddition() {
		int z = x + y;
		Assert.assertEquals(5, z);
	}

	@Test(groups = { "fast" })
	public void aFastTest() {
		System.out.println("Fast test");
	}

	@Test(groups = { "slow" })
	public void aSlowTest() {
		System.out.println("Slow test");
	}
}

- @Test 어노테이션을 사용하여 테스트 메소드를 정의하고, Assert 구문을 사용하여 테스트를 검증한다.
- JUnit과는 다르게 테스트 메소드를 groups 파미미터 지정으로 특정 그룹으로 지정할 수 있다.
- 이렇게 지정된 그룹은 이후에 그룹별 실행이나 종속성을 갖도록 설정되어질 수도 있다.

- 아래는 테스트를 수행한 한 예로 testng.xml 파일을 이용한 것이다. 결과는 콘솔에 보여준다.

C:\TestingWorks\TestNG01>java org.testng.TestNG testng.xml
[Parser] Running:
  D:\WORKS\TestingWorks\TestNG01\temp-testng-customsuite.xml

Slow test
Fast test
PASSED: aSlowTest
PASSED: testAddition
PASSED: aFastTest

===============================================
    SimpleTest
    Tests run: 3, Failures: 0, Skips: 0
===============================================


===============================================
TestNG01
Total tests run: 3, Failures: 0, Skips: 0
===============================================


- 테스트 수행은 다양하게 설정되어 질 수 있는데, 위 테스트의 testng.xml의 내용이다.

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="TestNG01">
  <test verbose="2" name="SimpleTest" annotations="JDK">
    <classes>
      <class name="testing.testng.SimpleTest"/>
    </classes>
  </test>
</suite>


2. 테스트 메소드 실행 전/후에 처리작업 수행하기

import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterGroups;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class BeforeAfterTest {

	@BeforeSuite
	public void beforeSuite() {
		System.out.println("@BeforeSuite");
	}

	@AfterSuite
	public void afterSuite() {
		System.out.println("@AfterSuite");
	}

	@BeforeTest
	public void beforeTest() {
		System.out.println("@BeforeTest");
	}

	@AfterTest
	public void afterTest() {
		System.out.println("@AfterTest");
	}

	@BeforeClass
	public void beforeClass() {
		System.out.println("@BeforeClass");
	}

	@AfterClass
	public void afterClass() {
		System.out.println("@AfterClass");
	}

	@BeforeGroups(groups = { "fast" })
	public void beforeGroups() {
		System.out.println("@BeforeGroups: fast");
	}

	@AfterGroups(groups = { "fast" })
	public void afterGroups() {
		System.out.println("@AfterGroups: fast");
	}

	@BeforeMethod
	public void beforeMethod() {
		System.out.println("@BeforeMethod");
	}

	@AfterMethod
	public void afterMethod() {
		System.out.println("@AfterMethod");
	}

	@Test(groups = { "fast" })
	public void aFastTest() {
		System.out.println("Fast test");
	}

	@Test(groups = { "slow" })
	public void aSlowTest() {
		System.out.println("Slow test");
	}
}

- TestNG는 테스트 수행시 다양한 스코프의 전/후 처리작업을 설정할 수 있다.
- 실행 전 순서: @BeforeSuite -> @BeforeTest -> @BeforeClass -> @BeforeGroups -> @BeforeMethod
- 실행 후 순서: @AfterMethod -> @AfterGroups -> @AfterClass -> @AfterTest -> @AfterSuite

- 아래는 테스트를 수행시 아래와 유사한 결과를 볼 수 있다.

[Parser] Running:
  C:\TestingWorks\TestNG01\temp-testng-customsuite.xml

@BeforeSuite
@BeforeTest
@BeforeClass
@BeforeMethod
Slow test
@AfterMethod
@BeforeGroups: fast
@BeforeMethod
Fast test
@AfterMethod
@AfterGroups: fast
@AfterClass
@AfterTest
PASSED: aSlowTest
PASSED: aFastTest

===============================================
    testing.testng.study02.BeforeAfterTest
    Tests run: 2, Failures: 0, Skips: 0
===============================================

@AfterSuite

===============================================
TestNG01
Total tests run: 2, Failures: 0, Skips: 0
===============================================


3. 테스트 그룹 지정을 통한 보다 유연한 테스트하기

import org.testng.annotations.Test;

@Test(groups = {"func-test"})
public class GroupTest {
	@Test(groups = { "fast" })
	public void aFastTest() {
		System.out.println("Fast test");
	}

	@Test(groups = { "slow", "broken" })
	public void aSlowTest() {
		System.out.println("Slow test");
	}
}

- 테스트 그룹은 메소드와 클래스 레벨 모두 지정할 수 있고, 클래스 레벨에서 지정된 그룹(func-test)은 해당 클래스내의 모든 테스트 메소드들이 속한 대한 그룹이 된다.
- 테스트 설정파일 testng.xml을 통해 테스트 코드가 완성되지 않은 특정 그룹에 속한 메소드는 테스트에서 제외할 수도 있다.

- 아래 예는 "fast" 그룹에 속한 메소드만을 실행하며, "broken" 그룹에 속한 메소드는 실행에서 제외한다.

<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="Simple Suite">
  <test name="Test2">
    <groups>
      <run>
        <include name="fast" />
        <exclude name="broken" />
      </run>
    </groups>
  </test>
</suite>


4. 테스트 메소드 및 그룹간 종속성 지정을 통한 단위테스트 넘어서기

import org.testng.annotations.Test;

public class MethodDependencyTest {
	@Test
	public void serverStartedOk() {
		System.out.println("serverStartedOk");
	}

	@Test(dependsOnMethods = { "serverStartedOk" })
	public void testMethod1() {
		System.out.println("testMethod1");
	}
}

- 테스트 메소드간 종속성을 지정하여 테스트 메소드간의 실행 순서를 지정할 수 있다.

import org.testng.annotations.Test;

public class GroupDependencyTest {
	@Test(groups = { "init" })
	public void serverStartedOk() {
		System.out.println("serverStartedOk");
	}

	@Test(groups = { "init" })
	public void initEnvironment() {
		System.out.println("initEnvironment");
	}

	@Test(dependsOnGroups = { "init.*" })
	public void testMethod1() {
		System.out.println("testMethod1");
	}
}

- 테스트 그룹간 종속성을 지정하여 해당 그룹에 지정된 테스트의 실행 순서를 지정할 수 있다.
- 그룹명 및 메소드명 지정시 정규표현식을 이용할 수 있다. 위 예에서 init.*는 init로 시작하는 모든 그룹을 의미한다.

5. 예외처리 테스트하기 

import org.testng.annotations.Test;

public class ExceptionTest {
	@Test(expectedExceptions = { ArithmeticException.class })
	public void divisionByZero() {
		int n = 2 / 0;
		System.out.println(n);
	}

	@Test(expectedExceptions = { NumberFormatException.class })
	public void parseInteger() {
		int n = Integer.parseInt("two");
		System.out.println(n);
	}
}

- 기대되어지는 예외 클래스와 동일한 예외가 발생되면 성공으로 간주한다. 예외 클래스는 1개이상 지정할 수 있다.

6. 타임아웃 테스트하기 

import java.util.Random;
import org.testng.annotations.Test;

public class TimeOutTest {
	@Test(timeOut = 2000)
	public void testServer() throws Exception {
		Random rand = new Random();
		long responseTime = rand.nextInt(3000);
		System.out.println(responseTime);
		Thread.sleep(responseTime);
	}
}

- 지정된 시간안에 정상적으로 처리되면 성공이고, 그렇지 않으면 실패로 간주된다. 

7. 지정된 회수만큼 반복 테스트 수행하기 

import org.testng.annotations.Test;

public class RepeatedTest {
	@Test(invocationCount = 5)
	public void testServer() {
		System.out.println("accessPage");
	}
}

- 지정된 회수만큼 반복하여 테스트를 수행한다. 

8. 지정된 쓰레드 개수만큼 테스트를 병렬로 수행하기 

import java.util.Random;

import org.testng.annotations.Test;

public class ParallelRunningTest {
	@Test(threadPoolSize = 5, invocationCount = 10, timeOut = 2000)
	public void testServer() throws Exception {
		System.out.println(Thread.currentThread().getId());

		Random rand = new Random();
		long responseTime = rand.nextInt(3000);
		System.out.println(responseTime);
		Thread.sleep(responseTime);		
	}
}

- 테스트 반복 수행시 테스트 수행속도 향상을 위해 지정된 쓰레드 개수만큼 병렬로 테스트를 수행한다. 

9. 파라미터를 이용한 테스트 수행하기 

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class ParameterTest {
	@Parameters( { "first-name" })
	@Test
	public void testSingleString(String firstName) {
		System.out.println("Invoked testString " + firstName);
		assert "Sehwan".equals(firstName);
	}
}

- @Parameters 어노테이션으로 지정된 파라미터들에 대해서 testng.xml 파일로부터 값을 얻어와 테스트를 수행할 수 있다.

- testng.xml 의 파라미터 설정 예는 아래와 같다.

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="TestNG01">
  <test name="Test1">
    <parameter name="first-name" value="Sehwan" />
    <classes>
      <class name="testing.testng.ParameterTest" />
    </classes>
  </test>  
</suite>


10. DataProvider를 이용한 Data-Driven 테스트 수행하기 

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataProviderTest {
	@Test(dataProvider = "dp")
	public void verifyData1(String n1, Integer n2) {
		System.out.println(n1 + " " + n2);
	}

	@DataProvider(name = "dp")
	public Object[][] createData() {
		return new Object[][] { { "John", new Integer(34) },
				{ "Jane", new Integer(31) }, };
	}
}

- @DataProvider을 통해 2차원 배열형태로 데이터를 제공자하는 메소드를 정의한다.
- 테스트 메소드는 이 Data Provider를 이용하여 미리 정의된 데이터 집합을 가지고 반복적인 테스트를 수행할 수 있다.

import java.lang.reflect.Method;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataProviderParamTest {
	@Test(dataProvider = "dp")
	public void testData1(String s) {
		System.out.println(s);
	}

	@Test(dataProvider = "dp")
	public void testData2(String s) {
		System.out.println(s);
	}

	@DataProvider(name = "dp")
	public Object[][] createData(Method m) {
		System.out.println(m.getName());
		return new Object[][] { { "Hello" } };
	}
}

- Data Provider를 이용하는 테스트 메소드들에 대한 참조를 할 수 있다.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataProviderIteratorTest {

	@Test(dataProvider = "dp")
	public void verifyData1(String n1, Integer n2) {
		System.out.println(n1 + " " + n2);
	}

	@DataProvider(name = "dp")
	public Iterator<Object[]> createData() {		
		List<Object[]> list = new ArrayList<Object[]>();
		list.add(new Object[] {"John", new Integer(34)});
		list.add(new Object[] {"Jane", new Integer(31)});
		return list.iterator();
	}
}

- 이차원 배열이 아닌 Iterator 타입의 데이터 집합을 반환하는 Data Provider를 만들 수 있다.
- 이 방식은 Lazy Loading을 통해서 메모리 부족에 대한 좋은 해결책을 제공할 수 있다.

import org.testng.annotations.Test;

public class StaticDataProviderTest {
	@Test(dataProvider = "dp", dataProviderClass = StaticDataProvider.class)
	public void test(Integer n) {
		System.out.println("Number:" + n);
	}
}

- Data Provider를 테스트 클래스 밖으로 꺼내어 별도의 클래스로 만들어서 이용할 수 있다.
- 동일한 Data Provider를 이용하는 테스트 클래스가 여럿일 경우 유용하게 쓰일 수 있다.

import org.testng.annotations.DataProvider;

public class StaticDataProvider {
	@DataProvider(name = "dp")
	public static Object[][] createData() {
		return new Object[][] { new Object[] { new Integer(42) } };
	}
}