我是单元测试的新手。我有这些类 AccountBl 调用 DataStore ,它使用SqlConnection从数据库中获取数据。
我需要通过模拟数据源来测试 AccountBl.GetInvestmentAccounts 方法,即使没有数据库连接,测试也需要运行。
以下是给定的课程 AccountBl :
public class AccountBl
{
private readonly DataStore dataStore = new DataStore();
public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType)
{
if (accountType == AccountType.Investment)
{
var accounts = dataStore.LoadAccounts(clientId);
return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
}
throw new Exception("Invalid account type provided");
}
}
和 DataStore :
public class DataStore
{
public static string GetAccountsSql = "irrelevant query";
public virtual List<Account> LoadAccounts(int clientId)
{
using (var connection = CreateConnection())
{
var sqlCommand = connection.CreateCommand();
sqlCommand.CommandText = GetAccountsSql;
sqlCommand.CommandType = CommandType.Text;
sqlCommand.Parameters.Add("@clientId", clientId);
var reader = sqlCommand.ExecuteReader();
var accounts = new List<Account>();
while (reader.Read())
{
var account = new Account();
account.AccountNumber = (string)reader["number"];
account.AccountOwner = clientId;
if (reader["accountType"] == null || reader["accountType"] == DBNull.Value)
{
account.AccountType = AccountType.Checking;
}
else
{
account.AccountType =
(AccountType)Enum.Parse(typeof(AccountType), reader["accountType"].ToString());
}
accounts.Add(account);
}
return accounts;
}
}
private static SqlConnection CreateConnection()
{
var sqlConnection = new SqlConnection(ConfigurationManager.AppSettings["ConnectionString"]);
sqlConnection.Open();
return sqlConnection;
}
}
这是我的 TestClass
[TestClass]
public class UnitTest1
{
[TestMethod]
public void GetInvestmentAccountsTest()
{
var clientId = 25;
var mockAccounts = new List<Account>
{
new Account{AccountNumber = "aaa", AccountOwner = clientId, AccountType = AccountType.Investment},
new Account{AccountNumber = "bbb", AccountOwner = clientId, AccountType = AccountType.Savings},
new Account{AccountNumber = "ccc", AccountOwner = clientId, AccountType = AccountType.Checking},
};
var mockDatastore = new Mock<DataStore>();
mockDatastore.Setup(x => x.LoadAccounts(clientId)).Returns(mockAccounts);
var accountBl = new AccountBl();
var accounts = accountBl.GetInvestmentAccounts(clientId, AccountType.Investment);
}
}
当我跑步时,收到错误消息
消息:测试方法 ScreeningSample.Tests.UnitTest1.GetInvestmentAccountsTest扔了 exception:System.InvalidOperationException:ConnectionString 财产尚未初始化。
显然它试图创建连接,但我需要在没有连接的情况下运行测试。
我是否错误地嘲笑?
答案 0 :(得分:1)
被测对象中的readonly DataStore dataStore
与课程紧密相连,难以单独测试对象。您需要能够在测试期间替换该依赖项,以便能够单独测试。
首先考虑抽象数据存储,
public interface IDataStore {
List<Account> LoadAccounts(int clientId);
}
让主题通过构造函数注入显式依赖于抽象,因为类应该依赖于抽象而不是结构。
public class AccountBl {
private readonly IDataStore dataStore;
public AccountBl(IDataStore dataStore) {
this.dataStore = dataStore;
}
public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType) {
if (accountType == AccountType.Investment) {
var accounts = dataStore.LoadAccounts(clientId);
return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
}
throw new Exception("Invalid account type provided");
}
}
SqlConnection
是一个不再是AccountBl
DataStore
实现将从抽象派生。
public class DataStore : IDataStore {
public List<Account> LoadAccounts(int clientId) {
//...code removed for brevity
}
//...
}
现在代码已经解耦,可以更加灵活地单独测试
[TestClass]
public class UnitTest1 {
[TestMethod]
public void GetInvestmentAccountsTest() {
//Arrange
var clientId = 25;
var mockAccounts = new List<Account> {
new Account{AccountNumber = "aaa", AccountOwner = clientId, AccountType = AccountType.Investment},
new Account{AccountNumber = "bbb", AccountOwner = clientId, AccountType = AccountType.Savings},
new Account{AccountNumber = "ccc", AccountOwner = clientId, AccountType = AccountType.Checking},
};
var mockDatastore = new Mock<IDataStore>();
mockDatastore.Setup(_ => _.LoadAccounts(clientId)).Returns(mockAccounts);
var subject = new AccountBl(mockDatastore.Object);
//Act
var accounts = subject.GetInvestmentAccounts(clientId, AccountType.Investment);
//Assert
//...
}
}
答案 1 :(得分:0)
在您的单元测试中,您创建了一个模拟数据源但不使用它;这就是调用DataStore::LoadAcounts
的原因。您应该在构造函数中注入DataStore
的实例,而不是在AccountBl
类中创建DataStore
的实例。这是依赖注入的一种形式。
public class AccountBl
{
private DataStore _dataStore;
public AccountBl(DataStore dataStore)
{
_dataStore = dataStore;
}
public List<Account> GetInvestmentAccounts(int clientId, AccountType accountType)
{
if (accountType == AccountType.Investment)
{
var accounts = _dataStore.LoadAccounts(clientId);
return accounts.Where(a => a.AccountType == AccountType.Investment).ToList();
}
throw new Exception("Invalid account type provided");
}
}
现在在单元测试中注入模拟数据源:
var mockDatastore = new Mock<DataStore>();
mockDatastore.Setup(x => x.LoadAccounts(clientId)).Returns(mockAccounts);
// Inject mock data source
var accountBl = new AccountBl(mockDataStore.Object);
var accounts = accountBl.GetInvestmentAccounts(clientId, AccountType.Investment);