20240304 (월) 최종 프로젝트 티켓레이더 2주차 - Test-code

2024. 3. 4. 19:57TIL

1. EventServiceImpl 구현완료

 

package com.codersgate.ticketraider.domain.event.service

import com.codersgate.ticketraider.domain.category.repository.CategoryRepository
import com.codersgate.ticketraider.domain.event.dto.EventRequest
import com.codersgate.ticketraider.domain.event.dto.EventResponse
import com.codersgate.ticketraider.domain.event.repository.EventRepository
import com.codersgate.ticketraider.domain.event.repository.price.PriceRepository
import com.codersgate.ticketraider.domain.event.repository.seat.AvailableSeatRepository
import com.codersgate.ticketraider.domain.place.repository.PlaceRepository
import com.codersgate.ticketraider.global.error.exception.ModelNotFoundException
import org.springframework.data.domain.Page
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.data.domain.Pageable

@Service
class EventServiceImpl(
    private val categoryRepository: CategoryRepository,
    private val eventRepository: EventRepository,
    private val priceRepository: PriceRepository,
    private val availableSeatRepository: AvailableSeatRepository,
    private val placeRepository: PlaceRepository
) : EventService {
    override fun createEvent(eventRequest: EventRequest) {
        val category = categoryRepository.findByIdOrNull(eventRequest.categoryId)
            ?: throw ModelNotFoundException("category", eventRequest.categoryId)
        val place = placeRepository.findPlaceByName(eventRequest.place)
            ?: throw ModelNotFoundException("place", 0)//예외 추가 필요함
        val (price, event) = eventRequest.toPriceAndEvent(category, place)
        event.price = price
        eventRepository.save(event)
        priceRepository.save(price)

        val date = eventRequest.startDate
        val duration = eventRequest.endDate.compareTo(eventRequest.startDate)
        for (i in 0..duration) {
            val seat = eventRequest.toAvailableSeat(event, place, date.plusDays(i.toLong()))
            availableSeatRepository.save(seat)
        }
    }


    override fun updateEvent(eventId: Long, eventRequest: EventRequest) {
        var event = eventRepository.findByIdOrNull(eventId)
            ?: throw ModelNotFoundException("Event", eventId)
        var price = priceRepository.findByEventId(eventId)
            ?: throw ModelNotFoundException("Price", eventId)
        val category = categoryRepository.findByIdOrNull(eventRequest.categoryId)
            ?: throw ModelNotFoundException("category", eventRequest.categoryId)
        val place = placeRepository.findPlaceByName(eventRequest.place)
            ?: throw ModelNotFoundException("place", 0)

        check(eventRequest.startDate != event.startDate || eventRequest.endDate != event.endDate) {
            availableSeatRepository.findAllByEventId(event.id!!).map { availableSeatRepository.delete(it!!) }
            val date = eventRequest.startDate
            val duration = eventRequest.endDate.compareTo(eventRequest.startDate)
            for (i in 0..duration) {
                val seat = eventRequest.toAvailableSeat(event, place, date.plusDays(i.toLong()))
                availableSeatRepository.save(seat)
            }
        }

        val (newPrice, newEvent) = eventRequest.toPriceAndEvent(category, place)
        event.price = newPrice
        price = newPrice
        event = newEvent
    }

    @Transactional
    override fun deleteEvent(eventId: Long) {
        val event = eventRepository.findByIdOrNull(eventId)
            ?: throw ModelNotFoundException("Event", eventId)
        eventRepository.delete(event)
    }

    override fun getPaginatedEventList(pageable: Pageable, status: String?, categoryId: Long?): Page<EventResponse>? {

        val category = categoryRepository.findByIdOrNull(categoryId)
            ?: throw ModelNotFoundException("Category", categoryId)

        val eventList = eventRepository.findByPageable(pageable, category)
        return eventList.map { EventResponse.from(it) }
    }

    override fun getEvent(eventId: Long): EventResponse {
        val event = eventRepository.findByIdOrNull(eventId)
            ?: throw ModelNotFoundException("Event", eventId)
        return EventResponse.from(event)
    }
}

생성을 제외한 RUD, 쿼리DSL 기반으로 Pageable기능을 구현하였다.

 

2. TickerServiceTest 생성(동시성 제어 테스트)

 

