테스트와 생산 코드 사이에 상수가 중복됩니까?


20

테스트와 실제 코드간에 데이터를 복제하는 것이 좋거나 나쁘습니까? 예를 들어 FooSaver특정 이름의 파일을 주어진 디렉토리에 저장 하는 Python 클래스가 있다고 가정 합니다.

class FooSaver(object):
  def __init__(self, out_dir):
    self.out_dir = out_dir

  def _save_foo_named(self, type_, name):
    to_save = None
    if type_ == FOOTYPE_A:
      to_save = make_footype_a()
    elif type == FOOTYPE_B:
      to_save = make_footype_b()
    # etc, repeated
    with open(self.out_dir + name, "w") as f:
      f.write(str(to_save))

  def save_type_a(self):
    self._save_foo_named(a, "a.foo_file")

  def save_type_b(self):
    self._save_foo_named(b, "b.foo_file")

이제 내 테스트에서 이러한 모든 파일이 만들어 졌는지 확인하고 싶습니다. 그래서 다음과 같이 말하고 싶습니다.

foo = FooSaver("/tmp/special_name")
foo.save_type_a()
foo.save_type_b()

self.assertTrue(os.path.isfile("/tmp/special_name/a.foo_file"))
self.assertTrue(os.path.isfile("/tmp/special_name/b.foo_file"))

이것은 파일 이름을 두 곳에서 복제하지만 그것이 좋다고 생각합니다. 다른 쪽 끝에서 나올 것으로 예상되는 것을 정확하게 적어두고 오타에 대한 보호 계층을 추가하며 일반적으로 일이 작동한다고 확신합니다. 내가 기대 한대로 나중에 변경 a.foo_file하면 type_a.foo_file테스트에서 검색 및 교체를 수행해야하지만 너무 큰 문제는 아니라고 생각합니다. 코드와 테스트에 대한 나의 이해가 동기화되어 있는지 확인하는 대신 테스트를 업데이트하는 것을 잊어 버린 경우 오히려 오 탐지가 있습니다.

동료는이 중복이 나쁘다고 생각하고 양쪽을 다음과 같이 리팩토링하는 것이 좋습니다.

class FooSaver(object):
  A_FILENAME = "a.foo_file"
  B_FILENAME = "b.foo_file"

  # as before...

  def save_type_a(self):
    self._save_foo_named(a, self.A_FILENAME)

  def save_type_b(self):
    self._save_foo_named(b, self.B_FILENAME)

그리고 시험에서 :

self.assertTrue(os.path.isfile("/tmp/special_name/" + FooSaver.A_FILENAME))
self.assertTrue(os.path.isfile("/tmp/special_name/" + FooSaver.B_FILENAME))

코드가 내가 기대하는 것을하고 있다고 확신하지 못하기 때문에 이것을 좋아하지 않습니다 .- out_dir + name생산 측과 테스트 측 모두에서 단계를 복제했습니다 . +문자열에서 작동 하는 방식에 대한 이해에서 오류를 발견 하지 못하며 오타를 잡지 않습니다.

반면에 문자열을 두 번 쓰는 것보다 덜 부서지기 쉽고 두 파일에서 데이터를 복제하는 것은 약간 잘못된 것 같습니다.

여기에 분명한 선례가 있습니까? 테스트와 프로덕션 코드에서 상수를 복제해도 괜찮습니까?

답변:


16

나는 그것이 당신이 시험하려고하는 것에 달려 있으며, 그것은 수업의 계약이 무엇인지에 달려 있다고 생각합니다.

클래스의 계약이 정확히 FooSaver생성 a.foo_file되고 b.foo_file특정 위치에있는 경우 직접 테스트해야합니다. 즉, 테스트에서 상수를 복제해야합니다.

그러나 클래스의 계약에 따라 두 개의 파일이 임시 영역에 생성되는 경우 (특히 런타임에 각 이름이 쉽게 변경됨) 테스트에서 제외 된 상수를 사용하여보다 일반적으로 테스트해야합니다.

