2017-02-24 6 views
1

以下の従業員モデルクラスとコンソールクライアントがあります。.NETのデータ挿入シナリオでNunitテストケースを行うためのガイダンスが必要

Employeeクラス: -

public class Employee 
{ 
    public int EmployeeId { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 
    public int phoneNumber { get; set; } 

    public Employee() 
    { 

    } 

    public Employee(string fname,string lname,int age,int phone) 
    {    
     this.FirstName = fname; 
     this.LastName = lname; 
     this.Age = age; 
     this.phoneNumber = phone; 
    } 

    public void InsertEmployee() 
    { 
     SqlConnection con = new SqlConnection("sqlconnection"); 
     SqlCommand cmd = new SqlCommand("sp_insert", con); 
     cmd.CommandType = CommandType.StoredProcedure; 
     cmd.Parameters.AddWithValue("fname", this.FirstName); 
     cmd.Parameters.AddWithValue("lname", this.LastName); 
     cmd.Parameters.AddWithValue("age", this.Age); 
     cmd.Parameters.AddWithValue("phoneno",this.phoneNumber);    
     con.Open(); 
     cmd.ExecuteNonQuery();    
     con.Close(); 
    } 

    public List<Employee> GetAllEmployees() 
    { 
     SqlConnection connection = new SqlConnection("sqlconnection"); 
     SqlCommand cmd = new SqlCommand("GetAllEmployees", connection); 
     cmd.CommandType = System.Data.CommandType.StoredProcedure; 
     connection.Open(); 
     SqlDataReader dr = cmd.ExecuteReader(); 
     List<Employee> employeeList = new List<Employee>();   
     while (dr.Read()) 
     { 
      Employee emp = new Employee(); 
      emp.EmployeeId = int.Parse(dr["empID"].ToString()); 
      emp.FirstName = dr["fname"].ToString(); 
      emp.LastName = dr["lname"].ToString(); 
      emp.Age= int.Parse(dr["age"].ToString()); 
      emp.phoneNumber= int.Parse(dr["phone"].ToString()); 
      employeeList.Add(emp); 
     } 
     return employeeList; 
    } 
} 

******Client code**** 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Employee newEmp = new Employee("Ram", "Prem", 30, 90000007); 
     newEmp.InsertEmployee(); 
     List<Employee> empList = newEmp.GetAllEmployees(); 
    } 
} 

******************** 

上記のコードは動作し、それは結構です。

今、私はInsertメソッドとFetchメソッドのNunitテストメソッドを書くように言われました。

はどのようにして、以下の条件で挿入するためにNUnitのテストメソッドを書くことができます: - マニュアルvalidation.Itは、NUnitのテストの一部であるべきではありませんdatabase.Thereに挿入されてしまった供給いままでの値を確保する方法
1)。
2)新しい列がテーブルに導入された場合。

EmployeeモデルでCityプロパティが追加され、Cityパラメータがパラメータとして渡されました。

