2010-08-12 38 views
8

Tôi đang trình bày một bài thuyết trình về lợi ích của Bài kiểm tra đơn vị và tôi muốn một ví dụ đơn giản về các hậu quả không mong muốn: Thay đổi mã trong một lớp phá vỡ chức năng trong lớp khác.Cần một ví dụ C# về những hậu quả không mong muốn

Ai đó có thể đề xuất đơn giản, dễ giải thích ví dụ về điều này?

Kế hoạch của tôi là viết các bài kiểm tra đơn vị xung quanh chức năng này để chứng minh rằng chúng tôi biết rằng chúng tôi đã phá vỡ điều gì đó bằng cách chạy thử nghiệm ngay lập tức.

+0

Ngôn ngữ câu hỏi này có bất khả tri không? – kbrimington

+0

@kbrimington C#. –

+0

C# được ưa thích, bởi vì đó là khán giả của tôi; Nhưng tôi có thể viết lại nó trong C#, đưa ra một ví dụ đơn giản, tốt. – dgiard

Trả lời

12

Một hơi đơn giản hơn, và do đó có lẽ rõ ràng hơn, ví dụ là:

public string GetServerAddress() 
{ 
    return "172.0.0.1"; 
} 

public void DoSomethingWithServer() 
{ 
    Console.WriteLine("Server address is: " + GetServerAddress()); 
} 

Nếu GetServerAddress là thay đổi để trả lại một mảng:

public string[] GetServerAddress() 
{ 
    return new string[] { "127.0.0.1", "localhost" }; 
} 

Kết quả từ DoSomethingWithServer sẽ có một chút khác nhau, nhưng nó tất cả sẽ vẫn biên dịch, làm cho một lỗi subtler thậm chí.

Phiên bản đầu tiên (không phải mảng) sẽ in Server address is: 127.0.0.1 và phiên bản thứ hai sẽ in Server address is: System.String[], đây là điều tôi cũng thấy trong mã sản xuất. Không cần phải nói nó không còn ở đó nữa!

+0

Làm thế nào trên trái đất bạn sẽ kiểm tra cho rằng mặc dù? Thay đổi giá trị trả về có thể bị bắt tại thời gian biên dịch (Ví dụ: không thể thực hiện 'String address = GetServerAddreess();'), nhưng việc bắt trong Strings gần như không thể là – TheLQ

+0

@TheLQ, nếu bạn đang viết mã: 'string serverAddress = GetServerAddress(); Console.WriteLine ("Địa chỉ máy chủ là:" + serverAddress); 'bạn sẽ nhận được một lỗi biên dịch trong ví dụ này =) Và, không có điểm đáng lo ngại về" mã tiết kiệm kém hiệu quả "như thể JIT không tối ưu hóa , Tôi sẽ rất ngạc nhiên * VÀ * lo lắng! :-) – Rob

8

Dưới đây là một ví dụ:

class DataProvider { 
    public static IEnumerable<Something> GetData() { 
     return new Something[] { ... }; 
    } 
} 

class Consumer { 
    void DoSomething() { 
     Something[] data = (Something[])DataProvider.GetData(); 
    } 
} 

Thay đổi GetData() để trả về một List<Something>, và Consumer sẽ phá vỡ.

Điều này có thể được nhìn thấy phần nào gây tranh cãi, nhưng tôi đã thấy các sự cố tương tự trong mã thực.

4

Giả sử bạn có một phương pháp có quyền này:

abstract class ProviderBase<T> 
{ 
    public IEnumerable<T> Results 
    { 
    get 
    { 
     List<T> list = new List<T>(); 
     using(IDataReader rdr = GetReader()) 
     while(rdr.Read()) 
      list.Add(Build(rdr)); 
     return list; 
    } 
    } 
    protected abstract IDataReader GetReader(); 
    protected T Build(IDataReader rdr); 
} 

Với việc triển khai khác nhau được sử dụng. Một trong số chúng được sử dụng trong:

public bool CheckNames(NameProvider source) 
{ 
    IEnumerable<string> names = source.Results; 
    switch(names.Count()) 
    { 
     case 0: 
     return true;//obviously none invalid. 
     case 1: 
     //having one name to check is a common case and for some reason 
     //allows us some optimal approach compared to checking many. 
     return FastCheck(names.Single()); 
     default: 
     return NormalCheck(names) 
    } 
} 

Hiện tại, không có điều gì đặc biệt lạ. Chúng tôi không giả định một triển khai cụ thể của IEnumerable. Thật vậy, điều này sẽ làm việc cho mảng và rất nhiều bộ sưu tập thường được sử dụng (không thể nghĩ ra một trong System.Collections.Generic mà không phù hợp với đỉnh đầu của tôi). Chúng tôi chỉ sử dụng các phương pháp thông thường và phương pháp mở rộng thông thường. Nó thậm chí không bình thường để có một trường hợp tối ưu hóa cho các bộ sưu tập một mục. Ví dụ, chúng ta có thể thay đổi danh sách thành một mảng, hoặc có thể là một HashSet (để tự động loại bỏ các bản sao), hoặc một LinkedList hoặc một vài thứ khác và nó sẽ tiếp tục hoạt động.

Tuy nhiên, mặc dù chúng tôi không phụ thuộc vào việc triển khai cụ thể, chúng tôi tùy thuộc vào một tính năng cụ thể, cụ thể là có thể quay lại được (Count() hoặc gọi ICollection.Count hoặc liệt kê thông qua số đếm, sau đó tên- .. kiểm tra sẽ diễn ra

có người dù thấy kết quả bất động sản và cho rằng "hmm, đó là một chút lãng phí" Họ thay thế nó bằng:

public IEnumerable<T> Results 
{ 
    get 
    { 
    using(IDataReader rdr = GetReader()) 
     while(rdr.Read()) 
     yield return Build(rdr); 
    } 
} 

này một lần nữa là hoàn toàn hợp lý, và thực sự sẽ dẫn đến một đáng kể hiệu suất tăng trong nhiều trường hợp.Nếu CheckNames không được nhấn trong "kiểm tra" ngay lập tức được thực hiện bởi bộ mã hóa được đề cập (có thể nó không được nhấn nhiều đường dẫn mã), thì thực tế là CheckNames sẽ lỗi (và có thể trả về kết quả sai trong trường hợp có hơn 1 tên, điều này thậm chí còn tệ hơn nếu nó mở ra một nguy cơ bảo mật).

Bất kỳ kiểm tra đơn vị nào truy cập trên CheckNames có kết quả nhiều hơn 0 sẽ bắt được nó.


Thay đổi tương tự (nếu phức tạp hơn) là lý do cho tính năng tương thích ngược trong NPGSQL. Không đơn giản như chỉ thay thế một List.Add() với một lợi nhuận trả về, nhưng một sự thay đổi theo cách mà ExecuteReader làm việc đã cho một sự thay đổi có thể so sánh từ O (n) đến O (1) để có được kết quả đầu tiên. Tuy nhiên, trước đó NpgsqlConnection cho phép người dùng có được một người đọc khác từ một kết nối trong khi người đầu tiên vẫn còn mở, và sau khi nó không. Các tài liệu cho IDbConnection cho biết bạn không nên làm điều này, nhưng điều đó không có nghĩa là không có mã chạy nào. May mắn là một đoạn mã chạy như vậy là một thử nghiệm NUnit, và một tính năng tương thích ngược được thêm vào để cho phép mã như vậy tiếp tục hoạt động chỉ với một thay đổi về cấu hình.

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