문자열 순열

.NET 2008. 11. 10. 13:09

Test Run
문자열 순열
Dr. James McCaffrey

코드 다운로드 위치: TestRun2006_12.exe (161 KB)
Browse the Code Online
문자열 순열을 프로그래밍 방식으로 만들고 사용하는 능력은 소프트웨어 테스트를 위한 필수적인 기술입니다. 문자열 순열이란 문자열 집합을 재정렬해 놓은 것을 말합니다. 예를 들어 apple, banana, cherry라는 3개의 문자열 집합이 있을 경우 순열의 총 수는 6개가 됩니다.
{ "apple", "banana", "cherry" }
{ "apple", "cherry", "banana" }
{ "banana", "apple", "cherry" }
{ "banana", "cherry", "apple" }
{ "cherry", "apple", "banana" }
{ "cherry", "banana", "apple" }
소프트웨어 테스트에서 순열은 주로 단위, API, 모듈 테스트를 위한 테스트 사례 데이터를 생성하는 데 사용됩니다. 3개의 문자열을 받는 메서드가 있고, 테스트 사례 입력 중 하나가 "apple", "banana", "cherry"라고 가정해 보겠습니다. 대부분의 경우 다른 입력 순열을 사용하여 5개의 테스트 사례를 추가하고자 할 것입니다. 소프트웨어 테스트에서 순열의 용도는 이외에도 다양합니다. 실제로 순열은 소프트웨어 엔지니어링에서 매우 중요하고 널리 사용되고 있으며 Microsoft에서 테스트 담당자 면접 시 자주 묻는 질문 중 하나입니다.
스크린샷을 통해 이번 달 칼럼에서 설명할 내용을 미리 파악하십시오. 그림 1은 데모 프로그램을 실행한 모습입니다이 데모 프로그램을 생성하는 데 사용되는 전체 소스 코드는 이 칼럼에서 제공하는 다운로드에 포함되어 있습니다. 그림 1의 결과를 통해 짐작할 수 있듯이, 문자열 순열에 관해 알아야 할 세 가지 기본적인 기술은 지정된 문자열 집합으로 만들 수 있는 순열 수를 정확히 계산하는 것, 모든 순열을 만드는 것, 그리고 지정된 순열을 만드는 것입니다.
그림 1 문자열 조합의 예 (더 크게 보려면 이미지를 클릭하십시오.)
순열의 용어는 문제 영역에 따라 다양하게 사용되므로 이 칼럼에서 사용할 몇 가지 핵심 용어를 설명하겠습니다. 순열 요소란 초기 집합을 사용하여 만든 정렬된 문자열 집합을 말합니다. 원소란 순열 요소 안에 있는 각각의 모든 문자열을 의미합니다. 순열의 차수는 요소 안에 있는 원소의 개수를 의미합니다. 이 칼럼에서는 사전식 순열을 사용하겠습니다. 따라서 초기 순열 요소(ID 요소라고도 함) 내의 원소는 사전순으로 나열될 것입니다. 또한 각 순열 요소는 서로 관련성을 갖고 나열됩니다. 즉, 고유한 ID 순열 요소와 마지막 요소가 있으며 선행 요소가 없는 첫 번째 요소와 후속 요소가 없는 마지막 요소를 제외하면 모든 요소에는 선행 요소와 후속 요소가 있습니다. 마지막으로 문자열 순열이란 원소가 문자열인 순열 요소를 의미합니다. 이러한 용어를 미리 정의하는 이유는 수학적 순열의 경우 차수가 n인 순열에는 { 0, 1, 2 }와 같이 0에서 n-1까지의 정수 원소가 있기 때문입니다. 차수가 n인 순열의 총 순열 요소 수는 n!이며 이를 "n의 계승"이라 읽습니다. 예를 들어 원소가 4개일 경우, 총 순열 요소 수는 다음과 같습니다.
  4! = 4 * 3 * 2 * 1 = 24
왜 이와 같은 결과가 나오는지 쉽게 이해할 수 있을 것입니다. 요소의 첫 번째 원소는 n 개의 원소 중에서 임의로 선택할 수 있습니다. 그 다음 원소는 n-1개의 남은 원소 중에서 임의로 선택할 수 있습니다. 계속 이런 식으로 반복됩니다.
순열은 조합과 밀접한 관련이 있으며 때로 혼동되기도 합니다. 문자열 조합이란 초기 문자열 집합의 하위 집합이며 특정한 순서대로 나열되지 않습니다. 예를 들어 초기 문자열 집합이 apple, banana, cherry로 구성되어 있을 경우 하위 집합 크기 k가 1인 조합 요소의 수는 3개입니다.
  { "apple" }, { "banana" }, {"cherry" }
