-
불안정(Flakey) 테스트와 예방법테스팅 관련/테스트 2020. 8. 20. 16:49
불안정(Flakey) 테스트
어떤 결과가 나올지 알 수 없는 테스트 (때에 따라 성공/실패)무엇이 테스트를 불안정하게 만들까?
- 동시실행(Concurrency)
- 멀티 스레드/프로세스가 동시에 작동할 때, 순서나 작동시간이 때에 따라 달라질 수 있다.
- 시간
- 테스트 성공/실패 시간을 정하면, 시스템 환경에 따라 성공/실패 유무가 정해진다.
- 환경 변화
- 다른 시스템 환경, 파일, 데이터베이스 등이 원인이 될 수 있다.
멀티 스레드
1개의 응용 프로그램이 스레드(thread)로 불리는 처리 단위를 복수 생성하여 복수의 처리를 병행하는 것. 즉, 응용 프로그램 내에서의 다중 작업(multitasking) 처리를 말한다.public class Multithread { class Runner implements Runnable { StringBuffer log; String id; public Runner(String id, StringBuffer log) { this.id = id; this.log = log; } void appendLog() { synchronized(log) { log.append(id); } } public void someComputation() { Thread.yield(); } @Override public void run() { for (int i=0; i<10; i++) { someComputation(); appendLog(); } } } public String runThreads() { StringBuffer buff = new StringBuffer(); Runner a = new Runner("a", buff); Runner b = new Runner("b", buff); Thread t1 = new Thread(a); Thread t2 = new Thread(b); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { } return buff.toString(); } }
동시실행
동시실행은 코드와 테스트에 미묘한 장애(failure)들을 발생시킨다
- 멀티 스레드
- 멀티 프로세스
멀티 스레드의 결과는 이러한 상황에 발생된다:
- 다른 순서
- 다른 시간(timing)
- .... 그리고 스레드가 독립적이지 않다면, 다른 결과를 야기시킬 수 있다
import static org.junit.Assert.*; public class MultithreadTest { // 불안정 테스트 @Test public void testRunThreadsFlakey() throws Exception { Multithread m = new Multithread(); String logResult = m.runThreads(); System.out.println("logResult equals: " + logResult); assertEquals(logResult, "babababababababa"); } // 연속 호출을 확인하기위해 모의객체(Mocks)를 사용할 때도 // 이 불안정함은 같은 방식으로 나타난다. // Q: 테스트 또는 프로그램 중에 어떤 것이 불안정한 것일까? // 순서가 꼭 baba로 나타나야 하는 걸까...? // 보통, 각 스레드가 몇 번 실행되었는지를 확인한다. @Test public void testRunThreads() throws Exception { Multithread m = new Multithread(); String logResult = m.runThreads(); System.out.println("logResult equals: " + logResult); char [] logArray = logResult.toCharArray(); Arrays.sort(logArray); logResult = new String(logArray); assertEquals(logResult, "aaaaaaaaaaabbbbbbbbbbb"); } // 모의객체에서도, 순서보다는 총 몇 번의 호출이 // 발생하였는지에 중점을 둘 수 있다. public long [] randomizeMemory() { int randomSize = (int)(Math.random() * 20000.0); long longArray [] = new long[randomSize + 1]; longArray[0] = 1; int randomSize1 = (int)(Math.random() * 20000.0); long longArray1 [] = new long[randomSize1 + 1]; longArray1[0] = 1; return longArray; } }
환경 변화
불안정하게 이끄는 요소들:
- 날짜/시간 변화
- 기본 OS
- GUI 프레임워크
- 사용 가능 메모리 (시간이 오래 걸릴 수도 있음)
- 프로세서 속도
- 네트워크 속도
- 디스크 속도
- JVM 버전
- 메모리 객체의 위치 (포인터)
해시코드의 기본 구현은 객체의 포인터 위치를 사용한다 = 해시코드는 (다른 플랫폼) 또는 (같은 플랫폼의 다른 실행)에 따라 불안정하다
// 환경 변화 // [데이터 구조에 따른 불안정함] // 포인터를 조심히 사용하지 않으면, 불안정함을 발생시킬 수 있다 // 이 테스트는 하나의 플랫폼/VM 구성에서 통과한다 // 하지만, 다른 플랫폼들에서는 fail한다 @Test public void flakeyTestAddToSet() throws Exception { long [] randomarray = randomizeMemory(); ClassWithoutHashcode c1 = new ClassWithoutHashcode("a"); ClassWithoutHashcode c2 = new ClassWithoutHashcode("c"); Set<ClassWithoutHashcode> aSet = new HashSet<>(); aSet.add(c1); aSet.add(c2); classWithoutHashcode [] result = {c1, c2}; System.out.println("C1 hash code: " + c1.hashCode()); System.out.println("C2 hash code: " + c2.hashCode()); assertThat(aSet.toArray(), equalTo(result)); }
불안정 테스트를 피해야하는 이유
- 불안정한 이유를 찾아내야한다 (개발자들은 코드가 올바르게 작동 하는지/안 하는지 알지 못 한다)
- 소프트웨어 또는 테스트가 튼튼하기를 바란다
- '테스트의 문제'가 아니라 '프로그램의 문제'를 보여주는 테스트를 작성하는게 테스터의 목표다
'테스팅 관련 > 테스트' 카테고리의 다른 글
여러 테스트 프레임워크를 같이 사용할 수 있을까? (0) 2021.02.18 API 테스트 (0) 2020.09.10 JaCoCo로 적합성과 코드 커버리지 분석 평가 (0) 2020.08.14 리스크 기반 테스트 (0) 2020.06.22 변이테스팅 (Mutation Testing) (0) 2020.05.28 - 동시실행(Concurrency)