新しい列のCity Nullable列がテーブルに追加され、Insertストアドプロシージャでは、開発者がinsert文に新しい列を追加しましたが、Cityパラメータがプロシージャに追加されました。 NUnitのテストは、それは市がテーブルに挿入されていないです(このバグを特定する方法?上記の条件でテストするためにNUnitのテストメソッドを記述するために

どのようにこの上記のシナリオでは

+0

このコードの設計はテストが難しく、プロジェクトが成熟するにつれ悪くなるだけです。 SOLID原則をレビューし、このコードをリファクタリングします。 – Nkosi

答えて

0

まず第一に、あなたはとてもあなたのコードでユニットテストを実行することができリポジトリパターンを利用するために必要があるだろう

あなたが従業員に関連する実行する操作を定義するインタフェースIEmployeeRepositoryを作成します。

public interface IEmployeeRepository { 

    void Insert(Employee employee); 
    List<Employee> GetAll(); 

} 

そのインターフェイスから継承しなければなりませんEmployeeRepositoryクラスを作成し、あなたが明示的に定義された機能を実装する:あなたはちょうどそのプロパティを定義し

public class EmployeeRepository : IEmployeeRepository { 

    public void Insert(Employee employee){ 
    // Your code to insert an employee from the db. 
    } 

    public List<Employee> GetAll(){ 
    // Your code to get all the employees from the db. 
    } 

} 

だから、このように、Employeeクラスがあります

public class Employee { 

     public int EmployeeId { get; set; } 
     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public int Age { get; set; } 
     public int phoneNumber { get; set; } 

     public Employee() 
     { 

     } 

     public Employee(string fname,string lname,int age,int phone) 
     {    
      this.FirstName = fname; 
      this.LastName = lname; 
      this.Age = age; 
      this.phoneNumber = phone; 
     } 
} 

UN:デシベルとコンストラクタであなたの社員表と一致それは NUnitのテストの一部であるべきマニュアルvalidation.Itをすべきではない database.Thereに挿入されてしまった供給いままでの値を確保する方法

をテストします。

あなたは本当にデータベースにヒットして操作を実行したくありません。だから、モックデータベースへの呼び出しを行う必要があります。それは直接SqlConnectionと関連の実装を呼び出しているよう

using System.Collections.Generic; 
using NUnit.Framework; 
using Moq; 

namespace UnitTestProject1 
{ 
    [TestFixture] 
    public class EmployeeRepositoryTests 
    { 
     [Test] 
     public void GetAll() 
     { 
      // Arrange 
      var repositoryMock = new Mock<IEmployeeRepository>(); 
      var employees = new List<Employee> 
      { 
       new Employee("John", "Smith", 20, 12345678), 
       new Employee("Robert", "Taylor", 20, 12345678) 
      }; 

      // We simulate the DB returns the previous employees when calling the "GetAll()" method 
      repositoryMock.Setup(x => x.GetAll()).Returns(employees); 

      // Act 
      var result = repositoryMock.Object.GetAll(); 

      // Assert 
      CollectionAssert.AreEquivalent(employees, result); 
     } 
    } 
} 
+0

お返事ありがとうございます。私たちのアプリケーションは、そのモデルがInsert、Update、Deleteメソッドを持つようなデザインで開発されています。これらのメソッドは、Employeeクラスの一部です。私たちは今それを再設計することはできません。従業員と結果をjsonとして変換して比較するのはどうですか? –

0

Employeeクラスがきつく、実装上の懸念に結合されています。

以前の回答は、リポジトリパターンを提案しました。これは、この問題を処理する標準的な方法です。

しかし、その答えに対するあなたのコメントに基づいて。

私たちのアプリケーションは、モデルに挿入、更新、削除メソッドがあるような設計で開発されています。これらのメソッドは、Employeeクラスの一部です。私たちは今それを再設計することはできません。

要件に基づいてより柔軟なデザインに変更できないという印象を受けます。つまり、現在の構造を維持できず、コードをテスト可能にすることができないということではありません。しかし、これは、抽象クラスに依存し、その懸念を分離するために、クラスをリファクタリングする必要があります。

データアクセスの抽象化を作成します。

public interface IEmployeeRepository { 
    void Add(Employee model); 
    List<Employee> GetAll(); 
} 

これは前が異なる実装を使用することができるという柔軟性を行ったように永続性への呼び出しを行うためにEmployeeクラスで使用されます。

ここではリファクタリングされたEmployeeクラスが懸念の分離を適用した後です。

public class Employee { 
    IEmployeeRepository repository; 

    public int EmployeeId { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public int Age { get; set; } 
    public int phoneNumber { get; set; } 

    public Employee() { 

    } 

    public Employee(string fname, string lname, int age, int phone) { 
     this.FirstName = fname; 
     this.LastName = lname; 
     this.Age = age; 
     this.phoneNumber = phone; 
    } 

    public void InsertEmployee() { 
     repository.Add(this); 
    } 

    public List<Employee> GetAllEmployees() { 
     return repository.GetAll(); 
    } 

    public void SetRepository(IEmployeeRepository repository) { 
     this.repository = repository; 
    } 
} 

このクラスの前の公開APIが変更されていないが、クラスの責任は、抽象化を含めて反転することがあることに注意してください。

Active Record Patternのようなものを使用しているとすれば、データベースなしのテストが非常に難しいところまでカプセル化が非常に有利です。このように、単体テストよりも統合テストの方が有利です。

コンストラクタインジェクションが現在のデザインにうまく適合しないため、レコードへの依存性の注入を可能にするメソッドを公開することをお勧めします。

これは、クラスに記載されている制限のためにのみ推奨されています。適切な使用のための前提条件を隠すため、カプセル化に違反します。

これで、Employeeクラスは、テストを配置するときに依存関係の模擬実装を使用して、孤立してテストできるようになりました。

[Test] 
public void InsertEmployee_Should_Add_Record() { 
    //Arrange 
    var employees = new List<Employee>(); 

    var repositoryMock = new Mock<IEmployeeRepository>(); 

    repositoryMock 
     .Setup(_ => _.Add(It.IsAny<Employee>())) 
     .Callback<Employee>(employees.Add) 
     .Verifiable(); 

    var newEmp = new Employee("Ram", "Prem", 30, 90000007); 
    newEmp.SetRepository(repositoryMock.Object); 

    //Act 
    newEmp.InsertEmployee(); 

    //Assert 
    employees.Should() 
     .HaveCount(1) 
     .And 
     .Contain(newEmp); 
    repositoryMock.Verify(); 
} 

[Test] 
public void GetAllEmployees_Should_GetAll() { 
    //Arrange 
    var expected = new List<Employee>() { 
     new Employee("Ram", "Prem", 30, 90000007), 
     new Employee("Pam", "Rem", 31, 90000008) 
    }; 

    var repositoryMock = new Mock<IEmployeeRepository>(); 

    repositoryMock 
     .Setup(_ => _.GetAll()) 
     .Returns(expected) 
     .Verifiable(); 

    var newEmp = new Employee(); 
    newEmp.SetRepository(repositoryMock.Object); 

    //Act 
    var actual = newEmp.GetAllEmployees(); 

    //Assert 
    expected.Should().Equal(actual); 
    repositoryMock.Verify(); 
} 

リポジトリの実動実装は、実装上の懸念事項に依存しないことによる懸念の分離によっても改善することができます。

ここでは、使用できるインターフェイスとサポートクラスの例を示します。

public interface IDbConnectionFactory { 
    ///<summary> 
    /// Creates a connection based on the given connection string. 
    ///</summary> 
    IDbConnection CreateConnection(string nameOrConnectionString); 
} 

public class SqlConnectionFactory : IDbConnectionFactory { 
    public IDbConnection CreateConnection(string nameOrConnectionString) { 
     return new SqlConnection(nameOrConnectionString); 
    } 
} 

public static class DbExtension { 
    public static IDbDataParameter AddParameterWithValue(this IDbCommand command, string parameterName, object value) { 
     var parameter = command.CreateParameter(); 
     parameter.ParameterName = parameterName; 
     parameter.Value = value; 
     command.Parameters.Add(parameter); 
     return parameter; 
    } 

    public static IDbCommand CreateCommand(this IDbConnection connection, string commandText) { 
     var command = connection.CreateCommand(); 
     command.CommandText = commandText; 
     return command; 
    } 
} 

public class EmployeeSqlRepository : IEmployeeRepository { 
    private IDbConnectionFactory connectionFactory; 

    public EmployeeSqlRepository(IDbConnectionFactory connectionFactory) { 
     this.connectionFactory = connectionFactory; 
    } 

    public void Add(Employee model) { 
     using (var connection = connectionFactory.CreateConnection("sqlconnection")) { 
      using (var command = connection.CreateCommand("sp_insert")) { 
       command.CommandType = CommandType.StoredProcedure; 
       command.AddParameterWithValue("fname", model.FirstName); 
       command.AddParameterWithValue("lname", model.LastName); 
       command.AddParameterWithValue("age", model.Age); 
       command.AddParameterWithValue("phoneno", model.phoneNumber); 
       connection.Open(); 
       command.ExecuteNonQuery(); 
       connection.Close(); 
      } 
     } 
    } 

    public List<Employee> GetAll() { 
     var employeeList = new List<Employee>(); 
     using (var connection = connectionFactory.CreateConnection("sqlconnection")) { 
      using (var command = connection.CreateCommand("GetAllEmployees")) { 
       command.CommandType = CommandType.StoredProcedure; 
       connection.Open(); 
       using (var reader = command.ExecuteReader()) { 
        while (reader.Read()) { 
         var employee = new Employee(); 
         employee.EmployeeId = int.Parse(reader["empID"].ToString()); 
         employee.FirstName = reader["fname"].ToString(); 
         employee.LastName = reader["lname"].ToString(); 
         employee.Age = int.Parse(reader["age"].ToString()); 
         employee.phoneNumber = int.Parse(reader["phone"].ToString()); 

         employee.SetRepository(this); 

         employeeList.Add(employee); 
        } 
       } 
      } 
     } 
     return employeeList; 
    } 
} 
関連する問題