2015-06-05 18 views
10

Do một vài hạn chế, tôi không thể sử dụng Khung thực thể và do đó cần sử dụng các kết nối SQL, lệnh và giao dịch theo cách thủ công.Thử nghiệm đơn vị với các giao dịch thủ công và giao dịch theo lớp

Trong khi viết kiểm tra đơn vị cho các phương thức gọi các hoạt động lớp dữ liệu này, tôi đã gặp phải một số vấn đề.

Đối với các bài kiểm tra đơn vị I NEED để thực hiện chúng trong Giao dịch vì hầu hết các thao tác đang thay đổi dữ liệu theo bản chất của chúng và do đó thực hiện chúng bên ngoài Giao dịch có vấn đề. Vì vậy, tôi cần phải đặt một giao dịch xung quanh những (không có cam kết bắn vào cuối).

Bây giờ tôi có 2 biến thể khác nhau về cách thức hoạt động của các phương thức BL này. Một số ít có Giao dịch bên trong chúng trong khi một số khác không có Giao dịch. Cả hai biến thể này đều gây ra vấn đề.

  • giao dịch Layered: Ở đây tôi nhận được lỗi rằng DTC hủy các giao dịch phân phối do hết giờ (mặc dù thời gian chờ đã được thiết lập để 15 phút và nó đang chạy chỉ 2 phút).

  • Chỉ 1 giao dịch: Ở đây tôi gặp lỗi về trạng thái Giao dịch khi tôi đến đường dây "new SQLCommand" trong phương thức được gọi.

Câu hỏi của tôi ở đây là những gì tôi có thể làm để sửa lỗi này và kiểm tra đơn vị với các giao dịch theo lớp bình thường và thủ công?

Unit phương pháp thử nghiệm ví dụ:

using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString)) 
{ 
    connection.Open(); 
    using (SqlTransaction transaction = connection.BeginTransaction()) 
    { 
     MyBLMethod(); 
    } 
} 

Ví dụ cho một giao dịch sử dụng phương pháp (rất đơn giản)

using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString)) 
{ 
    connection.Open(); 
    using (SqlTransaction transaction = connection.BeginTransaction()) 
    { 
     SqlCommand command = new SqlCommand(); 
     command.Connection = connection; 
     command.Transaction = transaction; 
     command.CommandTimeout = 900; // Wait 15 minutes before a timeout 
     command.CommandText = "INSERT ......"; 
     command.ExecuteNonQuery(); 

     // Following commands 
     .... 

     Transaction.Commit(); 
    } 
} 

Ví dụ cho một giao dịch không sử dụng phương pháp

using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString)) 
{ 
    connection.Open(); 

    SqlCommand command = new SqlCommand(); 
    command.Connection = connection; 
    command.CommandTimeout = 900; // Wait 15 minutes before a timeout 
    command.CommandText = "INSERT ......"; 
    command.ExecuteNonQuery(); 
} 
+1

Vì bạn phải sử dụng 'SqlConnection', bạn không thể kiểm tra đơn vị; bạn đang thực hiện các thử nghiệm tích hợp. Vì vậy, thay vì làm tất cả mọi thứ nhưng cam kết, sử dụng một DB thử nghiệm (mà bạn có thể xây dựng afresh lúc bắt đầu chạy thử nghiệm) và chỉ cần kiểm tra tương tác DB. –

+0

Những gì anh ta nói. Bạn nên sử dụng các snapshot cơ sở dữ liệu để tạo ra dữ liệu được biết rõ về từng dữ liệu đã biết. – Ewan

+0

Điều đó có nghĩa là sau đó tôi sẽ cần tạo lại một cơ sở dữ liệu toàn bộ (có thể mở rộng rất nhiều dữ liệu) cho mỗi lần chạy thử nghiệm đơn vị? b (có thể tăng gấp đôi hoặc gấp ba lần thời gian chạy sau đó đến một vài phút) – Thomas

Trả lời

4

Trên khuôn mặt của nó, bạn có một vài lựa chọn, tùy thuộc vào những gì bạn muốn kiểm tra và khả năng của bạn để SPE nd money/thay đổi mã cơ sở của bạn.

Hiện tại, bạn đang viết bài kiểm tra tích hợp hiệu quả. Nếu cơ sở dữ liệu không có sẵn thì thử nghiệm của bạn sẽ không thành công. Điều này có nghĩa là các thử nghiệm có thể chậm, nhưng về mặt cộng nếu chúng vượt qua bạn khá tự tin rằng mã của bạn có thể nhấn đúng cơ sở dữ liệu.

Nếu bạn không ngại nhấn vào cơ sở dữ liệu, thì tác động tối thiểu để thay đổi mã/chi tiêu của bạn sẽ cho phép bạn giao dịch hoàn tất và xác minh chúng trong cơ sở dữ liệu. Bạn có thể thực hiện điều này bằng cách lấy các snapshot cơ sở dữ liệu và thiết lập lại cơ sở dữ liệu cho mỗi lần chạy thử hoặc bằng cách sử dụng một cơ sở dữ liệu thử nghiệm chuyên dụng và viết các bài kiểm tra của bạn theo cách mà họ có thể an toàn truy cập vào cơ sở dữ liệu lặp đi lặp lại. Vì vậy, ví dụ, bạn có thể chèn một bản ghi với một id tăng dần, cập nhật bản ghi, và sau đó xác minh rằng nó có thể được đọc. Bạn có thể muốn giải quyết nhiều hơn nếu có lỗi, nhưng nếu bạn không sửa đổi mã truy cập dữ liệu hoặc cấu trúc cơ sở dữ liệu thường thì đây không phải là quá nhiều vấn đề.