하위 집합 크기 k가 2인 조합 요소의 수는 3개입니다.
{ "apple", "banana" }
{ "apple", "cherry" }
{ "banana", "cherry" }
하위 집합 크기 k가 3인 조합 요소 수는 1개입니다.
{ "apple", "banana", "cherry" }
2004년 7월호 MSDN®Magazine에 조합에 대한 칼럼인 "Using Combinations to Improve Your Software Test Case Generation"(영문)이 있으므로 참고하시기 바랍니다.
이 칼럼의 나머지 부분에서는 기본 생성자를 비롯하여 문자열 순열 클래스의 전반적인 설계 방식을 설명하고, 간결한 알고리즘을 사용하여 지정한 순열 요소의 후속 요소를 생성하는 방식으로 모든 가능한 순열 요소를 생성하는 방법에 대해 설명할 것입니다. 그런 다음, 필자가 알고 있는 여러 가지 뛰어난 알고리즘 중 하나를 사용하여 특정 순열 요소를 생성하는 방법을 알려드릴 것입니다. 또한 초기 문자열 집합을 사용하여 만들 수 있는 순열 수를 계산하는 방법을 설명하고, 여기에 나온 코드와 알고리즘을 여러분이 원하는 방향으로 적용 및 확장할 수 있는 방법을 간단한 설명하면서 이 칼럼을 마칠 것입니다. 이 칼럼을 읽고 나면 지금까지 익혀온 여러분의 기술 지식에 새롭고 유용한 몇 가지 도구에 대한 내용을 더할 수 있을 것이라 확신합니다.

문자열 순열 클래스
문자열 순열은 서로 밀접한 관련이 있는 데이터(문자열 원소)와 코드를 포함하므로 개체 지향 방식을 사용하여 구현하기에 완벽한 구조입니다. 클래스 라이브러리의 전체 구조는 그림 2에 나와 있습니다.
필자는 StringPerm이라는 이름의 클래스를 만들었습니다. 물론 이 이름은 여러분이 다른 것으로 변경할 수 있습니다. StringPerm 클래스에는 두 개의 private 필드가 있습니다. 문자열 배열은 각 문자열 순열 요소의 개별 원소 문자열을 담을 요소의 이름을 지정하고, 정수 필드는 순열 클래스의 차수를 저장합니다. 여기에서는 기본 생성자를 구현하는 대신에 문자열 배열을 단일 입력 매개 변수로 취하여 ID 순열 요소를 생성하는 생성자를 구현하였습니다.
public StringPerm(string[] atoms)
{
  if (atoms == null) throw new ArgumentNullException("atoms");
  if (!IsValid(atoms)) throw new ArgumentException("atoms");
  this.element = new string[atoms.Length];
  atoms.CopyTo(this.element, 0);
  this.order = atoms.Length;
}
간단히 클래스 배열 필드에 필요한 메모리를 할당하고 Array.CopyTo 메서드를 사용하여 각각의 값을 할당한 다음, 입력 인수의 Length 속성을 사용하여 차수 필드에 값을 할당했습니다. 이는 명확하고 간단한 방식이므로 클래스를 효율적으로 설계할 수 있습니다. 호출되는 클래스 생성자의 코드는 다음과 같습니다.
string[] atoms = new string[] { "ant", "bat", "cow", "dog" };
StringPerm p = new StringPerm(atoms);
여기에서는 사전식 순열 클래스를 사용하므로, 생성자는 정렬된 상태의 문자열 배열을 받아야 합니다. 이를 위해 생성자에서 호출하는 클래스에 정적 메서드인 IsValid를 추가합니다. IsValid 메서드의 내용은 다음과 같습니다.
public static bool IsValid(string[] e)
{
  if (e == null) return false;
  if (e.Length < 2) return false;
  for (int i = 0; i < e.Length - 1; ++i)
  {
    if (e[i].CompareTo(e[i + 1]) >= 0) return false;
  }
  return true;
}
이 메서드는 문자열 배열을 받아 이 배열이 StringPerm 생성자의 입력 인수로 유효한 경우 true를 반환하고 그렇지 않을 경우 false를 반환합니다. 이때 입력 배열은 null이 아니고 최소한 2개의 셀을 갖고 있어야 하므로 이를 확인하기 위한 코드도 추가했습니다. 필요하다면 차수가 1인 경우에 대한 문자열 순열도 모두 생성하도록 이 코드를 수정할 수 있습니다. 다음은 String.CompareTo 메서드를 사용하여 입력 배열의 각 문자열 원소가 이웃 원소보다 작은지, 즉 문자열 원소가 순서대로 정렬된 상태인지 확인합니다. 원소는 중복될 수 없도록 했습니다. 여기에서는 역방향 로직을 사용하며 인덱스 위치 i의 문자열 원소가 인덱스 i+1의 원소와 같거나 클 경우 false를 반환합니다. 아래 배열에서처럼 중복 원소를 포함시킬 수도 있습니다.
{ "ant", "bat",
 "bat", "cow" }
