Mocking giới thiệu xử lý trong mã sản xuất


15

Giả sử giao diện IReader, triển khai giao diện IReader ReaderImcellenceation và một lớp ReaderConsumer tiêu thụ và xử lý dữ liệu từ đầu đọc.

public interface IReader
{
     object Read()
}

Thực hiện

public class ReaderImplementation
{
    ...
    public object Read()
    {
        ...
    }
}

Khách hàng:

public class ReaderConsumer()
{
    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // read some data
    public object ReadData()
    {
        IReader reader = new ReaderImplementation(this.location)
        data = reader.Read()
        ...
        return processedData    
    }
}

Để kiểm tra ReaderConsumer và quá trình xử lý, tôi sử dụng một mock của IReader. Vì vậy, ReaderConsumer trở thành:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer()
    {
        ...
    }

    // mock constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader
    }

    // read some data
    public object ReadData()
    {
        try
        {
            if(this.reader == null)
            {
                 this.reader = new ReaderImplementation(this.location)
            }

            data = reader.Read()
            ...
            return processedData    
        }
        finally
        {
            this.reader = null
        }
    }
}

Trong giải pháp này, mocking giới thiệu một câu if cho mã sản xuất vì chỉ có hàm tạo mô phỏng cung cấp một thể hiện của giao diện.

Trong quá trình viết này, tôi nhận ra rằng khối thử cuối cùng có phần không liên quan vì nó ở đó để xử lý người dùng thay đổi vị trí trong thời gian chạy ứng dụng.

Nhìn chung, nó cảm thấy có mùi, làm thế nào nó có thể được xử lý tốt hơn?


16
Thông thường, đây không phải là vấn đề vì hàm tạo có phụ thuộc được chèn, sẽ là hàm tạo duy nhất. Là nó ra khỏi câu hỏi để làm cho ReaderConsumerđộc lập trên ReaderImplementation?
Chris Wohlert

Hiện tại sẽ khó để loại bỏ sự phụ thuộc. Bằng cách nhìn vào nó nhiều hơn một chút, tôi có một vấn đề sâu sắc hơn là sự phụ thuộc vào ReaderImplemenatation. Vì ReaderConsumer được tạo trong quá trình khởi động từ một nhà máy, tồn tại trong suốt vòng đời của ứng dụng và chấp nhận các thay đổi từ người dùng, nó yêu cầu một số thao tác xoa bóp thêm. Có lẽ đầu vào cấu hình / người dùng có thể tồn tại dưới dạng một đối tượng và sau đó ReaderConsumer và ReaderImcellenceation có thể được tạo ra một cách nhanh chóng. Cả hai câu trả lời đưa ra giải quyết trường hợp chung chung khá tốt.
kristian mo

3
. Đây là điểm của TDD: trước tiên phải viết bài kiểm tra ngụ ý một thiết kế tách rời hơn (nếu không bạn không thể viết bài kiểm tra đơn vị ...). Điều này giúp làm cho mã dễ bảo trì hơn và cũng có thể mở rộng.
Bakuriu

Một cách tốt để phát hiện mùi có thể được giải quyết bằng Dependency Injection, là tìm từ khóa 'mới'. Đừng mới phụ thuộc vào bạn. Thay vào đó hãy tiêm chúng.
Eternal21

Câu trả lời:


67

Thay vì khởi tạo trình đọc từ phương thức của bạn, hãy di chuyển dòng này

{
    this.reader = new ReaderImplementation(this.location)
}

Vào hàm tạo không tham số mặc định.

public ReaderConsumer()
{
    this.reader = new ReaderImplementation(this.location)
}

public ReaderConsumer(IReader reader)
{
    this.reader = reader
}

Không có thứ gọi là "hàm tạo giả", nếu lớp của bạn có một phụ thuộc mà nó yêu cầu để hoạt động, thì hàm tạo nên được cung cấp thứ đó hoặc tạo nó.


3
điểm ++ ... ngoại trừ từ quan điểm DI, hàm tạo mặc định đó chắc chắn là mùi mã.
Mathieu Guindon

3
@ Mat'sMug không có gì sai với việc triển khai mặc định. Việc thiếu ctor xích là mùi ở đây. =;) -
RubberDuck

Ồ, vâng, có - nếu bạn không có cuốn sách này , bài viết này dường như mô tả kiểu chống tiêm chích khốn khá tốt (mặc dù chỉ lướt qua).
Mathieu Guindon