Nếu bạn có thể chi tiêu một số tiền và bạn muốn thực sự chuyển các bài kiểm tra của mình thành các bài kiểm tra đơn vị, để chúng không nhấn vào cơ sở dữ liệu, thì bạn nên cân nhắc xem xét TypeMock.Đó là một khuôn khổ mocking rất mạnh mẽ có thể làm một số công cụ khá đáng sợ. Tôi tin rằng nó sử dụng API lược tả để chặn các cuộc gọi, thay vì sử dụng cách tiếp cận được sử dụng bởi các khung công tác như Moq. Có một ví dụ về việc sử dụng Typemock để giả lập một SQLConnection here.

Nếu bạn không có tiền để chi tiêu/bạn có thể thay đổi mã và không ngại tiếp tục dựa vào cơ sở dữ liệu thì bạn cần xem xét một số cách để chia sẻ kết nối cơ sở dữ liệu giữa mã thử nghiệm của bạn và các phương pháp truyền dữ liệu của bạn. Hai cách tiếp cận mà bạn cần biết là tiêm thông tin kết nối vào lớp hoặc làm cho nó có sẵn bằng cách tiêm một nhà máy cung cấp quyền truy cập vào thông tin kết nối (trong trường hợp này bạn có thể tiêm một mô hình của nhà máy trong quá trình thử nghiệm trả về kết nối bạn muốn).

Nếu bạn làm theo cách tiếp cận trên, thay vì tiêm trực tiếp SqlConnection, hãy xem xét tiêm một lớp bao bọc cũng chịu trách nhiệm cho giao dịch. Một cái gì đó như:

public class MySqlWrapper : IDisposable { 
    public SqlConnection Connection { get; set; } 
    public SqlTransaction Transaction { get; set; } 

    int _transactionCount = 0; 

    public void BeginTransaction() { 
     _transactionCount++; 
     if (_transactionCount == 1) { 
      Transaction = Connection.BeginTransaction(); 
     } 
    } 

    public void CommitTransaction() { 
     _transactionCount--; 
     if (_transactionCount == 0) { 
      Transaction.Commit(); 
      Transaction = null; 
     } 
     if (_transactionCount < 0) { 
      throw new InvalidOperationException("Commit without Begin"); 
     } 
    } 

    public void Rollback() { 
     _transactionCount = 0; 
     Transaction.Rollback(); 
     Transaction = null; 
    } 


    public void Dispose() { 
     if (null != Transaction) { 
      Transaction.Dispose(); 
      Transaction = null; 
     } 
     Connection.Dispose(); 
    } 
} 

Điều này sẽ ngừng giao dịch lồng nhau được tạo + cam kết.

Nếu bạn sẵn sàng hơn để tái cấu trúc mã của bạn, sau đó bạn có thể muốn gói mã DATAACCESS của bạn một cách mockable hơn. Vì vậy, ví dụ bạn có thể đẩy chức năng truy cập cơ sở dữ liệu lõi của bạn vào một lớp khác. Tùy thuộc vào những gì bạn đang làm, bạn sẽ cần phải mở rộng trên đó, tuy nhiên bạn có thể kết thúc với một cái gì đó như thế này:

public interface IMyQuery { 
    string GetCommand(); 
} 

public class MyInsert : IMyQuery{ 
    public string GetCommand() { 
     return "INSERT ..."; 
    } 
} 

class DBNonQueryRunner { 
    public void RunQuery(IMyQuery query) { 
     using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString)) { 
      connection.Open(); 
      using (SqlTransaction transaction = connection.BeginTransaction()) { 
       SqlCommand command = new SqlCommand(); 
       command.Connection = connection; 
       command.Transaction = transaction; 
       command.CommandTimeout = 900; // Wait 15 minutes before a timeout 
       command.CommandText = query.GetCommand(); 

       command.ExecuteNonQuery(); 

       transaction.Commit(); 
      } 
     } 
    } 
} 

này cho phép bạn kiểm tra đơn vị hơn của logic của bạn, giống như mã thế hệ chỉ huy, mà không cần phải lo lắng về việc nhấn vào cơ sở dữ liệu và bạn có thể kiểm tra mã dữ liệu cốt lõi của bạn (Runner) đối với cơ sở dữ liệu một lần, thay vì cho mỗi lệnh bạn muốn chạy với cơ sở dữ liệu. Tôi vẫn sẽ viết các bài kiểm tra tích hợp cho tất cả các mã dataaccess, nhưng tôi chỉ có xu hướng chạy chúng trong khi thực sự làm việc trên phần mã đó (để đảm bảo tên cột vv đã được chỉ định chính xác).

+0

+1 trên những gì @forsvarir nói. Bạn luôn có thể kiểm tra mọi thứ bằng cách thêm một lớp khác của indirection :) Nhưng hey, có TypeMock quá, nếu bạn ưa thích điều đó. –

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