중복 원소를 포함하려면 >= 연산자를 >로 바꾸면 됩니다.
이때 다음과 같은 ToString 도우미 메서드를 사용하면 편리합니다.
public override string ToString()
{
  StringBuilder result = new StringBuilder();
  result.Append("{ ");
  for (int i = 0; i < this.order; ++i)
  {
    result.Append(this.element[i]);
    result.Append(" ");
  }
  result.Append("}");
  return result;
}
이 코드는 각 문자열 원소를 공백으로 구분하여 연결하고 전체 목록을 중괄호로 묶습니다.

차수 n인 모든 순열 만들기
모든 문자열 순열을 생성하는 한 가지 방법은 사전순으로 따졌을 때 지정된 요소 다음에 해당하는 문자열 순열 요소를 반환하는 Successor 메서드를 작성하는 것입니다. 이는 꽤 흥미로운 도전이며 훌륭한 해결 방법이 있습니다. 간단히 설명하자면 하나의 순열 요소에 대한 후속 요소를 찾는 이 알고리즘은 두 원소 위치를 찾고 이 두 요소를 서로 바꾼 다음 오른쪽 나머지 원소를 역순으로 다시 정렬합니다. 이 말의 의미를 이해하려면 예를 드는 것이 가장 좋은 방법일 것입니다. 이해를 돕기 위해 숫자로 문자열 순열을 표현해 보겠습니다. 현재 순열 요소는 다음과 같습니다.
{ "1" "3" "5" "4" "2" "0" }
후속 요소는 다음과 같습니다.
{ "1" "4" "0" "2" "3" "5" }
먼저 서로 교환할 두 개의 원소를 가리키는 왼쪽과 오른쪽 인덱스를 찾습니다. 왼쪽 인덱스를 찾으려면 마지막 원소 옆(여기서는 2)에서부터 시작하고, 왼쪽의 원소가 현재 원소보다 작을 때까지 계속 왼쪽으로 이동합니다. 이 예의 경우 5보다 적은 원소 3까지 이동합니다. 따라서 왼쪽 인덱스는 [1]이 됩니다. 오른쪽 인덱스를 찾으려면 오른쪽에 있는 가장 마지막 원소(여기서는 0)부터 시작하여 왼쪽 인덱스의 원소보다 큰 원소를 찾을 때까지 계속 오른쪽으로 이동합니다. 여기에서는 원소 4까지 이동하며 오른쪽 인덱스는 [3]이 됩니다. 이제 왼쪽과 오른쪽 인덱스([1]과 [3])에 있는 원소를 서로 교환하면 아래와 같은 중간 결과가 나타납니다.
{ "1" "4" "5" "3" "2" "0" }
왼쪽 인덱스 오른쪽에 있는 모든 원소, 즉 인덱스 [1]의 오른쪽에 있는 모든 원소인 5, 3, 2, 0을 역순으로 바꾸면 최종 후속 요소는 다음과 같습니다.
{ "1" "4" "0" "2" "3" "5" }
이 알고리즘을 구현하기 위한 코드는 그림 3에 나와 있습니다.
이 Successor 메서드는 순열에 중복된 원소가 없을 경우에만 사용 가능합니다. Successor 메서드에서 약간 까다로운 문제는 마지막 순열 요소일 경우 어떻게 처리하느냐 하는 것입니다. 예를 들어 아래 요소의 경우
{ "ant", "bat", "cow", "dog" }
마지막 순열 요소는 아래와 같습니다.
{ "dog", "cow", "bat", "ant" }
마지막 순열 요소를 확인하는 방법은 여러 가지가 있는데, 여기에서는 Successor 코드에서 왼쪽 인덱스 값을 정할 때, 왼쪽 인덱스 값이 0이고 [0]에 있는 원소가 [1]의 원소보다 클 경우 이 순열 요소는 가장 마지막 순열 요소라고 판단하는 방식을 사용합니다. 이 때 코드는 null을 반환합니다. null을 반환하는 방법 대신 첫 번째 요소인 ID 요소를 반환하여 첫 번째 순열 요소까지 래핑하거나 예외를 발생시키는 방법도 사용할 수 있습니다.
이 Successor 메서드를 사용하면 다음과 같이 중복되는 원소가 없다는 전제 하에 어떠한 순열 요소에 대해서도 손쉽게 후속 순열 요소를 생성하거나 모든 순열 요소를 생성할 수 있습니다.
string[] atoms = new string[] { "ant", "bat", "cow", "dog" };
Console.WriteLine("In lexicographical order, all permutations are:\n");
for(StringPerm p = new StringPerm(atoms); p != null; p = p.Successor())
{
 Console.WriteLine(p.ToString());
}
이 알고리즘의 부분적인 문제는 중복되는 문자열 원소에 대한 것입니다. 이미 언급했듯이 이 코드에서는 중복 원소를 허용하지 않았습니다. 순열 요소에 중복되는 원소를 포함하고 싶을 경우, 먼저 위에서 설명한 IsValid 도우미 메서드를 수정해야 합니다. 그런 다음 중복되는 원소를 포함해 모든 순열 요소를 생성하려면 다음과 같이 다른 방식을 사용해야 합니다.

