Làm thế nào tôi nên viết một bài kiểm tra cho một phương thức thuần túy mà không trả về bất cứ điều gì?


13

Tôi có một loạt các lớp liên quan đến việc xác nhận các giá trị. Chẳng hạn, một RangeValidatorlớp kiểm tra xem một giá trị có nằm trong phạm vi được chỉ định hay không.

Mỗi lớp trình xác nhận chứa hai phương thức: is_valid(value)trả về Truehoặc Falsetùy thuộc vào giá trị và ensure_valid(value)kiểm tra một giá trị được chỉ định và không làm gì nếu giá trị hợp lệ hoặc ném một ngoại lệ cụ thể nếu giá trị không khớp với quy tắc được xác định trước.

Hiện tại có hai bài kiểm tra đơn vị liên quan đến phương pháp này:

  • Giá trị vượt qua giá trị không hợp lệ và đảm bảo rằng ngoại lệ được đưa ra.

    def test_outside_range(self):
        with self.assertRaises(demo.ValidationException):
            demo.RangeValidator(0, 100).ensure_valid(-5)
    
  • Một trong đó vượt qua một giá trị hợp lệ.

    def test_in_range(self):
        demo.RangeValidator(0, 100).ensure_valid(25)
    

Mặc dù bài kiểm tra thứ hai thực hiện công việc của mình, thất bại nếu ném ngoại lệ, và thành công nếu ensure_validkhông ném bất cứ thứ gì mà thực tế là không có gì assertbên trong trông lạ cả. Một người đọc mã như vậy sẽ ngay lập tức tự hỏi tại sao có một bài kiểm tra dường như không làm gì cả.

Đây có phải là cách làm hiện tại khi thử nghiệm các phương pháp không trả về giá trị và không có tác dụng phụ? Hay tôi nên viết lại bài kiểm tra theo một cách khác? Hoặc đơn giản là đặt một bình luận giải thích những gì tôi đang làm?



11
Điểm phạm vi, nhưng nếu bạn có một hàm không có đối số (lưu để selftham khảo) và không trả về kết quả, thì đó không phải là một hàm thuần túy.
David Arno

10
@DavidArno: Đây không phải là một điểm phạm vi, nó đi thẳng vào trọng tâm của câu hỏi: phương pháp này khó kiểm tra chính xác nó không trong sạch.
Jörg W Mittag

@DavidArno Thêm điểm phạm vi, bạn có thể có phương thức "không làm gì" (giả sử "trả về không có kết quả" được hiểu là "trả về một loại đơn vị" được gọi voidbằng nhiều ngôn ngữ và có các quy tắc ngớ ngẩn.) Ngoài ra, bạn có thể có vô hạn vòng lặp (hoạt động ngay cả khi "trả về không có kết quả" thực sự có nghĩa là không trả lại kết quả.
Derek Elkins rời SE

một hàm thuần túy của kiểu unit -> unittrong bất kỳ ngôn ngữ nào coi đơn vị là kiểu dữ liệu tiêu chuẩn thay vì thứ gì đó đặc biệt kỳ diệu. Thật không may, nó chỉ trả về đơn vị và không làm gì khác.
Phoshi

Câu trả lời:


21

Hầu hết các khung kiểm tra đều có xác nhận rõ ràng về "Không ném", ví dụ Jasmine có expect(() => {}).not.toThrow();và nUnit và bạn bè cũng có một xác nhận.


1
Và nếu khung kiểm tra không có xác nhận như vậy, thì luôn có thể tạo một phương thức cục bộ thực hiện chính xác điều tương tự, làm cho mã tự giải thích. Ý tưởng tốt.
Arseni Mourzenko

7

Điều này phụ thuộc mạnh mẽ vào ngôn ngữ và khuôn khổ được sử dụng. Nói về mặt NUnit, có Assert.Throws(...)phương pháp. Bạn có thể truyền cho họ một phương thức lambda:

Assert.Throws(() => rangeValidator.EnsureValid(-5))

được thực hiện trong Assert.Throws. Cuộc gọi đến lambda rất có thể sẽ được bao bọc bởi một try { } catch { }khối và xác nhận thất bại, nếu một ngoại lệ bị bắt.

Nếu khung của bạn không cung cấp các phương tiện này, bạn có thể xử lý nó bằng cách tự mình thực hiện cuộc gọi (Tôi đang viết bằng C #):

// assert that the method does not fail
try
{
    rangeValidator.EnsureValid(50);
}
catch(Exception e)
{
    Assert.IsTrue(false, $"Exception: {e.Message}");
}

// assert that the method does fail
try
{
    rangeValidator.EnsureValid(50);
    Assert.IsTrue(false, $"Method is expected to throw an exception");
}
catch(Exception e)
{
}

Điều này làm cho ý định rõ ràng hơn, nhưng làm mờ mã ở một mức độ nhất định. (Tất nhiên bạn có thể gói tất cả những thứ này trong một phương thức.) Cuối cùng, nó sẽ tùy thuộc vào bạn.

Biên tập

Như Doc Brown đã chỉ ra trong các bình luận, vấn đề không phải là chỉ ra rằng phương pháp ném, mà là nó không ném. Trong NUnit cũng có một khẳng định cho điều đó

Assert.DoesNotThrow(() => rangeValidator.EnsureValid(-5))

1

Chỉ cần thêm một nhận xét để làm rõ lý do tại sao không cần khẳng định và tại sao bạn không quên nó.

Như bạn có thể thấy trong các câu trả lời khác, bất cứ điều gì khác làm cho mã phức tạp hơn và lộn xộn hơn. Với nhận xét ở đó, các lập trình viên khác sẽ biết mục đích của bài kiểm tra.

Điều đó đang được nói, loại thử nghiệm này phải là một ngoại lệ (không có ý định chơi chữ). Nếu bạn thấy mình viết một cái gì đó như vậy thường xuyên, thì có lẽ các bài kiểm tra đang nói với bạn rằng thiết kế không tối ưu.


1

Bạn cũng có thể khẳng định rằng một số phương thức được gọi (hoặc không được gọi) đúng cách.

Ví dụ:

public void SendEmail(User user)
     ... construct email ...
     _emailSender.Send(email);

Trong bài kiểm tra của bạn:

emailSenderMock.VerifyIgnoreArgs(service =>
    service.Send(It.IsAny<Email>()),
    Times.Once
);
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.