3
@ Mat'sMug: Bạn đang diễn giải DI một cách giáo điều. Nếu bạn không cần tiêm hàm tạo mặc định, thì bạn không cần.
Robert Harvey

3
Cuốn sách @ Mat'sMug được liên kết cũng nói về "mặc định chấp nhận được" cho các phụ thuộc là tốt (mặc dù nó đề cập đến việc tiêm tài sản). Chúng ta hãy giải thích theo cách này: cách tiếp cận hai nhà xây dựng tốn kém hơn về tính đơn giản và khả năng bảo trì so với phương pháp một nhà xây dựng, và với câu hỏi được đơn giản hóa quá mức cho rõ ràng, chi phí không được chứng minh, nhưng trong một số trường hợp có thể .
Carl Leth

54

Bạn chỉ cần hàm tạo duy nhất:

public class ReaderConsumer()
{
    private IReader reader = null

    public string location

    // constructor
    public ReaderConsumer(IReader reader)
    {
        this.reader = reader;
    }

trong mã sản xuất của bạn:

var rc = new ReaderConsumer(new ReaderImplementation(0));

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

var rc = new ReaderConsumer(new MockImplementation(0));

13

Nhìn vào tiêm phụ thuộc và đảo ngược kiểm soát

Cả Ewan và RubberDuck đều có câu trả lời tuyệt vời. Nhưng tôi muốn đề cập đến một lĩnh vực khác để xem xét đó là Dependency Injection (DI) và Inversion of Control (IoC). Cả hai cách tiếp cận này đều đưa vấn đề bạn gặp phải vào một khung / thư viện để bạn không phải lo lắng về nó.

Ví dụ của bạn rất đơn giản và nhanh chóng được phân phối, nhưng, chắc chắn bạn sẽ xây dựng nó và bạn sẽ kết thúc với hàng tấn các hàm tạo hoặc các thói quen khởi tạo giống như:

var foo = new Foo (Thanh mới (mới Baz (), Quz mới ()), Foo2 ()) mới;

Với DI / IoC, bạn sử dụng một thư viện cho phép bạn đánh vần các quy tắc để khớp giao diện với các triển khai và sau đó bạn chỉ cần nói "Hãy cho tôi một Foo" và nó tìm ra cách nối tất cả.

Có rất nhiều Container IoC rất thân thiện (như chúng được gọi) ngoài đó, và tôi sẽ khuyên bạn nên xem xét, nhưng, vui lòng khám phá, vì có rất nhiều lựa chọn tốt.

Một cách đơn giản để bắt đầu là:

http://www.ninject.org/

Dưới đây là danh sách để khám phá:

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx


7
Cả hai câu trả lời của Ewan và RubberDuck đều chứng minh DI. DI / IoC không phải là về một công cụ hoặc khung, mà là về kiến ​​trúc và cấu trúc mã theo cách mà điều khiển được đảo ngược và các phụ thuộc được đưa vào. Cuốn sách của Mark Seemann thực hiện một công việc tuyệt vời (tuyệt vời!) Trong việc giải thích làm thế nào DI / IoC hoàn toàn có thể đạt được mà không cần khung IoC, và tại saokhi khung IoC trở thành một ý tưởng hay (gợi ý: khi bạn có sự phụ thuộc của phụ thuộc vào .. .) =)
Mathieu Guindon

Ngoài ra ... +1 vì Ninject rất tuyệt và khi đọc lại câu trả lời của bạn có vẻ ổn. Đoạn đầu tiên đọc hơi giống câu trả lời của Ewan và RubberDuck không phải về DI, đó là điều khiến tôi nhận xét đầu tiên.
Mathieu Guindon

1
@ Mat'sMug Chắc chắn nhận được điểm của bạn. Tôi đã cố gắng khuyến khích OP xem xét DI / IoC mà các áp phích khác đang sử dụng (Ewan là Poor Man's DI), nhưng không đề cập đến. Tất nhiên, lợi thế của việc sử dụng khung là nó sẽ đưa người dùng vào thế giới DI với rất nhiều ví dụ cụ thể, hy vọng sẽ hướng dẫn họ hiểu những gì họ đang làm ... mặc dù ... không phải lúc nào cũng vậy. :-) Và, tất nhiên, tôi yêu cuốn sách của Mark Seemann. Đây là một trong những điều tôi thích nhất.
Reginald Blue

Cảm ơn bạn về mẹo về khung DI, nó dường như là cách để cấu trúc lại mớ hỗn độn này một cách chính xác :)
kristian mo
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.