특정 순열 요소 결정하기
여기에서는 특정한 문자열 순열 요소 하나만 생성한다고 가정하겠습니다. 예를 들어 문자열이 "ant", "bat", "cow", "dog"이고 [13]번째 요소만 필요할 경우 결과는 다음과 같습니다.
{ "cow" "ant" "dog" "bat" }
처음에 필자는 ID 순열 요소부터 시작하여 위에서 설명한 Successor 메서드를 13번 호출하면 간단히 해결할 수 있다고 생각했습니다. 하지만, 문자열 원소 수가 4개가 아니라 20개라면, 순열 요소가 2,432,902,008,176,640,000개나 됩니다! 원하는 요소가 [999,999,999,999]라고 가정하면, 필자의 데스크톱 컴퓨터에서 반복 구문을 사용하여 이를 계산할 경우 요소를 생성하는데 하루도 넘게 걸릴 것입니다. 또한 중복되는 문자열 원소를 포함할 경우 루핑 기법을 통해 모든 순열 요소를 생성하는 것은 불가능합니다. 하지만 중복되는 원소를 포함하면서도 1초만에 컴퓨터가 계산 결과를 보여주는 뛰어난 알고리즘이 있습니다.
특정 순열 요소를 직접 계산해내는 이 탁월한 알고리즘은 factoradic이라 불리는 효과적인 수치 생성법입니다. Factoradic은 정수를 나타내는 용어로, 이 알고리즘의 원리는 원하는 인덱스 값의 factoradic을 계산한 다음, 해당 factoradic을 순열 요소로 바꾸는 것입니다.
자세한 내용을 설명하기에 앞서 factoradic에 대해 알아보겠습니다. 859라는 숫자가 있다고 가정합니다. 차수 n을 7로 정할 경우 숫자 859는 다음과 같이 나타낼 수 있습니다.
(6! * 1) + (5! * 1) + (4! * 0) + 
(3! * 3) + (2! * 0) + (1! * 1) + (0! * 0)

= 720 + 120 + 0 + 18 + 0 + 1