package com.codersgate.ticketraider.domain.ticket.service

import com.codersgate.ticketraider.domain.category.model.Category
import com.codersgate.ticketraider.domain.category.repository.CategoryRepository
import com.codersgate.ticketraider.domain.event.dto.EventRequest
import com.codersgate.ticketraider.domain.event.repository.EventRepository
import com.codersgate.ticketraider.domain.event.repository.price.PriceRepository
import com.codersgate.ticketraider.domain.event.service.EventService
import com.codersgate.ticketraider.domain.member.entity.Member
import com.codersgate.ticketraider.domain.member.entity.MemberRole
import com.codersgate.ticketraider.domain.member.repository.MemberRepository
import com.codersgate.ticketraider.domain.place.model.Place
import com.codersgate.ticketraider.domain.place.repository.PlaceRepository
import com.codersgate.ticketraider.domain.ticket.dto.CreateTicketRequest
import com.codersgate.ticketraider.domain.ticket.entity.TicketGrade
import com.codersgate.ticketraider.global.error.exception.ModelNotFoundException
import io.mockk.junit5.MockKExtension
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.data.repository.findByIdOrNull
import org.springframework.test.context.ActiveProfiles
import java.time.LocalDate
import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors

@SpringBootTest
@ExtendWith(MockKExtension::class)
@ActiveProfiles("test")
class TicketServiceImplTest(
    @Autowired val memberRepository: MemberRepository,
    @Autowired val placeRepository: PlaceRepository,
    @Autowired val eventRepository: EventRepository,
    @Autowired val categoryRepository: CategoryRepository,
    @Autowired val ticketService: TicketService,
    @Autowired val eventService: EventService,
    @Autowired val priceRepository: PriceRepository
) {

    @Test
    @DisplayName("티켓 테스트")
    fun `ticket test`() {
        val place = Place(
            name = "문화회관",
            totalSeat = 300,
            seatR = 50,
            seatS = 100,
            seatA = 150
        )
        placeRepository.save(place)
        val member = Member(
            email = "test@gmail.com",
            password = "test12",
            nickname = "testMember",
            role = MemberRole.ADMIN
        )
        memberRepository.save(member)
        val category = Category(
            title = "MUSICAL"
        )
        val getCate = categoryRepository.save(category)
        val createEventReq = EventRequest(
            title = "오페라의 유령",
            posterImage = "string",
            categoryId = getCate.id!!,
            eventInfo = "String",
            _startDate = "2024-03-15",
            _endDate = "2024-03-17",
            place = "문화회관",
            seatRPrice = 150000,
            seatSPrice = 100000,
            seatAPrice = 50000
        )
        eventService.createEvent(createEventReq)
        val getEvent = eventRepository.findByIdOrNull(1)
        val threadCount = 10

        val executorService = Executors.newFixedThreadPool(10)
        val countDownLatch = CountDownLatch(threadCount)
        val createTicketReq = CreateTicketRequest(
            date = LocalDate.now(),
            grade = TicketGrade.R,
            seatNo = 15,
            eventId = getEvent!!.id!!,
            memberId = member.id!!
        )
        var success = 0
        var fail = 0

        //when 100개의 스레드로 동시에 티켓을 구매했을때
        repeat(threadCount) {
            executorService.submit {
                try {
                    ticketService.createTicket(createTicketReq)
                    success++
                } catch (e: ModelNotFoundException) { //예외처리 추가후 수정
                    fail++
                } finally {
                    countDownLatch.countDown()
                }
            }
        }
        countDownLatch.await()

        // 시도한 횟수랑 = 실패 + 성공 횟수 같아야함.
        // 성공 횟수가 100이 아닌거는 동시성 문제해결.
        println("success : $success")
        println("fail : $fail")

        //then 성공50 실패50 이여야한다.
//        Assertions.assertThat(success).isEqualTo()
//        Assertions.assertThat(fail).isEqualTo)
    }
}

 

Docker 사용법에 대한 공부와 Redis 설치및 실행, Test 코드 작성을 완료하였다.

 


이전까지는 Docker를 팀원의 서버를 이용했는데 오늘 내가 직접 사용해보니 확실히 Window로 사용하려다 보니 최적화같은 문제가 있는것 같다.(오래켜두면 렉이 매우 심해짐)

테스트할때만 키도록 해야겠다.