feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다. 주요 기능: - 네이버 지도 연동 맛집 추가 - 랜덤 메뉴 추천 시스템 - 날씨 기반 거리 조정 - 방문 기록 관리 - Bluetooth 맛집 공유 - 다크모드 지원 기술 스택: - Flutter 3.8.1+ - Riverpod 상태 관리 - Hive 로컬 DB - Clean Architecture 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
193
test/unit/domain/usecases/recommendation_engine_test.dart
Normal file
193
test/unit/domain/usecases/recommendation_engine_test.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lunchpick/domain/usecases/recommendation_engine.dart';
|
||||
import 'package:lunchpick/domain/entities/restaurant.dart';
|
||||
import 'package:lunchpick/domain/entities/visit_record.dart';
|
||||
import 'package:lunchpick/domain/entities/user_settings.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
void main() {
|
||||
late RecommendationEngine engine;
|
||||
late List<Restaurant> testRestaurants;
|
||||
late List<VisitRecord> testVisitRecords;
|
||||
|
||||
setUp(() {
|
||||
engine = RecommendationEngine();
|
||||
|
||||
// 테스트용 맛집 데이터 생성
|
||||
testRestaurants = [
|
||||
Restaurant(
|
||||
id: '1',
|
||||
name: '가까운 한식당',
|
||||
category: '한식',
|
||||
subCategory: '백반',
|
||||
roadAddress: '서울 중구 세종대로 110',
|
||||
jibunAddress: '서울 중구 태평로1가 31',
|
||||
latitude: 37.5666,
|
||||
longitude: 126.9784,
|
||||
source: DataSource.USER_INPUT,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
visitCount: 0,
|
||||
),
|
||||
Restaurant(
|
||||
id: '2',
|
||||
name: '먼 중식당',
|
||||
category: '중식',
|
||||
subCategory: '짜장면',
|
||||
roadAddress: '서울 강남구 테헤란로 123',
|
||||
jibunAddress: '서울 강남구 역삼동 123',
|
||||
latitude: 37.5012,
|
||||
longitude: 127.0396,
|
||||
source: DataSource.USER_INPUT,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
visitCount: 0,
|
||||
),
|
||||
Restaurant(
|
||||
id: '3',
|
||||
name: '최근 방문한 일식당',
|
||||
category: '일식',
|
||||
subCategory: '스시',
|
||||
roadAddress: '서울 종로구 종로 123',
|
||||
jibunAddress: '서울 종로구 종로1가 123',
|
||||
latitude: 37.5702,
|
||||
longitude: 126.9842,
|
||||
source: DataSource.USER_INPUT,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
visitCount: 1,
|
||||
),
|
||||
];
|
||||
|
||||
// 테스트용 방문 기록 생성
|
||||
testVisitRecords = [
|
||||
VisitRecord(
|
||||
id: const Uuid().v4(),
|
||||
restaurantId: '3',
|
||||
visitDate: DateTime.now().subtract(const Duration(days: 2)),
|
||||
isConfirmed: true,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
});
|
||||
|
||||
group('RecommendationEngine', () {
|
||||
test('거리 필터링이 정상 작동해야 함', () async {
|
||||
final config = RecommendationConfig(
|
||||
userLatitude: 37.5665,
|
||||
userLongitude: 126.9780,
|
||||
maxDistance: 1.0, // 1km
|
||||
selectedCategories: [],
|
||||
userSettings: UserSettings(),
|
||||
);
|
||||
|
||||
final result = await engine.generateRecommendation(
|
||||
allRestaurants: testRestaurants,
|
||||
recentVisits: [],
|
||||
config: config,
|
||||
);
|
||||
|
||||
// 1km 이내의 맛집만 추천되어야 함
|
||||
expect(result, isNotNull);
|
||||
expect(result!.id, isIn(['1', '3'])); // 가까운 한식당과 일식당만
|
||||
expect(result.id, isNot('2')); // 먼 중식당은 제외
|
||||
});
|
||||
|
||||
test('재방문 방지가 정상 작동해야 함', () async {
|
||||
final settings = UserSettings();
|
||||
final updatedSettings = settings.copyWith(revisitPreventionDays: 7);
|
||||
|
||||
final config = RecommendationConfig(
|
||||
userLatitude: 37.5665,
|
||||
userLongitude: 126.9780,
|
||||
maxDistance: 10.0, // 10km
|
||||
selectedCategories: [],
|
||||
userSettings: updatedSettings,
|
||||
);
|
||||
|
||||
final result = await engine.generateRecommendation(
|
||||
allRestaurants: testRestaurants,
|
||||
recentVisits: testVisitRecords,
|
||||
config: config,
|
||||
);
|
||||
|
||||
// 최근 방문한 일식당은 제외되어야 함
|
||||
expect(result, isNotNull);
|
||||
expect(result!.id, isNot('3'));
|
||||
});
|
||||
|
||||
test('카테고리 필터링이 정상 작동해야 함', () async {
|
||||
final config = RecommendationConfig(
|
||||
userLatitude: 37.5665,
|
||||
userLongitude: 126.9780,
|
||||
maxDistance: 100.0, // 100km
|
||||
selectedCategories: ['한식'],
|
||||
userSettings: UserSettings(),
|
||||
);
|
||||
|
||||
final result = await engine.generateRecommendation(
|
||||
allRestaurants: testRestaurants,
|
||||
recentVisits: [],
|
||||
config: config,
|
||||
);
|
||||
|
||||
// 한식만 추천되어야 함
|
||||
expect(result, isNotNull);
|
||||
expect(result!.category, '한식');
|
||||
});
|
||||
|
||||
test('모든 조건을 만족하는 맛집이 없으면 null을 반환해야 함', () async {
|
||||
final config = RecommendationConfig(
|
||||
userLatitude: 37.5665,
|
||||
userLongitude: 126.9780,
|
||||
maxDistance: 0.1, // 100m - 너무 가까움
|
||||
selectedCategories: [],
|
||||
userSettings: UserSettings(),
|
||||
);
|
||||
|
||||
final result = await engine.generateRecommendation(
|
||||
allRestaurants: testRestaurants,
|
||||
recentVisits: [],
|
||||
config: config,
|
||||
);
|
||||
|
||||
expect(result, isNull);
|
||||
});
|
||||
|
||||
test('가중치 시스템이 정상 작동해야 함', () async {
|
||||
// 한식에 높은 가중치 부여
|
||||
final settings = UserSettings();
|
||||
final updatedSettings = settings.copyWith(
|
||||
categoryWeights: {
|
||||
'한식': 2.0,
|
||||
'중식': 0.5,
|
||||
'일식': 1.0,
|
||||
},
|
||||
);
|
||||
|
||||
final config = RecommendationConfig(
|
||||
userLatitude: 37.5665,
|
||||
userLongitude: 126.9780,
|
||||
maxDistance: 100.0,
|
||||
selectedCategories: [],
|
||||
userSettings: updatedSettings,
|
||||
);
|
||||
|
||||
// 여러 번 실행하여 한식이 더 자주 추천되는지 확인
|
||||
final results = <String, int>{};
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final result = await engine.generateRecommendation(
|
||||
allRestaurants: testRestaurants,
|
||||
recentVisits: [],
|
||||
config: config,
|
||||
);
|
||||
if (result != null) {
|
||||
results[result.category] = (results[result.category] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 한식이 다른 카테고리보다 더 많이 추천되어야 함
|
||||
expect(results['한식'] ?? 0, greaterThan(results['중식'] ?? 0));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user