2010-02-11 35 views
8

Nếu tôi có một phương thức tự gọi theo một điều kiện nhất định, có thể viết một bài kiểm tra để xác minh hành vi không? Tôi rất muốn xem một ví dụ, tôi không quan tâm đến khuôn khổ giả hoặc ngôn ngữ. Tôi đang sử dụng RhinoMocks trong C# vì vậy tôi tò mò nếu nó là một tính năng thiếu của khuôn khổ, hoặc nếu tôi hiểu lầm một cái gì đó cơ bản, hoặc nếu nó chỉ là một impossibility.Làm thế nào để viết một bài kiểm tra Mockist của một phương pháp đệ quy

+0

Nó không rõ ràng với tôi. Chính xác những gì đang cố gắng thử nghiệm? Đó là phương pháp tự gọi mình "trong điều kiện nhất định" (rằng "ngăn xếp cuộc gọi" sẽ theo một con đường nhất định "trong điều kiện nhất định") hoặc cái gì khác? – Ando

Trả lời

3

Giả sử bạn muốn làm một cái gì đó giống như lấy tên tập tin từ một đường dẫn đầy đủ, ví dụ:

c:/windows/awesome/lol.cs -> lol.cs 
c:/windows/awesome/yeah/lol.cs -> lol.cs 
lol.cs -> lol.cs 

và bạn có:

public getFilename(String original) { 
    var stripped = original; 
    while(hasSlashes(stripped)) { 
    stripped = stripped.substringAfterFirstSlash(); 
    } 
    return stripped; 
} 

và bạn muốn viết:

public getFilename(String original) { 
    if(hasSlashes(original)) { 
    return getFilename(original.substringAfterFirstSlash()); 
    } 
    return original; 
} 

Đệ quy ở đây là chi tiết triển khai và không được kiểm tra. Bạn thực sự muốn có thể chuyển đổi giữa hai triển khai và xác minh rằng chúng tạo ra cùng một kết quả: cả hai đều tạo ra lol.cs cho ba ví dụ trên. Điều đó đang được nói, bởi vì bạn đang đệ quy theo tên, thay vì nói điều nàyMethod.again() vv, trong Ruby bạn có thể bí danh phương thức gốc thành một tên mới, xác định lại phương thức với tên cũ, gọi hàm mới và kiểm tra xem bạn có kết thúc bằng phương thức mới được xác định hay không.

def blah 
    puts "in blah" 
    blah 
end 

alias blah2 blah 

def blah 
    puts "new blah" 
end 

blah2 
+0

Bạn có nói rằng trong trường hợp này, một bài kiểm tra đơn vị xác minh trạng thái của phương pháp là đủ tốt? – JeremyWeir

+0

xin lỗi, tôi không hiểu rõ câu hỏi của bạn. Trong ví dụ đường dẫn tệp của tôi, kiểm tra đơn vị xác minh đầu ra của phương thức là đủ hoặc thậm chí tốt hơn so với phương thức xác minh đệ quy. Tuy nhiên, tôi không biết tình huống cụ thể của bạn, vì vậy nó có thể khác. – miaubiz

+0

@jayrdub - Trong xác minh nhà nước nói chung là chính xác những gì bạn muốn thử nghiệm đơn vị của bạn để làm. Kiểm tra giá trị trả về của phương thức và/hoặc các thuộc tính công khai của đối tượng đang được kiểm tra. Mọi thứ khác là chi tiết triển khai và có thể thay đổi trong khi tái cấu trúc. – TrueWill

1

Không có gì để giám sát độ sâu ngăn xếp/số lượng các cuộc gọi hàm (đệ quy) trong bất kỳ khung mocking nào mà tôi biết. Tuy nhiên, đơn vị kiểm tra rằng các điều kiện trước giả lập thích hợp cung cấp các đầu ra chính xác phải giống như mô phỏng một hàm không đệ quy.

Đệ quy đệ quy dẫn đến tràn ngăn xếp, bạn sẽ phải gỡ lỗi riêng biệt, nhưng kiểm tra đơn vị và mocks chưa bao giờ loại bỏ nhu cầu đó ngay từ đầu.

6

một phương pháp mà các cuộc gọi riêng của mình dưới một điều kiện nhất định, là nó có thể viết một bài kiểm tra để xác minh hành vi?

Có. Tuy nhiên, nếu bạn cần kiểm tra đệ quy, bạn tốt hơn tách điểm vào vào đệ quy và bước đệ quy cho mục đích thử nghiệm.

Dù sao, đây là ví dụ về cách kiểm tra nếu bạn không thể làm điều đó. Bạn không thực sự cần bất kỳ chế nhạo nào:

// Class under test 
public class Factorial 
{ 
    public virtual int Calculate(int number) 
    { 
     if (number < 2) 
      return 1 
     return Calculate(number-1) * number; 
    } 
} 

// The helper class to test the recursion 
public class FactorialTester : Factorial 
{ 
    public int NumberOfCalls { get; set; } 

    public override int Calculate(int number) 
    { 
     NumberOfCalls++; 
     return base.Calculate(number) 
    } 
}  

// Testing 
[Test] 
public void IsCalledAtLeastOnce() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(1); 
    Assert.GreaterOrEqual(1, tester.NumberOfCalls ); 
} 
[Test] 
public void IsCalled3TimesForNumber3() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(3); 
    Assert.AreEqual(3, tester.NumberOfCalls ); 
} 
4

Bạn hiểu nhầm mục đích của các đối tượng giả. Mocks (theo nghĩa Mockist) được sử dụng để kiểm tra tương tác hành vi với các phụ thuộc của hệ thống đang được kiểm tra.

