def __post_init__(self): # Pythonのデフォルト引数の罠を踏まないための実装 if self.execute_date is None: self.execute_date = datetime.datetime.today() self.__dummy_reservation = Reservation(self._get_next_reservation_id(), self._default_time_to_range(), NumberOfParticipants(4), MeetingRoomId('A'), EmployeeId('001'))
def reservation(self) -> Reservation: """不正でないReservationインスタンスを作成するだけのfixture""" return Reservation( ReservationId(str(uuid.uuid4())), TimeRangeToReserve(使用日時(2020, 4, 2, 13, 00), 使用日時(2020, 4, 2, 14, 00)), NumberOfParticipants(4), MeetingRoomId('A'), EmployeeId('001'))
def test_単一の正常なReservationを生成できる_予約時間帯は翌日13時から14時がデフォルトとなる(self): expected = Reservation( ReservationId('1'), TimeRangeToReserve(使用日時(2020, 4, 2, 13, 00), 使用日時(2020, 4, 2, 14, 00)), NumberOfParticipants(4), MeetingRoomId('A'), EmployeeId('001')) assert DummyReservationBuilder().build() == expected
def to_reservation(cls, source: OratorReservationModel) -> Reservation: start_yyyy_mm_dd_HH_MM = datetime.datetime.strptime( source.start_datetime, '%Y-%m-%d %H:%M:%S').timetuple()[:5] end_yyyy_mm_dd_HH_MM = datetime.datetime.strptime( source.end_datetime, '%Y-%m-%d %H:%M:%S').timetuple()[:5] time_range_to_reserve = TimeRangeToReserve( 使用日時(*start_yyyy_mm_dd_HH_MM), 使用日時(*end_yyyy_mm_dd_HH_MM)) return Reservation( ReservationId(source.id), time_range_to_reserve, NumberOfParticipants(source.number_of_participants), MeetingRoomId(source.meeting_room_id), EmployeeId(source.reserver_id), ReservationStatus.from_str(source.reservation_status))
def test_Reservationが作れるよ(self): reservation_id = 'データクラス同士の比較のために、やっているよ' actual = self.reservation_factory.create(date='20200402', start_time='1100', end_time='1300', meeting_room_id='A', reserver_id='001', number_of_participants='5', reservation_id=reservation_id) expected = Reservation( ReservationId(reservation_id), TimeRangeToReserve(使用日時(2020, 4, 2, 11, 00), 使用日時(2020, 4, 2, 13, 00)), NumberOfParticipants(5), MeetingRoomId('A'), EmployeeId('001')) assert actual == expected
def test_複雑なReservationもメソッドチェーンでつくりやすいよ(self): another_time_range_to_reserve = TimeRangeToReserve( 使用日時(2020, 4, 15, 13, 00), 使用日時(2020, 4, 15, 14, 00)) another_meeting_room_id = MeetingRoomId('Z') another_employee_id_999 = EmployeeId('999') expected = Reservation(ReservationId('1'), another_time_range_to_reserve, NumberOfParticipants(4), another_meeting_room_id, another_employee_id_999, ReservationStatus.Canceled) actual = DummyReservationBuilder() \ .with_time_range_to_reserve(another_time_range_to_reserve) \ .with_meeting_room_id(another_meeting_room_id) \ .with_reserver_id(another_employee_id_999) \ .with_cancel() \ .build() assert actual == expected
def create( self, date: str, start_time: str, end_time: str, meeting_room_id: str, reserver_id: str, number_of_participants: str, reservation_id: str = str(uuid.uuid4()) ) -> Reservation: reservation_id = self._create_reservation_id(reservation_id) time_range_to_reserve = self._create_time_range_to_reserve( date, end_time, start_time) number_of_participants = self._create_number_of_participants( number_of_participants) meeting_room_id = self._create_meeting_room_id(meeting_room_id) employee_id = self._create_employee_id(reserver_id) return Reservation(reservation_id, time_range_to_reserve, number_of_participants, meeting_room_id, employee_id)
class DummyReservationBuilder: """テスト用のReservation生成を楽にするためのクラス テスト以外では使ってはいけない理由 1. 不正なReservationをつくれてしまうから - 存在しない会議室IDや存在しない予約者IDを持つReservationの生成ができてしまう 2. Reservation間の整合性(予約時間帯が重なっていないなど)は担保できないから - やろうと思えば、リポジトリやドメインサービスを適用するってことはできるが、それはやりすぎでは? ポイント 1. デフォルトの予約時間帯は、「実行日の翌日の13時〜14時」である - `time_range_to_reserve` の指定記述が非常にだるいのでこれで十分という判断 - 「翌日」にしているのは、不正な予約時間帯になるのを防ぐため - 特定の予約時間帯で作りたい場合は、 `self.with_time_range_to_reserve()` を使いましょう できないこと 1. 過去の日時を持つデータはつくれない - 結局、使用日時クラスの now で判断しているため生成不可能。freezegunなどで日時を固定すること 2. reservation_id と number_of_participants の指定はできない(2020年6月1日時点) - 現状、これらを特定の値にしたいモチベーションがないため その他 1. 同一のインスタンスから生成されるReservationのId重複だけは防いでいる """ execute_date: datetime.date = dataclasses.field(default=None) used_reservation_ids: Set[ReservationId] = dataclasses.field(default_factory=set) __dummy_reservation: Reservation = dataclasses.field(init=False) def __post_init__(self): # Pythonのデフォルト引数の罠を踏まないための実装 if self.execute_date is None: self.execute_date = datetime.datetime.today() self.__dummy_reservation = Reservation(self._get_next_reservation_id(), self._default_time_to_range(), NumberOfParticipants(4), MeetingRoomId('A'), EmployeeId('001')) def _get_next_reservation_id(self) -> ReservationId: """次のReservationIdを生成する。Idの値は1から順にインクリメントする。""" # これを採用したメリット: Reservationインスタンスのassertionが楽にできる # これを採用したリスク: インスタンスを複数つくれば、ReservationIdかぶりをつくれる used_id_count = len(self.used_reservation_ids) next_id = str(used_id_count + 1) return ReservationId(next_id) def _default_time_to_range(self) -> TimeRangeToReserve: """実行日の翌日13時〜14時 がデフォルトの予約時間帯""" the_next_day = self.execute_date + datetime.timedelta(days=1) yyyy, mm, dd, *_ = the_next_day.timetuple() return TimeRangeToReserve(使用日時(yyyy, mm, dd, 13, 00), 使用日時(yyyy, mm, dd, 14, 00)) def with_meeting_room_id(self, meeting_room_id: MeetingRoomId) -> DummyReservationBuilder: # テストデータ作成のための強引なミューテーション self.__dummy_reservation = dataclasses.replace(self.__dummy_reservation, meeting_room_id=meeting_room_id) return self def with_cancel(self) -> DummyReservationBuilder: self.__dummy_reservation = self.__dummy_reservation.cancel() return self def with_reserver_id(self, reserver_id: EmployeeId) -> DummyReservationBuilder: # テストデータ作成のための強引なミューテーションだから妥協して使用している self.__dummy_reservation = dataclasses.replace(self.__dummy_reservation, reserver_id=reserver_id) return self def with_time_range_to_reserve(self, time_range_to_reserve: TimeRangeToReserve) -> DummyReservationBuilder: # テストデータ作成のための強引なミューテーションだから妥協して使用している self.__dummy_reservation = dataclasses.replace(self.__dummy_reservation, time_range_to_reserve=time_range_to_reserve) return self def with_random_id(self) -> DummyReservationBuilder: # テストデータ作成のための強引なミューテーションだから妥協して使用している # ReservationId の生成ルールがガードできていないので注意! self.__dummy_reservation = dataclasses.replace(self.__dummy_reservation, id=ReservationId(str(uuid.uuid4()))) return self def build(self) -> Reservation: if self._has_already_build_reservation_id(): self._set_next_id() self._register_used_id() return self.__dummy_reservation def _has_already_build_reservation_id(self) -> bool: return self.__dummy_reservation.id in self.used_reservation_ids def _set_next_id(self) -> None: self.__dummy_reservation = dataclasses.replace(self.__dummy_reservation, id=self._get_next_reservation_id()) def _register_used_id(self) -> None: self.used_reservation_ids.add(self.__dummy_reservation.id)