717 lines
20 KiB
Markdown
717 lines
20 KiB
Markdown
|
|
# 🧪 Unit Test 구현 계획 - 2025-09-23 개정판
|
||
|
|
|
||
|
|
**✅ 전제 조건**: MAJOR_REFACTORING_GUIDE 완료, GPU 파이프라인 재설계 완료
|
||
|
|
**🎯 현재 목표**: 단순화된 아키텍처 (734줄)에 대한 포괄적 Unit Test 구현
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 **현재 상황 (2025-09-23)**
|
||
|
|
|
||
|
|
### **✅ 완료된 작업**
|
||
|
|
- **MAJOR_REFACTORING_GUIDE**: 복잡한 파이프라인 제거, ProcessSingleFrame() 25줄 단순화
|
||
|
|
- **GPU 파이프라인**: SimpleGPURenderer 구현, CPU-GPU 하이브리드 아키텍처
|
||
|
|
- **기본 Unit Test**: VideoTypesTest.cpp 완료
|
||
|
|
- **헤드리스 프로젝트**: 빌드 및 실행 성공
|
||
|
|
|
||
|
|
### **📋 현재 Unit Test 구조**
|
||
|
|
```
|
||
|
|
D:\Project\video-av1\vav2\Vav2Player\Vav2Player\
|
||
|
|
├── Vav2UnitTest.vcxproj # ✅ 기존 테스트 프로젝트
|
||
|
|
├── unit-test/
|
||
|
|
│ ├── pch.h / pch.cpp # ✅ 테스트 전용 PCH
|
||
|
|
│ └── VideoTypesTest.cpp # ✅ 기본 데이터 구조체 테스트
|
||
|
|
└── x64/Debug/UnitTest/ # ✅ 빌드 출력
|
||
|
|
```
|
||
|
|
|
||
|
|
### **🎯 추가 필요한 테스트**
|
||
|
|
- **WebMFileReader**: 파일 파싱 로직 (실제 구체 클래스 테스트)
|
||
|
|
- **AV1Decoder**: 디코딩 로직
|
||
|
|
- **VideoDecoderFactory**: 팩토리 패턴
|
||
|
|
- **SimpleGPURenderer**: GPU 렌더링 (스텁 모드)
|
||
|
|
- **통합 테스트**: 전체 파이프라인
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔍 **인터페이스 분석 (Unit Test 전 필수 검토)**
|
||
|
|
|
||
|
|
### **현재 인터페이스 상황**
|
||
|
|
```cpp
|
||
|
|
✅ IVideoDecoder.h // 이미 인터페이스화 완료
|
||
|
|
❌ WebMFileReader.h // 구체 클래스, 인터페이스 없음
|
||
|
|
❌ SimpleGPURenderer.h // 구체 클래스, 인터페이스 없음
|
||
|
|
✅ VideoDecoderFactory.h // 팩토리 패턴, 인터페이스 기반
|
||
|
|
```
|
||
|
|
|
||
|
|
### **🔧 Option A: 인터페이스 추가 리팩토링 (권장)**
|
||
|
|
|
||
|
|
#### **A.1 IWebMFileReader 인터페이스 생성**
|
||
|
|
```cpp
|
||
|
|
// src/FileIO/IWebMFileReader.h (신규 생성)
|
||
|
|
#pragma once
|
||
|
|
#include "../Common/VideoTypes.h"
|
||
|
|
#include <string>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
namespace Vav2Player {
|
||
|
|
|
||
|
|
class IWebMFileReader {
|
||
|
|
public:
|
||
|
|
virtual ~IWebMFileReader() = default;
|
||
|
|
|
||
|
|
// 파일 열기/닫기
|
||
|
|
virtual bool OpenFile(const std::string& file_path) = 0;
|
||
|
|
virtual void CloseFile() = 0;
|
||
|
|
virtual bool IsFileOpen() const = 0;
|
||
|
|
|
||
|
|
// 파일 및 스트림 정보
|
||
|
|
virtual const VideoMetadata& GetVideoMetadata() const = 0;
|
||
|
|
virtual std::string GetFilePath() const = 0;
|
||
|
|
|
||
|
|
// 비디오 트랙 관리
|
||
|
|
virtual std::vector<VideoTrackInfo> GetVideoTracks() const = 0;
|
||
|
|
virtual bool SelectVideoTrack(int track_number) = 0;
|
||
|
|
|
||
|
|
// 패킷 읽기
|
||
|
|
virtual bool ReadNextPacket(VideoPacket& packet) = 0;
|
||
|
|
virtual bool SeekToTime(double time_seconds) = 0;
|
||
|
|
virtual bool SeekToFrame(uint64_t frame_number) = 0;
|
||
|
|
virtual bool Reset() = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **A.2 WebMFileReader 리팩토링**
|
||
|
|
```cpp
|
||
|
|
// src/FileIO/WebMFileReader.h 수정
|
||
|
|
class WebMFileReader : public IWebMFileReader {
|
||
|
|
// 기존 구현을 virtual 메서드로 변경
|
||
|
|
// 인터페이스 상속으로 변경
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **A.3 IVideoRenderer 인터페이스 생성**
|
||
|
|
```cpp
|
||
|
|
// src/Rendering/IVideoRenderer.h (신규 생성)
|
||
|
|
#pragma once
|
||
|
|
#include "../Common/VideoTypes.h"
|
||
|
|
#include <windows.h>
|
||
|
|
|
||
|
|
namespace Vav2Player {
|
||
|
|
|
||
|
|
class IVideoRenderer {
|
||
|
|
public:
|
||
|
|
virtual ~IVideoRenderer() = default;
|
||
|
|
|
||
|
|
// 초기화
|
||
|
|
virtual HRESULT Initialize(uint32_t width, uint32_t height) = 0;
|
||
|
|
virtual bool IsInitialized() const = 0;
|
||
|
|
virtual void Cleanup() = 0;
|
||
|
|
|
||
|
|
// 렌더링
|
||
|
|
virtual bool TryRenderFrame(const VideoFrame& frame) = 0;
|
||
|
|
virtual HRESULT RenderVideoFrame(const VideoFrame& frame) = 0;
|
||
|
|
|
||
|
|
// 설정
|
||
|
|
virtual void SetAspectFitMode(bool enabled) = 0;
|
||
|
|
virtual void UpdateContainerSize(uint32_t width, uint32_t height) = 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **🚀 Option B: 인터페이스 없이 직접 테스트 (빠른 시작)**
|
||
|
|
|
||
|
|
#### **B.1 장점**
|
||
|
|
- 즉시 테스트 작성 시작 가능
|
||
|
|
- 기존 코드 수정 없음
|
||
|
|
- 단순한 접근법
|
||
|
|
|
||
|
|
#### **B.2 단점**
|
||
|
|
- Mock 생성 어려움
|
||
|
|
- 의존성 주입 불가능
|
||
|
|
- 테스트 격리 한계
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 **단계별 구현 계획**
|
||
|
|
|
||
|
|
### **🔄 Phase 0: 인터페이스 리팩토링 (선택적)**
|
||
|
|
|
||
|
|
#### **0.1 인터페이스 필요성 평가**
|
||
|
|
```cpp
|
||
|
|
// 테스트 복잡도 vs 인터페이스 도입 비용
|
||
|
|
Component | Mock 필요도 | 인터페이스 우선순위
|
||
|
|
--------------------|----------|----------------
|
||
|
|
WebMFileReader | 높음 | ⭐⭐⭐ 권장
|
||
|
|
SimpleGPURenderer | 중간 | ⭐⭐ 선택적
|
||
|
|
VideoDecoderFactory | 낮음 | ⭐ 이미 충분
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **0.2 인터페이스 리팩토링 (선택 시)**
|
||
|
|
1. **IWebMFileReader.h 생성** (2시간)
|
||
|
|
2. **WebMFileReader.h 수정** (1시간)
|
||
|
|
3. **IVideoRenderer.h 생성** (1시간)
|
||
|
|
4. **SimpleGPURenderer.h 수정** (1시간)
|
||
|
|
5. **VideoPlayerControl.xaml.h 의존성 업데이트** (30분)
|
||
|
|
6. **빌드 테스트** (30분)
|
||
|
|
|
||
|
|
**총 예상 시간: 6시간**
|
||
|
|
|
||
|
|
### **🔧 Phase 1: 핵심 컴포넌트 테스트 (우선순위 1)**
|
||
|
|
|
||
|
|
#### **1.1 WebMFileReader 테스트 (인터페이스 기반)**
|
||
|
|
```cpp
|
||
|
|
// unit-test/WebMFileReaderTest.cpp
|
||
|
|
#include "pch.h"
|
||
|
|
#include "../src/FileIO/WebMFileReader.h"
|
||
|
|
|
||
|
|
// Option A를 선택한 경우: Mock을 사용한 테스트
|
||
|
|
#ifdef USE_INTERFACE_APPROACH
|
||
|
|
#include "../unit-test/mocks/MockWebMFileReader.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||
|
|
using namespace Vav2Player;
|
||
|
|
|
||
|
|
TEST_CLASS(WebMFileReaderTest)
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
// Option A: 인터페이스 기반 테스트 (Mock 사용 가능)
|
||
|
|
TEST_METHOD(OpenFile_ValidFile_ShouldReturnTrue_WithMock)
|
||
|
|
{
|
||
|
|
#ifdef USE_INTERFACE_APPROACH
|
||
|
|
// Arrange
|
||
|
|
MockWebMFileReader mockReader;
|
||
|
|
mockReader.SetOpenFileResult(true);
|
||
|
|
|
||
|
|
// Act
|
||
|
|
bool result = mockReader.OpenFile("test.webm");
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsTrue(result);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
// Option B: 직접 테스트 (인터페이스 없이)
|
||
|
|
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
WebMFileReader reader;
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(reader.IsFileOpen());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(OpenFile_NonExistentFile_ShouldReturnFalse)
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
WebMFileReader reader;
|
||
|
|
std::string nonExistentFile = "nonexistent_file.webm";
|
||
|
|
|
||
|
|
// Act
|
||
|
|
bool result = reader.OpenFile(nonExistentFile);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(result);
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(OpenFile_EmptyFilename_ShouldReturnFalse)
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
WebMFileReader reader;
|
||
|
|
std::string emptyFile = "";
|
||
|
|
|
||
|
|
// Act
|
||
|
|
bool result = reader.OpenFile(emptyFile);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(result);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **1.2 AV1Decoder 테스트**
|
||
|
|
```cpp
|
||
|
|
// unit-test/AV1DecoderTest.cpp
|
||
|
|
#include "pch.h"
|
||
|
|
#include "../src/Decoder/AV1Decoder.h"
|
||
|
|
|
||
|
|
TEST_CLASS(AV1DecoderTest)
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
AV1Decoder decoder;
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(decoder.IsInitialized());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(Initialize_ValidMetadata_ShouldReturnTrue)
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
AV1Decoder decoder;
|
||
|
|
VideoMetadata metadata;
|
||
|
|
metadata.width = 1920;
|
||
|
|
metadata.height = 1080;
|
||
|
|
metadata.frame_rate = 30.0;
|
||
|
|
metadata.codec_type = VideoCodecType::AV1;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
bool result = decoder.Initialize(metadata);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsTrue(result);
|
||
|
|
Assert::IsTrue(decoder.IsInitialized());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(Initialize_InvalidCodecType_ShouldReturnFalse)
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
AV1Decoder decoder;
|
||
|
|
VideoMetadata metadata;
|
||
|
|
metadata.codec_type = VideoCodecType::VP9; // Wrong codec
|
||
|
|
|
||
|
|
// Act
|
||
|
|
bool result = decoder.Initialize(metadata);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(result);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **1.3 VideoDecoderFactory 테스트**
|
||
|
|
```cpp
|
||
|
|
// unit-test/VideoDecoderFactoryTest.cpp
|
||
|
|
#include "pch.h"
|
||
|
|
#include "../src/Decoder/VideoDecoderFactory.h"
|
||
|
|
|
||
|
|
TEST_CLASS(VideoDecoderFactoryTest)
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TEST_METHOD(CreateDecoder_AV1Software_ShouldReturnAV1Decoder)
|
||
|
|
{
|
||
|
|
// Act
|
||
|
|
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||
|
|
VideoCodecType::AV1,
|
||
|
|
VideoDecoderFactory::DecoderType::SOFTWARE
|
||
|
|
);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsNotNull(decoder.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(CreateDecoder_AV1Auto_ShouldReturnDecoder)
|
||
|
|
{
|
||
|
|
// Act
|
||
|
|
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||
|
|
VideoCodecType::AV1,
|
||
|
|
VideoDecoderFactory::DecoderType::AUTO
|
||
|
|
);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsNotNull(decoder.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(IsCodecSupported_AV1_ShouldReturnTrue)
|
||
|
|
{
|
||
|
|
// Act
|
||
|
|
bool isSupported = VideoDecoderFactory::IsCodecSupported(VideoCodecType::AV1);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsTrue(isSupported);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### **🔧 Phase 1.5: Mock 시스템 구현 (Option A 선택 시)**
|
||
|
|
|
||
|
|
#### **1.5.1 MockWebMFileReader 구현**
|
||
|
|
```cpp
|
||
|
|
// unit-test/mocks/MockWebMFileReader.h
|
||
|
|
#pragma once
|
||
|
|
#include "../../src/FileIO/IWebMFileReader.h"
|
||
|
|
#include <vector>
|
||
|
|
#include <string>
|
||
|
|
|
||
|
|
namespace Vav2Player {
|
||
|
|
|
||
|
|
class MockWebMFileReader : public IWebMFileReader {
|
||
|
|
private:
|
||
|
|
bool m_openFileResult = true;
|
||
|
|
bool m_isFileOpen = false;
|
||
|
|
VideoMetadata m_metadata;
|
||
|
|
std::vector<VideoPacket> m_testPackets;
|
||
|
|
size_t m_currentPacketIndex = 0;
|
||
|
|
|
||
|
|
public:
|
||
|
|
// Mock 설정 메서드들
|
||
|
|
void SetOpenFileResult(bool result) { m_openFileResult = result; }
|
||
|
|
void SetMetadata(const VideoMetadata& metadata) { m_metadata = metadata; }
|
||
|
|
void AddTestPacket(const VideoPacket& packet) { m_testPackets.push_back(packet); }
|
||
|
|
void ResetPackets() { m_testPackets.clear(); m_currentPacketIndex = 0; }
|
||
|
|
|
||
|
|
// IWebMFileReader 구현
|
||
|
|
bool OpenFile(const std::string& file_path) override {
|
||
|
|
m_isFileOpen = m_openFileResult;
|
||
|
|
return m_openFileResult;
|
||
|
|
}
|
||
|
|
|
||
|
|
void CloseFile() override {
|
||
|
|
m_isFileOpen = false;
|
||
|
|
m_currentPacketIndex = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsFileOpen() const override {
|
||
|
|
return m_isFileOpen;
|
||
|
|
}
|
||
|
|
|
||
|
|
const VideoMetadata& GetVideoMetadata() const override {
|
||
|
|
return m_metadata;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string GetFilePath() const override {
|
||
|
|
return "mock_file.webm";
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<VideoTrackInfo> GetVideoTracks() const override {
|
||
|
|
std::vector<VideoTrackInfo> tracks;
|
||
|
|
VideoTrackInfo track;
|
||
|
|
track.track_number = 1;
|
||
|
|
track.codec_type = VideoCodecType::AV1;
|
||
|
|
track.width = 1920;
|
||
|
|
track.height = 1080;
|
||
|
|
tracks.push_back(track);
|
||
|
|
return tracks;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool SelectVideoTrack(int track_number) override {
|
||
|
|
return track_number == 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ReadNextPacket(VideoPacket& packet) override {
|
||
|
|
if (m_currentPacketIndex >= m_testPackets.size()) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
packet = m_testPackets[m_currentPacketIndex++];
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool SeekToTime(double time_seconds) override {
|
||
|
|
m_currentPacketIndex = 0;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool SeekToFrame(uint64_t frame_number) override {
|
||
|
|
m_currentPacketIndex = static_cast<size_t>(frame_number);
|
||
|
|
return m_currentPacketIndex < m_testPackets.size();
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Reset() override {
|
||
|
|
m_currentPacketIndex = 0;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### **1.5.2 MockVideoRenderer 구현**
|
||
|
|
```cpp
|
||
|
|
// unit-test/mocks/MockVideoRenderer.h
|
||
|
|
#pragma once
|
||
|
|
#include "../../src/Rendering/IVideoRenderer.h"
|
||
|
|
|
||
|
|
namespace Vav2Player {
|
||
|
|
|
||
|
|
class MockVideoRenderer : public IVideoRenderer {
|
||
|
|
private:
|
||
|
|
bool m_initialized = false;
|
||
|
|
bool m_tryRenderResult = true;
|
||
|
|
HRESULT m_renderResult = S_OK;
|
||
|
|
uint32_t m_width = 0;
|
||
|
|
uint32_t m_height = 0;
|
||
|
|
|
||
|
|
public:
|
||
|
|
// Mock 설정
|
||
|
|
void SetTryRenderResult(bool result) { m_tryRenderResult = result; }
|
||
|
|
void SetRenderResult(HRESULT result) { m_renderResult = result; }
|
||
|
|
|
||
|
|
// 테스트 검증용
|
||
|
|
uint32_t GetLastWidth() const { return m_width; }
|
||
|
|
uint32_t GetLastHeight() const { return m_height; }
|
||
|
|
|
||
|
|
// IVideoRenderer 구현
|
||
|
|
HRESULT Initialize(uint32_t width, uint32_t height) override {
|
||
|
|
m_width = width;
|
||
|
|
m_height = height;
|
||
|
|
m_initialized = (width > 0 && height > 0);
|
||
|
|
return m_initialized ? S_OK : E_INVALIDARG;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool IsInitialized() const override {
|
||
|
|
return m_initialized;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Cleanup() override {
|
||
|
|
m_initialized = false;
|
||
|
|
m_width = 0;
|
||
|
|
m_height = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool TryRenderFrame(const VideoFrame& frame) override {
|
||
|
|
return m_initialized && m_tryRenderResult;
|
||
|
|
}
|
||
|
|
|
||
|
|
HRESULT RenderVideoFrame(const VideoFrame& frame) override {
|
||
|
|
return m_initialized ? m_renderResult : E_NOT_VALID_STATE;
|
||
|
|
}
|
||
|
|
|
||
|
|
void SetAspectFitMode(bool enabled) override {
|
||
|
|
// Mock implementation - do nothing
|
||
|
|
}
|
||
|
|
|
||
|
|
void UpdateContainerSize(uint32_t width, uint32_t height) override {
|
||
|
|
// Mock implementation - store values
|
||
|
|
m_width = width;
|
||
|
|
m_height = height;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### **⚡ Phase 2: GPU 렌더링 테스트**
|
||
|
|
|
||
|
|
#### **2.1 SimpleGPURenderer 테스트 (스텁 모드)**
|
||
|
|
```cpp
|
||
|
|
// unit-test/SimpleGPURendererTest.cpp
|
||
|
|
#include "pch.h"
|
||
|
|
#include "../src/Rendering/SimpleGPURenderer.h"
|
||
|
|
|
||
|
|
TEST_CLASS(SimpleGPURendererTest)
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TEST_METHOD(Constructor_Default_ShouldInitializeCorrectly)
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
SimpleGPURenderer renderer;
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsFalse(renderer.IsInitialized());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(Initialize_ValidDimensions_ShouldReturnTrue)
|
||
|
|
{
|
||
|
|
// Arrange
|
||
|
|
SimpleGPURenderer renderer;
|
||
|
|
|
||
|
|
// Act
|
||
|
|
HRESULT hr = renderer.Initialize(1920, 1080);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
// Note: May fail in headless environment, but should not crash
|
||
|
|
// Success depends on D3D12 availability
|
||
|
|
Assert::IsTrue(SUCCEEDED(hr) || hr == DXGI_ERROR_UNSUPPORTED);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### **🧪 Phase 3: 통합 테스트**
|
||
|
|
|
||
|
|
#### **3.1 간단한 통합 테스트**
|
||
|
|
```cpp
|
||
|
|
// unit-test/IntegrationTest.cpp
|
||
|
|
#include "pch.h"
|
||
|
|
#include "../src/FileIO/WebMFileReader.h"
|
||
|
|
#include "../src/Decoder/VideoDecoderFactory.h"
|
||
|
|
|
||
|
|
TEST_CLASS(IntegrationTest)
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TEST_METHOD(BasicPipeline_ComponentCreation_ShouldWork)
|
||
|
|
{
|
||
|
|
// Arrange & Act
|
||
|
|
auto fileReader = std::make_unique<WebMFileReader>();
|
||
|
|
auto decoder = VideoDecoderFactory::CreateDecoder(
|
||
|
|
VideoCodecType::AV1,
|
||
|
|
VideoDecoderFactory::DecoderType::SOFTWARE
|
||
|
|
);
|
||
|
|
|
||
|
|
// Assert
|
||
|
|
Assert::IsNotNull(fileReader.get());
|
||
|
|
Assert::IsNotNull(decoder.get());
|
||
|
|
}
|
||
|
|
|
||
|
|
TEST_METHOD(VideoFrame_AllocationAndDeallocation_ShouldNotLeak)
|
||
|
|
{
|
||
|
|
// Memory leak test
|
||
|
|
for (int i = 0; i < 100; i++) {
|
||
|
|
VideoFrame frame;
|
||
|
|
bool result = frame.AllocateYUV420P(1920, 1080);
|
||
|
|
Assert::IsTrue(result);
|
||
|
|
}
|
||
|
|
// Frames should automatically deallocate
|
||
|
|
}
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📁 **최종 테스트 프로젝트 구조**
|
||
|
|
|
||
|
|
### **목표 구조 (Phase 3 완료 후)**
|
||
|
|
```
|
||
|
|
D:\Project\video-av1\vav2\Vav2Player\Vav2Player\
|
||
|
|
├── Vav2UnitTest.vcxproj
|
||
|
|
├── unit-test/
|
||
|
|
│ ├── pch.h / pch.cpp # ✅ 기존
|
||
|
|
│ ├── VideoTypesTest.cpp # ✅ 기존
|
||
|
|
│ ├── WebMFileReaderTest.cpp # 🆕 Phase 1
|
||
|
|
│ ├── AV1DecoderTest.cpp # 🆕 Phase 1
|
||
|
|
│ ├── VideoDecoderFactoryTest.cpp # 🆕 Phase 1
|
||
|
|
│ ├── SimpleGPURendererTest.cpp # 🆕 Phase 2
|
||
|
|
│ └── IntegrationTest.cpp # 🆕 Phase 3
|
||
|
|
└── x64/Debug/UnitTest/
|
||
|
|
```
|
||
|
|
|
||
|
|
### **프로젝트 파일 업데이트**
|
||
|
|
```xml
|
||
|
|
<!-- Vav2UnitTest.vcxproj에 추가할 항목들 -->
|
||
|
|
<ClCompile Include="unit-test\WebMFileReaderTest.cpp" />
|
||
|
|
<ClCompile Include="unit-test\AV1DecoderTest.cpp" />
|
||
|
|
<ClCompile Include="unit-test\VideoDecoderFactoryTest.cpp" />
|
||
|
|
<ClCompile Include="unit-test\SimpleGPURendererTest.cpp" />
|
||
|
|
<ClCompile Include="unit-test\IntegrationTest.cpp" />
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ⚙️ **구현 상세**
|
||
|
|
|
||
|
|
### **1. Mock 없는 실제 클래스 테스트**
|
||
|
|
- **접근법**: 구체 클래스를 직접 테스트, Mock 복잡성 제거
|
||
|
|
- **외부 의존성**: 실패 케이스 테스트로 격리 (존재하지 않는 파일 등)
|
||
|
|
- **GPU 의존성**: 실패해도 크래시하지 않는지 검증
|
||
|
|
|
||
|
|
### **2. 테스트 데이터 최소화**
|
||
|
|
- **파일 의존성**: 실제 파일 대신 존재하지 않는 파일명으로 에러 케이스 테스트
|
||
|
|
- **메모리 테스트**: 동적 할당/해제 반복으로 메모리 누수 검증
|
||
|
|
- **단순한 입력**: 기본값과 경계값을 활용한 테스트
|
||
|
|
|
||
|
|
### **3. CI/CD 통합**
|
||
|
|
```bash
|
||
|
|
# 자동 테스트 스크립트
|
||
|
|
cd "D:\Project\video-av1\vav2\Vav2Player\Vav2Player"
|
||
|
|
|
||
|
|
# 1. 유닛 테스트 빌드
|
||
|
|
MSBuild Vav2UnitTest.vcxproj //p:Configuration=Debug //p:Platform=x64 //v:minimal
|
||
|
|
|
||
|
|
# 2. 테스트 실행
|
||
|
|
vstest.console.exe "x64\Debug\UnitTest\Vav2UnitTest.dll"
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🤔 **의사결정 가이드**
|
||
|
|
|
||
|
|
### **Option A vs Option B 선택 기준**
|
||
|
|
|
||
|
|
#### **Option A 선택 (인터페이스 + Mock)**
|
||
|
|
**다음 조건 중 하나라도 해당하면 권장:**
|
||
|
|
- 의존성 주입 패턴을 사용하고 싶은 경우
|
||
|
|
- 복잡한 시나리오 테스트가 필요한 경우
|
||
|
|
- 외부 의존성 격리가 중요한 경우
|
||
|
|
- 장기적인 테스트 확장성이 필요한 경우
|
||
|
|
|
||
|
|
**예상 시간**: Phase 0 (6시간) + Phase 1-3 (4일) = **5일**
|
||
|
|
|
||
|
|
#### **Option B 선택 (직접 테스트)**
|
||
|
|
**다음 조건에 해당하면 권장:**
|
||
|
|
- 빠른 테스트 구현이 우선인 경우
|
||
|
|
- 기존 코드 수정을 최소화하려는 경우
|
||
|
|
- 단순한 기능 검증만 필요한 경우
|
||
|
|
- 리소스(시간/인력)가 제한적인 경우
|
||
|
|
|
||
|
|
**예상 시간**: Phase 1-3 (4일) = **4일**
|
||
|
|
|
||
|
|
### **권장사항**
|
||
|
|
```
|
||
|
|
현재 상황 고려 시 🎯 Option A 권장
|
||
|
|
|
||
|
|
이유:
|
||
|
|
1. WebMFileReader는 파일 I/O 의존성이 높음
|
||
|
|
2. GPU 렌더링은 환경 의존성이 높음
|
||
|
|
3. 장기적인 유지보수성 필요
|
||
|
|
4. MAJOR_REFACTORING_GUIDE 완료로 안정적인 기반 확보됨
|
||
|
|
```
|
||
|
|
|
||
|
|
## 🕐 **실행 스케줄**
|
||
|
|
|
||
|
|
### **Option A 선택 시 (권장)**
|
||
|
|
- **Phase 0**: 1일 (인터페이스 리팩토링)
|
||
|
|
- **Phase 1.5**: 1일 (Mock 시스템)
|
||
|
|
- **Phase 1**: 2일 (핵심 컴포넌트 테스트)
|
||
|
|
- **Phase 2**: 1일 (GPU 렌더링 테스트)
|
||
|
|
- **Phase 3**: 1일 (통합 테스트)
|
||
|
|
|
||
|
|
**총 6일**
|
||
|
|
|
||
|
|
### **Option B 선택 시**
|
||
|
|
- **Phase 1**: 2-3일 (핵심 컴포넌트 테스트)
|
||
|
|
- **Phase 2**: 1일 (GPU 렌더링 테스트)
|
||
|
|
- **Phase 3**: 1일 (통합 테스트)
|
||
|
|
|
||
|
|
**총 4-5일**
|
||
|
|
|
||
|
|
### **우선순위**
|
||
|
|
1. **의사결정**: Option A vs B 선택
|
||
|
|
2. **WebMFileReaderTest.cpp** (가장 핵심적)
|
||
|
|
3. **AV1DecoderTest.cpp** (디코딩 로직)
|
||
|
|
4. **VideoDecoderFactoryTest.cpp** (팩토리 패턴)
|
||
|
|
5. **SimpleGPURendererTest.cpp** (GPU 안정성)
|
||
|
|
6. **IntegrationTest.cpp** (전체 검증)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 **성공 지표**
|
||
|
|
|
||
|
|
### **정량적 목표**
|
||
|
|
- **테스트 커버리지**: 핵심 public 메서드 80% 이상
|
||
|
|
- **테스트 실행 시간**: 30초 이내
|
||
|
|
- **테스트 파일 수**: 6개 (기존 1개 + 신규 5개)
|
||
|
|
- **빌드 성공률**: 100%
|
||
|
|
|
||
|
|
### **정성적 목표**
|
||
|
|
- **크래시 방지**: 모든 테스트가 크래시 없이 완료
|
||
|
|
- **회귀 방지**: 기존 기능 변경 시 테스트로 검증 가능
|
||
|
|
- **유지보수성**: 새로운 컴포넌트 추가 시 테스트 패턴 재사용 가능
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ⚠️ **주의사항**
|
||
|
|
|
||
|
|
### **1. 환경 의존성 최소화**
|
||
|
|
- **D3D12**: GPU 없는 환경에서도 실패하지 않도록 처리
|
||
|
|
- **dav1d**: 라이브러리 로드 실패 시에도 테스트 완료
|
||
|
|
- **MediaFoundation**: 지원되지 않는 환경에서 graceful failure
|
||
|
|
|
||
|
|
### **2. 테스트 격리**
|
||
|
|
- **파일 시스템**: 실제 파일에 의존하지 않는 테스트 우선
|
||
|
|
- **전역 상태**: 테스트 간 상호 영향 최소화
|
||
|
|
- **메모리**: 각 테스트 후 리소스 정리
|
||
|
|
|
||
|
|
### **3. 실용적 접근**
|
||
|
|
- **완벽한 Coverage보다 핵심 기능 안정성**
|
||
|
|
- **복잡한 Mock보다 간단한 실제 테스트**
|
||
|
|
- **외부 의존성 Mock보다 에러 케이스 활용**
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**🎯 핵심 메시지: "단순하고 실용적인 테스트로 안정성 확보"**
|
||
|
|
|
||
|
|
*문서 작성일: 2025-09-23*
|
||
|
|
*이전 문서 대체: UNIT_TEST_REFACTORING_PLAN.md (구식)*
|