Làm thế nào để đơn vị kiểm tra một chức năng được tái cấu trúc theo mẫu chiến lược?


10

Nếu tôi có một chức năng trong mã của mình như sau:

class Employee{

    public string calculateTax(string name, int salary)
    {
        switch (name)
        {
            case "Chris":
                doSomething($salary);
            case "David":
                doSomethingDifferent($salary);
            case "Scott":
               doOtherThing($salary);               
       }
}

Thông thường tôi sẽ cấu trúc lại cái này để sử dụng Ploymorphism bằng cách sử dụng một lớp chiến lược và mô hình nhà máy:

public string calculateTax(string name)
{
    InameHandler nameHandler = NameHandlerFactory::getHandler(name);
    nameHandler->calculateTax($salary);
}

Bây giờ nếu tôi đang sử dụng TDD thì tôi sẽ có một số thử nghiệm hoạt động trên bản gốc calculateTax()trước khi tái cấu trúc.

Ví dụ:

calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}    
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}

calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}   
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){} 

calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}

Sau khi tái cấu trúc tôi sẽ có một lớp Factory NameHandlerFactoryvà ít nhất 3 lần thực hiện InameHandler.

Làm thế nào tôi nên tiến hành cấu trúc lại các bài kiểm tra của mình? Tôi có nên xóa bài kiểm tra đơn vị claculateTax()từ EmployeeTestsvà tạo một lớp Kiểm tra cho mỗi lần thực hiện InameHandlerkhông?

Tôi có nên kiểm tra lớp Factory không?

Câu trả lời:


6

Các xét nghiệm cũ chỉ tốt để xác minh rằng calculateTaxvẫn hoạt động như bình thường. Tuy nhiên, bạn không cần nhiều trường hợp kiểm tra cho việc này, chỉ 3 trường hợp (hoặc có thể một số trường hợp khác, nếu bạn cũng muốn kiểm tra xử lý lỗi, sử dụng các giá trị không mong muốn của name).

Mỗi trường hợp riêng lẻ (tại thời điểm được triển khai trong doSomethinget al.) Cũng phải có bộ thử nghiệm riêng, thử nghiệm các chi tiết bên trong và các trường hợp đặc biệt liên quan đến mỗi lần thực hiện. Trong thiết lập mới, các thử nghiệm này có thể / nên được chuyển đổi thành các thử nghiệm trực tiếp trên lớp Chiến lược tương ứng.

Tôi thích loại bỏ các bài kiểm tra đơn vị cũ chỉ khi mã mà chúng thực hiện và chức năng mà nó thực hiện, hoàn toàn không còn tồn tại. Mặt khác, kiến ​​thức được mã hóa trong các bài kiểm tra này vẫn có liên quan, chỉ có các bài kiểm tra cần được tự cấu trúc lại.

Cập nhật

Có thể có một số trùng lặp giữa các thử nghiệm của calculateTax(hãy gọi chúng là các thử nghiệm cấp cao ) và các thử nghiệm cho các chiến lược tính toán riêng lẻ ( thử nghiệm cấp độ thấp ) - tùy thuộc vào việc thực hiện của bạn.

Tôi đoán việc triển khai ban đầu các thử nghiệm của bạn khẳng định kết quả của việc tính thuế cụ thể, ngầm xác minh rằng chiến lược tính toán cụ thể đã được sử dụng để sản xuất nó. Nếu bạn giữ lược đồ này, bạn sẽ có sự trùng lặp thực sự. Tuy nhiên, như @Kristof gợi ý, bạn cũng có thể thực hiện các bài kiểm tra cấp cao bằng cách sử dụng giả, để chỉ xác minh rằng loại chiến lược (giả) phù hợp đã được chọn và được gọi bởi calculateTax. Trong trường hợp này sẽ không có sự trùng lặp giữa các bài kiểm tra cấp cao và cấp thấp.

Vì vậy, nếu tái cấu trúc các bài kiểm tra bị ảnh hưởng không quá tốn kém, tôi thích cách tiếp cận sau. Tuy nhiên, trong cuộc sống thực, khi thực hiện một số phép tái cấu trúc lớn, tôi chấp nhận một số lượng nhỏ mã sao chép thử nghiệm nếu nó giúp tôi tiết kiệm đủ thời gian :-)

Tôi có nên kiểm tra lớp Factory không?

Một lần nữa, nó phụ thuộc. Lưu ý rằng các bài kiểm tra calculateTaxhiệu quả của nhà máy. Vì vậy, nếu mã nhà máy là một switchkhối tầm thường như mã của bạn ở trên, những thử nghiệm này có thể là tất cả những gì bạn cần. Nhưng nếu nhà máy thực hiện một số điều khó khăn hơn, bạn có thể muốn dành một số thử nghiệm dành riêng cho nó. Tất cả tập trung vào số lượng thử nghiệm bạn cần để tự tin rằng mã được đề cập thực sự hoạt động. Nếu, khi đọc mã - hoặc phân tích dữ liệu bao phủ mã - bạn thấy các đường dẫn thực thi chưa được kiểm tra, hãy dành thêm một số thử nghiệm để thực hiện các thử nghiệm này. Sau đó lặp lại điều này cho đến khi bạn hoàn toàn tự tin vào mã của mình.


Tôi đã sửa đổi mã một chút để làm cho nó gần hơn với mã thực tế thực tế của tôi. Bây giờ một đầu vào thứ hai salarycho chức năng calculateTax()đã được thêm vào. Theo cách này, tôi nghĩ rằng tôi sẽ sao chép mã kiểm tra cho chức năng ban đầu và 3 triển khai của lớp chiến lược.
Songo

