Entity Framework の経験から、クエリを最適化する必要があるほど、より多くのビジネス ロジックがデータ層に流れ込むか、ストアド プロシージャを介してデータベースに流れ込みます。これにより、単体テストがより困難になります。人々がこれにどのように対処するのか疑問に思っていました。
たとえば、ビジネス レイヤー/リポジトリに、単体テスト用の一連のビジネス ルールとロジックがある関数があるとします。ただし、これをストアド プロシージャに結合して複数の結果セットなどを返すことができることがわかりましたが、今では単体テストは役に立ちません。
以下は、株式市場のシナリオで買い注文を出す例です。データベースにデータを戻すヒットが多数あり、データベースに戻って特定の基準が満たされているかどうかを確認する必要があります。
これをストアド プロシージャに入れることもできますが、その後、すべてのロジックがデータベースにプッシュされ、単体テストが難しくなります。
public IEnumerable<ValidationResult> PlaceBuyOrder(int sellOrderId, int requestedShares, int buyerUserId)
{
if (sellOrderId <= 0) throw new ArgumentNullException("sellOrderId");
if (requestedShares <= 0) throw new ArgumentNullException("requestedShares");
if (buyerUserId <= 0) throw new ArgumentNullException("buyerUserId");
// Get the sell order to check to see if the sell order still exists.
var sellerOrderActivity = GetLatestUnprocessedOrderActivityForOrder(sellOrderId);
if (sellerOrderActivity == null)
yield return new ValidationResult(GlobalResources.OrderDoestExist);
else
{
// Make sure when the order type is "sell all" that the
if (sellerOrderActivity.Order.Type == OrderTypes.SellAll && requestedShares < sellerOrderActivity.QtyRemaining)
yield return new ValidationResult("requestedShares", GlobalResources.RequestedSharesCannotBeLessthanWhatIsBeingSold);
else
{
if (requestedShares > sellerOrderActivity.QtyRemaining)
requestedShares = sellerOrderActivity.QtyRemaining;
var requestedSharesAmount = requestedShares * sellerOrderActivity.Price;
if (!_financeService.CanUserAffordSharesPurchase(buyerUserId, requestedSharesAmount))
yield return new ValidationResult(GlobalResources.InsufficentFundsToPurchaseRequestedShares);
else if (!_financeService.CanUserAffordSharesPurchase(sellerOrderActivity.Order.UserId, 0))
yield return new ValidationResult(GlobalResources.SellerHasInsufficentFundsAfterCommission);
else
{
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TransactionManager.MaximumTimeout }))
{
// Insert both the order and order activity.
var newBuyOrderActivity = new OrderActivity
{
Price = sellerOrderActivity.Price,
QtyRemaining = requestedShares,
Status = OrderActivityStatuses.Open,
Order = new Order
{
Type = OrderTypes.Buy,
UserId = buyerUserId,
VideoId = sellerOrderActivity.Order.VideoId
}
};
_orderRepository.AddOrderActivity(newBuyOrderActivity);
_orderRepository.SaveChanges();
var sellerProcessedOrderActivity = new ProcessedOrderActivity();
var buyerProcessedOrderActivity = new ProcessedOrderActivity();
ProcessBuyAndSellOrderActivities(newBuyOrderActivity.Id, sellerOrderActivity, requestedShares, sellerProcessedOrderActivity, buyerProcessedOrderActivity);
if (sellerOrderActivity.Order.Type == OrderTypes.Sell && requestedShares != sellerOrderActivity.QtyRemaining)
{
var newSellerPartialOrderActivity = new OrderActivity
{
OrderId = sellerOrderActivity.OrderId,
Price = sellerOrderActivity.Price,
QtyRemaining = sellerOrderActivity.QtyRemaining - requestedShares,
Status = OrderActivityStatuses.Partial
};
_orderRepository.AddOrderActivity(newSellerPartialOrderActivity);
_orderRepository.SaveChanges();
}
var sellerAccountId = _accountRepository.GetAccountIdForUser(sellerOrderActivity.Order.UserId);
var sellerAccountTransaction = new Data.Model.Transaction();
var buyerAccountTransaction = new Data.Model.Transaction();
_financeService.TransferFundsFromBuyerToSeller(buyerUserId, 0, sellerOrderActivity.Order.UserId, sellerAccountId, requestedSharesAmount,
sellerAccountTransaction, buyerAccountTransaction);
// Add the apporpriate transactions to the portfolio activity table and make
// sure the seller comes before the buyer to negate the portfolio first.
var sellerPortfolio = new UserPortfolioActivity
{
Price = sellerOrderActivity.Price,
ProcessedOrderActivityId = sellerProcessedOrderActivity.Id,
Qty = -requestedShares,
TransactionId = sellerAccountTransaction.Id,
UserId = sellerOrderActivity.Order.UserId,
VideoId = sellerOrderActivity.Order.VideoId
};
var buyerPortfolio = new UserPortfolioActivity
{
Price = sellerOrderActivity.Price,
ProcessedOrderActivityId = buyerProcessedOrderActivity.Id,
Qty = requestedShares,
TransactionId = buyerAccountTransaction.Id,
UserId = buyerUserId,
VideoId = sellerOrderActivity.Order.VideoId
};
_userRepository.AddUserPortfolio(sellerPortfolio);
_userRepository.AddUserPortfolio(buyerPortfolio);
_userRepository.SaveChanges();
transactionScope.Complete();
}
}
}
}
}