= 859
이때 859의 factoradic은 [1 1 0 3 0 1 0]입니다. 어떠한 정수라도 특정한 순서로 된 고유의 factoradic으로 표현할 수 있습니다. factoradic이 유용한 이유는 factoradic과 순열 요소간에 1:1 매핑을 할 수 있다는 점입니다. 예를 들어 차수가 4인 순열을 작업할 경우 그림 4의 표에서 정수와 해당 factoradic 및 해당 수치 순열 간의 관계를 확인할 수 있습니다.
아래와 같이 예를 들어 보겠습니다. 네 개의 문자열 원소인 "ant", "bat", "cow", "dog"에 대해 [13]번째 순열을 직접 계산한다고 가정해 보면, 먼저 13의 factoradic인 [2 0 1 0]을 구하고 이를 수치 순열인 { 2 0 3 1 }에 매핑한 다음, 다시 초기 문자열 원소에 매핑하면 됩니다.
{ "cow", "ant", "dog", "bat" }
factoradic이 본 칼럼의 주제는 아니므로 여기에서는 이 정도만 하겠습니다. factoradic에 대한 자세한 내용은 필자의 저서인 NET Test Automation Recipes(Apress, 2006)를 참조하거나 온라인에서 확인할 수 있습니다.
필자는 특정 순열 요소를 찾는 서브루틴을 보조 생성자로 구현하였습니다. 코드는 그림 5에 나와 있습니다. 그림 5의 생성자를 사용하면 다음과 같이 호출할 수 있습니다.
string[] atoms = new string[] { "ant", "bat", "cow", "dog" };
Console.WriteLine("\nJust element [13] computed directly is:");
p = new StringPerm(atoms, 13);
Console.WriteLine("   " + p.ToString());
이 생성자를 사용하면 원소의 중복 여부와 관계없이 모든 순열 요소를 생성할 수 있습니다. 예를 들어 다음 코드를 살펴보겠습니다.
string[] atoms = new string[] { "ant", "bat", "cow", "cow" };
StringPerm p = null;
Console.WriteLine("All permutations are:\n");
for (int k = 0; (ulong)k < StringPerm.FactorialLookup(4); ++k)
{
 p = new StringPerm(atoms, k);
 Console.WriteLine(k + " " + p.ToString());
}
이 방법을 사용하면 중복되는 순열 요소를 생성하게 됩니다. 중복 요소를 제외하고 고유의 요소만 원할 경우 여러 가지 다른 방법을 사용할 수 있습니다. 한 가지 예로 모든 요소를 해시 테이블에 로드하여 각 추가 작업을 하기 전에 요소가 중복되어 있는지 확인하는 방법을 들 수 있습니다.

차수 n인 순열 요소 수
지정된 문자열 집합에 대한 총 문자열 순열 요소 수를 계산하는 방법은 쉬우면서도 어렵습니다. 문자열이 "ant", "bat", "cow", "dog"와 같이 4개가 있어 차수 n = 4라고 가정해 보면, 그림 1과 같이 총 순열 요소 수는 24개입니다. 모든 요소는 처음(맨 왼쪽) 위치에 4개의 문자열 원소 중 하나를 가질 수 있으며 그 다음 위치에서는 남은 3개의 원소, 그 다음 위치는 남은 2개의 원소, 그리고 마지막 위치에는 남은 하나의 원소를 가질 수 있습니다. 즉 n = 4일 경우, 순열 요소 수는 4 * 3 * 2 * 1 = 24개입니다. n의 계승(또는 n!) 공식이 적용됨을 눈치채셨을 것입니다. 따라서 차수가 n인 순열의 총 요소 수는 n의 계승과 같습니다. 컴퓨터 공학 문제와 비슷하다고 느끼십니까? 꼭 그렇지는 않습니다. 이제 계승 메서드를 3가지 방법으로 구현해 보겠습니다.
첫 번째는 최근에 대학원생들이 많이 쓰는 것으로, 재귀 방식을 사용하여 계승 메서드를 작성합니다. 예를 들면 다음과 같습니다.
public static ulong FactorialRecursive(int n)
{
 if (n < 0) throw new ArgumentOutOfRangeException("n");
 if (n == 0 || n == 1) return 1;
 else return (ulong)n * FactorialRecursive(n - 1);
}
이 방법은 대부분의 경우 그다지 유용하지 못하며 잘못된 결과가 얻어질 수 있으므로 주의해야 합니다. n!의 값은 순식간에 커질 수 있습니다. 예를 들어 64!의 값은 대략 다음과 같습니다.
126,886,932,100,000,000,000,000,000,000,000,000,000,
000,000,000,000,000,000,000,000,000,000,000,000,000
또한 ulong 유형의 변수로 저장할 수 있는 최대값이 20!이라는 점도 문제입니다.
20 * 19 * 18 *. .. * 2 * 1 = 2,432,902,008,176,640,000
프로그램에서 다음과 같이 호출하면 어떤 결과가 나타날까요?
ulong result = FactorialRecursive(21)
일종의 산술 오버플로가 발생할 것이라고 생각하겠지만 기본적으로는 오버플로가 발생하지 않습니다. 컴파일러가 ulong.MaxValue인 18,446,744,073,709,551,615에 도달하면 다시 0으로 돌아가 아무런 경고 없이 계속 연산을 수행하게 됩니다. CLR(공용 언어 런타임)은 수치 연산에 대한 오버플로 및 언더플로 검사가 가능하며 사용자가 이 기능을 컴파일 타임에 사용할 수 있습니다. 무엇보다도 중요한 것은, 재귀적 해결법을 꼭 사용해야 할 어떠한 이유도 없다는 것입니다. 두 번째 계승 메서드 작성 방법은 다음과 같이 결과를 반복적으로 계산하는 것입니다.
public static ulong FactorialCompute(int n)
{
 if (n < 0 || n > 20)
   throw new Exception("Input argument must be between 0 and 20");
 ulong answer = 1;
 for (int i = 1; i <= n; ++i) answer = checked(answer * (ulong)i);
 return answer;
}
이 방법은 대부분의 경우에서 훨씬 더 효과적입니다. 메서드는 입력 매개 변수를 검사하고, C#의 checked라는 키워드를 사용하여 산술 오버플로가 발생하면 런타임 예외를 발생시킵니다. 입력 인수 집합이 0 ~ 20 사이로 작기 때문에, 다음과 같이 21개의 가능한 결과를 조회 테이블에 저장하는 것도 가능합니다.
public static ulong FactorialLookup(int n)
{
 if (n < 0 || n > 20)
  throw new Exception("Input argument must be between 0 and 20");
 ulong[] answers = new ulong[] { 1, 1, 2, 6, 24, 120, 720, 5040, 40320,
  362880, 3628800, 39916800, 479001600, 6227020800, 87178291200,
  1307674368000, 20922789888000, 355687428096000, 6402373705728000,
  121645100408832000, 2432902008176640000 };
 return answers[n];
}
대부분의 소프트웨어 테스트 시나리오에서 가장 효율적인 것은 조회 방식입니다. 물론 여기에서 설명한 여러 계승 메서드 실행 방법은 흥미로운 서브루틴의 세계를 살짝 맛본 정도에 지나지 않습니다.

