Nhân đôi hằng số giữa các thử nghiệm và mã sản xuất?


20

Nó là tốt hay xấu để sao chép dữ liệu giữa các thử nghiệm và mã thực? Ví dụ: giả sử tôi có một lớp Python FooSaverlưu các tệp có tên cụ thể vào một thư mục nhất định:

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")

Bây giờ trong thử nghiệm của tôi, tôi muốn đảm bảo rằng tất cả các tệp này đã được tạo, vì vậy tôi muốn nói điều gì đó như thế này:

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"))

Mặc dù điều này sao chép tên tệp ở hai nơi, nhưng tôi nghĩ nó tốt: nó buộc tôi phải viết chính xác những gì tôi mong đợi ở đầu kia, nó thêm một lớp bảo vệ chống lại lỗi chính tả và nói chung khiến tôi cảm thấy tự tin rằng mọi thứ đang hoạt động chính xác như tôi mong đợi Tôi biết rằng nếu tôi thay đổi a.foo_fileđể type_a.foo_filetrong tương lai tôi sẽ phải làm một số tìm kiếm và thay thế trong các thử nghiệm của tôi, nhưng tôi không nghĩ đó là quá lớn của một thỏa thuận. Tôi muốn có một số tích cực sai nếu tôi quên cập nhật thử nghiệm để đổi lấy việc đảm bảo rằng sự hiểu biết của tôi về mã và các thử nghiệm được đồng bộ hóa.

Một đồng nghiệp cho rằng sự trùng lặp này là xấu và khuyên tôi nên cấu trúc lại cả hai mặt thành một thứ như thế này:

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)

và trong bài kiểm tra:

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

Tôi không thích điều này bởi vì nó không khiến tôi tự tin rằng mã đang làm những gì tôi mong đợi --- Tôi vừa mới nhân đôi out_dir + namebước ở cả phía sản xuất và phía thử nghiệm. Nó sẽ không phát hiện ra lỗi trong cách hiểu của tôi về cách +hoạt động của chuỗi và nó sẽ không mắc lỗi chính tả.

Mặt khác, nó rõ ràng ít giòn hơn so với việc viết ra các chuỗi đó hai lần và đối với tôi, việc sao chép dữ liệu qua hai tệp như vậy có vẻ hơi sai.

Có một tiền lệ rõ ràng ở đây? Có thể sao chép các hằng số qua các thử nghiệm và mã sản xuất, hoặc nó quá dễ vỡ?

Câu trả lời:


16

Tôi nghĩ nó phụ thuộc vào những gì bạn đang cố gắng kiểm tra, điều này đi đến hợp đồng của lớp học là gì.

Nếu hợp đồng của lớp chính xác là FooSavertạo ra a.foo_fileb.foo_fileở một vị trí cụ thể, thì bạn nên kiểm tra trực tiếp, tức là nhân đôi hằng số trong các bài kiểm tra.

Tuy nhiên, nếu hợp đồng của lớp là nó tạo ra hai tệp vào một khu vực tạm thời, tên của mỗi tệp dễ dàng thay đổi, đặc biệt là trong thời gian chạy, thì bạn phải kiểm tra tổng quát hơn, có thể sử dụng các hằng số được đưa ra trong các bài kiểm tra.

Vì vậy, bạn nên tranh luận với đồng nghiệp về bản chất và hợp đồng thực sự của lớp từ góc độ thiết kế tên miền cấp cao hơn. Nếu bạn không thể đồng ý thì tôi sẽ nói rằng đây là vấn đề về mức độ hiểu và mức độ trừu tượng của chính lớp, thay vì kiểm tra nó.

Cũng hợp lý khi thấy hợp đồng của lớp thay đổi trong quá trình tái cấu trúc, ví dụ, mức độ trừu tượng của nó sẽ được nâng lên theo thời gian. Lúc đầu, tôi nói về hai tệp cụ thể ở một vị trí tạm thời cụ thể, nhưng theo thời gian, bạn có thể thấy sự trừu tượng hóa bổ sung được bảo hành. Tại thời điểm đó, thay đổi các bài kiểm tra để giữ chúng đồng bộ với hợp đồng của lớp. Không cần thiết phải xây dựng hợp đồng của lớp ngay lập tức chỉ vì bạn đang thử nghiệm nó (YAGNI).

Khi hợp đồng của một lớp không được xác định rõ, việc kiểm tra nó có thể khiến chúng ta đặt câu hỏi về bản chất của lớp, nhưng cũng sẽ sử dụng nó. Tôi sẽ nói rằng bạn không nên nâng cấp hợp đồng của lớp chỉ vì bạn đang thử nghiệm nó; bạn nên nâng cấp hợp đồng của lớp vì những lý do khác, chẳng hạn như, đó là một sự trừu tượng yếu cho miền, và nếu không thì hãy kiểm tra nó như hiện tại.


4

Những gì @Erik đề xuất - về mặt đảm bảo bạn rõ ràng về những gì bạn đang thử nghiệm - chắc chắn nên là điểm cân nhắc đầu tiên của bạn.

Nhưng liệu quyết định của bạn có dẫn bạn đến hướng bao thanh toán các hằng số không, điều đó khiến phần thú vị của câu hỏi của bạn (diễn giải) "Tại sao tôi nên đánh đổi các hằng số sao chép để sao chép mã?". (Liên quan đến nơi bạn nói về "trùng lặp [ing] bước out_dir + tên".)

Tôi tin rằng (ý kiến ​​của modulo Erik) hầu hết các tình huống đều có lợi từ việc loại bỏ các hằng số trùng lặp. Nhưng bạn cần làm điều này theo cách không trùng lặp mã. Trong ví dụ cụ thể của bạn, điều này là dễ dàng. Thay vì xử lý một đường dẫn là các chuỗi "thô" trong mã sản xuất của bạn, hãy coi một đường dẫn là một đường dẫn. Đây là một cách mạnh mẽ hơn để tham gia các thành phần đường dẫn hơn so với nối chuỗi:

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

Trong mã kiểm tra của bạn, mặt khác, tôi muốn giới thiệu một cái gì đó như thế này. Ở đây, sự nhấn mạnh cho thấy rằng bạn có một đường dẫn và bạn đang "cắm" một tên tệp lá:

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

Đó là, bằng cách lựa chọn các yếu tố ngôn ngữ chu đáo hơn, bạn có thể tự động tránh sao chép mã. (Không phải tất cả thời gian, nhưng rất thường xuyên theo kinh nghiệm của tôi.)


1

Tôi đồng ý với câu trả lời của Erik Eidt, nhưng có một lựa chọn thứ ba: loại bỏ hằng số trong thử nghiệm, vì vậy thử nghiệm vượt qua ngay cả khi bạn thay đổi giá trị của hằng số trong mã sản xuất.

(xem stubbing một hằng số trong 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"))

Và khi làm những việc như thế này, tôi thường đảm bảo thực hiện kiểm tra độ tỉnh táo khi tôi chạy các bài kiểm tra mà không cần withcâu lệnh và đảm bảo tôi thấy "'a.foo_file'! = 'Unique_to_your_test_a'", sau đó đưa withcâu lệnh trở lại trong bài kiểm tra Vì vậy, nó đi qua một lần nữa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.