Vì vậy, ví dụ, bạn có thể có một cái gì đó như thế này:

interface IMailOrder 
{ 
    void OrderExplosives(); 
} 

class Coyote 
{ 
    public Coyote(IMailOrder mailOrder) {} 

    public void CatchDinner() {} 
} 

Coyote phụ thuộc vào IMailOrder. Trong mã sản xuất, một thể hiện của Coyote sẽ được thông qua một thể hiện của Acme, thực hiện IMailOrder. (Điều này có thể được thực hiện thông qua tính năng Chèn phụ thuộc thủ công hoặc thông qua khung DI.)

Bạn muốn thử phương thức CatchDinner và xác minh rằng phương thức đó gọi OrderExplosives.Để làm như vậy, bạn:

  1. Tạo đối tượng giả để triển khai IMailOrder và tạo một thể hiện của Coyote (hệ thống đang thử nghiệm) bằng cách truyền đối tượng giả cho công cụ xây dựng của nó. (Sắp xếp)
  2. Gọi CatchDinner. (Act)
  3. Yêu cầu đối tượng giả để xác minh rằng một kỳ vọng nhất định (OrderExplosives được gọi) đã được đáp ứng. (Assert)

Khi bạn thiết lập các kỳ vọng trên đối tượng giả có thể phụ thuộc vào khuôn mẫu mô phỏng (cách ly) của bạn.

Nếu lớp hoặc phương pháp bạn đang thử nghiệm không có phụ thuộc bên ngoài, bạn không cần (hoặc muốn) sử dụng các đối tượng giả cho tập hợp thử nghiệm đó. Nó không quan trọng nếu phương pháp này là đệ quy hay không.

Bạn thường muốn kiểm tra các điều kiện biên, vì vậy bạn có thể kiểm tra cuộc gọi không nên đệ quy, một cuộc gọi với một cuộc gọi đệ quy duy nhất và một cuộc gọi đệ quy sâu sắc. (miaubiz có một điểm tốt về đệ quy là một chi tiết thực hiện, mặc dù.)

EDIT: Bằng cách "gọi" trong đoạn cuối tôi có nghĩa là một cuộc gọi với các thông số hoặc trạng thái đối tượng sẽ kích hoạt độ sâu đệ quy nhất định. Tôi cũng khuyên bạn nên đọc The Art of Unit Testing.

EDIT 2: Ví dụ kiểm tra mã sử dụng Moq:

var mockMailOrder = new Mock<IMailOrder>(); 
var wily = new Coyote(mockMailOrder.Object); 

wily.CatchDinner(); 

mockMailOrder.Verify(x => x.OrderExplosives()); 
+0

"Nếu lớp hoặc phương pháp bạn đang thử nghiệm không có phụ thuộc bên ngoài, bạn không cần (hoặc muốn) sử dụng các đối tượng mô phỏng cho tập hợp các phép thử đó. Cho dù phương pháp này có đệ quy hay không." Đó là phần tôi cần được nhắc nhở, cảm ơn. Tôi thích câu trả lời của bạn tốt nhất nhưng nó tự động chọn trước khi tôi có thể. – JeremyWeir

+0

@jayrdub - Cảm ơn! :) – TrueWill

0

Dưới đây là 'nông dân' cách tiếp cận của tôi (trong Python, kiểm tra, xem các ý kiến ​​cho các lý do)

Lưu ý rằng việc thực hiện chi tiết "tiếp xúc" nằm ngoài câu hỏi ở đây, vì những gì bạn đang thử nghiệm là kiến ​​trúc cơ bản được sử dụng bởi mã "cấp cao nhất". Vì vậy, thử nghiệm nó là hợp pháp và cư xử tốt (tôi cũng hy vọng, đó là những gì bạn có trong tâm trí).

Mã này (ý tưởng chính là để đi từ một hàm đệ quy đơn nhưng "untestable" để một cặp tương đương với chức năng đệ quy phụ thuộc (và do đó kiểm chứng)):

def factorial(n): 
    """Everyone knows this functions contract:) 
    Internally designed to use 'factorial_impl' (hence recursion).""" 
    return factorial_impl(n, factorial_impl) 

def factorial_impl(n, fct=factorial): 
    """This function's contract is 
    to return 'n*fct(n-1)' for n > 1, or '1' otherwise. 

    'fct' must be a function both taking and returning 'int'""" 
    return n*fct(n - 1) if n > 1 else 1 

Bài kiểm tra:

import unittest 

class TestFactorial(unittest.TestCase): 

    def test_impl(self): 
     """Test the 'factorial_impl' function, 
     'wiring' it to a specially constructed 'fct'""" 

     def fct(n): 
      """To be 'injected' 
      as a 'factorial_impl''s 'fct' parameter""" 
      # Use a simple number, which will 'show' itself 
      # in the 'factorial_impl' return value. 
      return 100 

     # Here we must get '1'. 
     self.assertEqual(factorial_impl(1, fct), 1) 
     # Here we must get 'n*100', note the ease of testing:) 
     self.assertEqual(factorial_impl(2, fct), 2*100) 
     self.assertEqual(factorial_impl(3, fct), 3*100) 

    def test(self): 
     """Test the 'factorial' function""" 
     self.assertEqual(factorial(1), 1) 
     self.assertEqual(factorial(2), 2) 
     self.assertEqual(factorial(3), 6) 

sản lượng:

Finding files... 
['...py'] ... done 
Importing test modules ... done. 

Test the 'factorial' function ... ok 
Test the 'factorial_impl' function, ... ok 

---------------------------------------------------------------------- 
Ran 2 tests in 0.000s 

OK 
Các vấn đề liên quan