@Songo, vui lòng xem cập nhật của tôi.
Péter Török

5

Tôi sẽ bắt đầu bằng cách nói rằng tôi không phải là chuyên gia về TDD hoặc kiểm tra đơn vị, nhưng đây là cách tôi sẽ kiểm tra điều này (Tôi sẽ sử dụng mã giống như giả):

CalculateTaxDelegatesToNameHandler()
{
    INameHandlerFactory fakeNameHandlerFactory = Fake(INameHandlerFactory);
    INameHandler fakeNameHandler = Fake(INameHandler);

    A.Call.To(fakeNameHandlerFactory.getHandler("John")).Returns(fakeNameHandler);

    Employee employee = new Employee(fakeNameHandlerFactory);
    employee.CalculateTax("John");

    Assert.That.WasCalled(fakeNameHandler.calculateTax());
}

Vì vậy, tôi đã kiểm tra rằng calculateTax()phương thức của lớp nhân viên yêu cầu chính xác NameHandlerFactorycho nó NameHandlervà sau đó gọi calculateTax()phương thức trả về NameHandler.


hmmmm vì vậy, ý bạn là tôi nên thực hiện kiểm tra một bài kiểm tra hành vi (kiểm tra rằng các hàm nhất định đã được gọi) và thực hiện các xác nhận giá trị trên các lớp được ủy quyền?
Songo

Vâng, đó là những gì tôi sẽ làm. Tôi thực sự sẽ viết các bài kiểm tra riêng cho NameHandlerFactory và NameHandler. Khi bạn có những thứ đó, không có lý do gì để kiểm tra lại chức năng của chúng trong Employee.calculateTax()phương thức. Bằng cách đó, bạn không cần phải thêm các bài kiểm tra nhân viên khi bạn giới thiệu một NameHandler mới.
Kristof Claes

3

Bạn đang học một lớp (nhân viên làm mọi thứ) và tạo 3 nhóm lớp: nhà máy, nhân viên (chỉ chứa một chiến lược) và các chiến lược.

Vì vậy, thực hiện 3 nhóm thử nghiệm:

  1. Kiểm tra nhà máy trong sự cô lập. Liệu nó xử lý đầu vào chính xác. Điều gì xảy ra khi bạn vượt qua trong một ẩn số?
  2. Kiểm tra nhân viên trong sự cô lập. Bạn có thể thiết lập một chiến lược tùy ý và nó hoạt động như bạn mong đợi? Điều gì xảy ra nếu không có chiến lược hoặc nhà máy đặt ra? (nếu điều đó có thể trong mã)
  3. Kiểm tra các chiến lược trong sự cô lập. Có phải mỗi người thực hiện chiến lược mà bạn mong đợi? Họ có xử lý đầu vào ranh giới lẻ một cách nhất quán?

Tất nhiên bạn có thể thực hiện các bài kiểm tra tự động cho toàn bộ shebang, nhưng giờ đây chúng giống như các bài kiểm tra tích hợp và nên được xử lý như vậy.


2

Trước khi viết bất kỳ mã nào, tôi sẽ bắt đầu với một bài kiểm tra cho một Nhà máy. Chế giễu những thứ tôi cần tôi sẽ buộc bản thân phải suy nghĩ về việc triển khai và sử dụng.

Hơn tôi sẽ triển khai một Nhà máy và tiếp tục với một thử nghiệm cho mỗi lần thực hiện và cuối cùng là chính các triển khai đó cho các thử nghiệm đó.

Cuối cùng tôi sẽ loại bỏ các bài kiểm tra cũ.


2

Ý kiến ​​của tôi là bạn không nên làm gì, có nghĩa là bạn không nên thêm bất kỳ bài kiểm tra mới nào.

Tôi nhấn mạnh rằng đây là một ý kiến, và nó thực sự phụ thuộc vào cách bạn nhận thức những kỳ vọng từ đối tượng. Bạn có nghĩ rằng người dùng của lớp muốn cung cấp một chiến lược để tính thuế? Nếu anh ta không quan tâm, thì các bài kiểm tra sẽ phản ánh điều đó và hành vi được phản ánh từ các bài kiểm tra đơn vị là họ không nên quan tâm rằng lớp đã bắt đầu sử dụng một đối tượng chiến lược để tính thuế.

Tôi thực sự gặp vấn đề này nhiều lần khi sử dụng TDD. Tôi nghĩ lý do chính là một đối tượng chiến lược không phải là một phụ thuộc tự nhiên, trái ngược với việc nói một phụ thuộc ranh giới kiến ​​trúc như một tài nguyên bên ngoài (một tệp, DB, một dịch vụ từ xa, v.v.). Vì nó không phải là một sự phụ thuộc tự nhiên, tôi thường không dựa trên hành vi của lớp mình trong chiến lược này. Bản năng của tôi là tôi chỉ nên thay đổi các bài kiểm tra của mình nếu kỳ vọng từ lớp học của tôi đã thay đổi.

Có một bài viết tuyệt vời từ chú Bob, nói chính xác về vấn đề này khi sử dụng TDD.

Tôi nghĩ rằng xu hướng kiểm tra từng lớp riêng biệt là điều đang giết chết TDD. Toàn bộ vẻ đẹp của TDD là bạn sử dụng các bài kiểm tra để thúc đẩy các kế hoạch thiết kế chứ không phải ngược lại.

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.