요약
문자열 순열은 다양한 용도로 사용할 수 있습니다. 예를 들어 어떤 소프트웨어 설치 프로그램을 테스트할 때, 3개의 관련된 추가 프로그램을 설치하는 순서에 따라 어떠한 결과가 발생하는지 보고 싶다고 가정해 보겠습니다. 소프트웨어 프로그램을 "A", 나머지 3개의 프로그램을 각각 "B", "C", "D"라고 하면, 이 칼럼에서 설명한 기법을 사용하여 설치 순서가 모두 24가지임을 확인하고 각각의 순열을 만들 수 있습니다. 하지만 관련 프로그램이 19개라면 설치 순서가 20!가지나 되므로 이를 모조리 테스트하는 것은 불가능하며 중요한 것부터 우선적으로 처리할 수 밖에 없습니다.
간단하게 정리하면 문자열 순열이란 문자열 집합을 다시 정렬해 놓은 것입니다. 소프트웨어 테스터는 기본적으로 문자열 순열을 이해하고 사용할 줄 알아야 합니다. 특히 모든 테스터는 지정된 초기 문자열 집합에 대한 순열 요소 수를 계산하고, 가능한 모든 순열 요소를 만들고, 지정된 문자열 요소를 효율적으로 생성할 수 있어야 합니다. 이번 달 칼럼에서는 이러한 작업을 수행하는 방법에 대해 알아보았습니다. 이 기사의 소스 코드는 그대로 사용하거나 여러분이 필요한 조건에 따라 수정해서 사용할 수 있습니다.

James에게 질문이나 의견이 있으면 다음 전자 메일 주소로 보내시기 바랍니다: testrun@microsoft.com.


Dr. James McCaffrey는 Volt Information Sciences Inc.에서 Microsoft 소프트웨어 엔지니어의 기술 교육을 관리하고 있습니다. 그는 Internet Explorer 및 MSN Search를 비롯한 여러 Microsoft 제품에 대한 업무 경험을 갖고 있으며 .NET Test Automation Recipes(Apress, 2006)를 집필하였습니다. James에게 궁금한 점이 있으면 jmccaffrey@volt.com이나 v-jammc@microsoft.com으로 전자 메일을 보내십시오.

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.

출처: MSDL 2006년 12월판

AND