따라서 높은 수준의 도메인 디자인 관점에서 클래스의 실제 특성과 계약에 대해 동료와 논의해야합니다. 만약 당신이 동의 할 수 없다면, 이것은 이것을 테스트하기보다는 클래스 자체의 이해와 추상화 수준의 문제라고 말할 것입니다.

리팩토링 중에 클래스의 계약 변경, 예를 들어 추상화 레벨이 시간이 지남에 따라 증가하는 것을 찾는 것도 합리적입니다. 처음에는 특정 임시 위치에있는 두 개의 특정 파일에 관한 것이지만 시간이 지남에 따라 추가 추상화가 필요하다는 것을 알 수 있습니다. 이때 시험을 변경하여 수업 계약과 동기화되도록하십시오. 테스트하고 있기 때문에 클래스 계약을 바로 구축 할 필요가 없습니다 (YAGNI).

클래스의 계약이 잘 정의되어 있지 않으면 테스트하면 클래스의 특성에 의문이 생길 수 있지만 사용하는 것입니다. 나는 당신이 그것을 테스트하고 있다고해서 당신이 클래스의 계약을 업그레이드해서는 안된다고 말할 것입니다. 도메인의 추상화가 약한 등의 다른 이유로 클래스 계약을 업그레이드해야하며 그렇지 않은 경우 그대로 테스트해야합니다.


4

테스트중인 대상을 명확하게하기 위해 @Erik이 제안한 사항은 반드시 첫 번째 고려 사항이되어야합니다.

그러나 당신의 결정으로 상수를 빼는 방향으로 이끌어야하는데, 그것은 질문의 흥미로운 부분을 남깁니다. ( "out_dir + name step 복제"에 대한 내용을 참조하십시오.)

(modulo Erik의 의견) 대부분의 상황 중복 상수를 제거 하면 도움이 된다고 생각합니다 . 그러나 코드를 복제 하지 않는 방식으로이 작업을 수행해야합니다 . 특정 예에서 이것은 쉽습니다. 프로덕션 코드에서 경로를 "원시"문자열로 처리하는 대신 경로를 경로로 취급하십시오. 이것은 문자열 연결보다 경로 구성 요소를 결합하는 더 강력한 방법입니다.

os.path.join(self.out_dir, name)

반면 테스트 코드에서는 이와 같은 것이 좋습니다. 여기에 경로가 있고 리프 파일 이름을 "플러그인"하고 있음을 강조합니다.

self.assertTrue(os.path.isfile("/tmp/special_name/{0}".format(FooSaver.A_FILENAME)))

즉, 언어 요소를보다 신중하게 선택하면 코드 중복을 자동으로 피할 수 있습니다. (항상 그런 것은 아니지만 제 경험에서 매우 자주 나타납니다.)


1

Erik Eidt의 답변에 동의하지만 세 번째 옵션이 있습니다. 테스트에서 상수를 스텁 아웃하므로 프로덕션 코드에서 상수 값을 변경하더라도 테스트가 통과됩니다.

( python unittest에서 상수 스텁 참조 )

foo = FooSaver("/tmp/special_name")
foo.save_type_a()
foo.save_type_b()

with mock.patch.object(FooSaver, 'A_FILENAME', 'unique_to_your_test_a'):
  self.assertTrue(os.path.isfile("/tmp/special_name/unique_to_your_test_a"))
with mock.patch.object(FooSaver, 'B_FILENAME', 'unique_to_your_test_b'):
  self.assertTrue(os.path.isfile("/tmp/special_name/unique_to_your_test_b"))

그리고 이와 같은 일을 할 때 나는 보통 with진술 없이 테스트를 실행하고 " 'a.foo_file'! = 'unique_to_your_test_a'"가 표시되는지 확인 with하는 테스트를 실시합니다. 다시 통과합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.