Construct testable business layer logic

前端 未结 1 1905
我寻月下人不归
我寻月下人不归 2020-12-07 06:13

I am building an applications in .net/c#/Entity Framework that uses a layered architecture. The applications interface to the outside world is a WCF service Layer. Underneat

相关标签:
1条回答
  • 2020-12-07 06:45

    I generally use IServices, Services, and MockServices.

    • IServices provides the available operations that all business logic must invoke methods on.
    • Services is the data access layer that my code-behind injects into view-model (i.e. actual database).
    • MockServices is the data access layer that my unit tests injects to the view-model (i.e. mock data).

    IServices:

    public interface IServices
    {
        IEnumerable<Warehouse> LoadSupply(Lookup lookup);
        IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Lookup lookup);
    
        IEnumerable<Inventory> LoadParts(int daysFilter);
        Narration LoadNarration(string stockCode);
        IEnumerable<PurchaseHistory> LoadPurchaseHistory(string stockCode);
    
        IEnumerable<StockAlternative> LoadAlternativeStockCodes();
        AdditionalInfo GetSupplier(string stockCode);
    }
    

    MockServices:

    public class MockServices : IServices
    {
        #region Constants
        const int DEFAULT_TIMELINE = 30;
        #endregion
    
        #region Singleton
        static MockServices _mockServices = null;
    
        private MockServices()
        {
        }
    
        public static MockServices Instance
        {
            get
            {
                if (_mockServices == null)
                {
                    _mockServices = new MockServices();
                }
    
                return _mockServices;
            }
        }
        #endregion
    
        #region Members
        IEnumerable<Warehouse> _supply = null;
        IEnumerable<Demand> _demand = null;
        IEnumerable<StockAlternative> _stockAlternatives = null;
        IConfirmationInteraction _refreshConfirmationDialog = null;
        IConfirmationInteraction _extendedTimelineConfirmationDialog = null;
        #endregion
    
        #region Boot
        public MockServices(IEnumerable<Warehouse> supply, IEnumerable<Demand> demand, IEnumerable<StockAlternative> stockAlternatives, IConfirmationInteraction refreshConfirmationDialog, IConfirmationInteraction extendedTimelineConfirmationDialog)
        {
            _supply = supply;
            _demand = demand;
            _stockAlternatives = stockAlternatives;
            _refreshConfirmationDialog = refreshConfirmationDialog;
            _extendedTimelineConfirmationDialog = extendedTimelineConfirmationDialog;
        }
    
        public IEnumerable<StockAlternative> LoadAlternativeStockCodes()
        {
            return _stockAlternatives;
        }
    
        public IEnumerable<Warehouse> LoadSupply(Lookup lookup)
        {
            return _supply;
        }
    
        public IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Syspro.Business.Lookup lookup)
        {
            return _demand;
        }
    
        public IEnumerable<Inventory> LoadParts(int daysFilter)
        {
            var job1 = new Job() { Id = Globals.jobId1, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode100 };
            var job2 = new Job() { Id = Globals.jobId2, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode200 };
            var job3 = new Job() { Id = Globals.jobId3, AssembledRequiredDate = DateTime.Now, StockCode = Globals.stockCode300 };
    
            return new HashSet<Inventory>()
            {
                new Inventory() { StockCode = Globals.stockCode100, UnitQTYRequired = 1, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job1} },
                new Inventory() { StockCode = Globals.stockCode200, UnitQTYRequired = 2, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job2} },
                new Inventory() { StockCode = Globals.stockCode300, UnitQTYRequired = 3, Category = "Category_1", Details = new PartDetails() { Warehouse = Globals.Instance.warehouse1, Job = job3} },
            };
        }
        #endregion
    
        #region Selection
        public Narration LoadNarration(string stockCode)
        {
            return new Narration()
            {
                Text = "Some description"
            };
        }
    
        public IEnumerable<PurchaseHistory> LoadPurchaseHistory(string stockCode)
        {
            return new List<PurchaseHistory>();
        }
    
        public AdditionalInfo GetSupplier(string stockCode)
        {
            return new AdditionalInfo()
            {
                SupplierName = "Some supplier name"
            };
        }
        #endregion
    
        #region Creation
        public Inject Dependencies(IEnumerable<Warehouse> supply, IEnumerable<Demand> demand, IEnumerable<StockAlternative> stockAlternatives, IConfirmationInteraction refreshConfirmation = null, IConfirmationInteraction extendedTimelineConfirmation = null)
        {
            return new Inject()
            {
                Services = new MockServices(supply, demand, stockAlternatives, refreshConfirmation, extendedTimelineConfirmation),
    
                Lookup = new Lookup()
                {
                    PartKeyToCachedParts = new Dictionary<string, Inventory>(),
                    PartkeyToStockcode = new Dictionary<string, string>(),
                    DaysRemainingToCompletedJobs = new Dictionary<int, HashSet<Job>>(),
    .
    .
    .
    
                },
    
                DaysFilterDefault = DEFAULT_TIMELINE,
                FilterOnShortage = true,
                PartCache = null
            };
        }
    
        public List<StockAlternative> Alternatives()
        {
            var stockAlternatives = new List<StockAlternative>() { new StockAlternative() { StockCode = Globals.stockCode100, AlternativeStockcode = Globals.stockCode100Alt1 } };
            return stockAlternatives;
        }
    
        public List<Demand> Demand()
        {
            var demand = new List<Demand>()
            {
                new Demand(){ Job = new Job{ Id = Globals.jobId1, StockCode = Globals.stockCode100, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode100, RequiredQTY = 1}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId2, StockCode = Globals.stockCode200, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode200, RequiredQTY = 2}, 
            };
            return demand;
        }
    
        public List<Warehouse> Supply()
        {
            var supply = new List<Warehouse>() 
            { 
                Globals.Instance.warehouse1, 
                Globals.Instance.warehouse2, 
                Globals.Instance.warehouse3,
            };
            return supply;
        }
        #endregion
    }
    

    Services:

    public class Services : IServices
    {
        #region Singleton
        static Services services = null;
    
        private Services()
        {
        }
    
        public static Services Instance
        {
            get
            {
                if (services == null)
                {
                    services = new Services();
                }
    
                return services;
            }
        }
        #endregion
    
        public IEnumerable<Inventory> LoadParts(int daysFilter)
        {
            return InventoryRepository.Instance.Get(daysFilter);
        }
    
        public IEnumerable<Warehouse> LoadSupply(Lookup lookup)
        {
            return SupplyRepository.Instance.Get(lookup);
        }
    
        public IEnumerable<StockAlternative> LoadAlternativeStockCodes()
        {
            return InventoryRepository.Instance.GetAlternatives();
        }
    
        public IEnumerable<Demand> LoadDemand(IEnumerable<string> stockCodes, int daysFilter, Lookup lookup)
        {
            return DemandRepository.Instance.Get(stockCodes, daysFilter, lookup);
        }
    .
    .
    .
    

    Unit Test:

        [TestMethod]
        public void shortage_exists()
        {
            // Setup
            var supply = new List<Warehouse>() { Globals.Instance.warehouse1, Globals.Instance.warehouse2, Globals.Instance.warehouse3 };
            Globals.Instance.warehouse1.TotalQty = 1;
            Globals.Instance.warehouse2.TotalQty = 2;
            Globals.Instance.warehouse3.TotalQty = 3;
    
            var demand = new List<Demand>()
            {
                new Demand(){ Job = new Job{ Id = Globals.jobId1, StockCode = Globals.stockCode100, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode100, RequiredQTY = 1}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId2, StockCode = Globals.stockCode200, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode200, RequiredQTY = 3}, 
                new Demand(){ Job = new Job{ Id = Globals.jobId3, StockCode = Globals.stockCode300, AssembledRequiredDate = DateTime.Now}, StockCode = Globals.stockCode300, RequiredQTY = 4}, 
            };
    
            var alternatives = _mock.Alternatives();
            var dependencies = _mock.Dependencies(supply, demand, alternatives);
    
            var viewModel = new MainViewModel();
            viewModel.Register(dependencies);
    
            // Test
            viewModel.Load();
    
            AwaitCompletion(viewModel);
    
            // Verify
            var part100IsNotShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode100) && (!p.HasShortage)).Single() != null;
            var part200IsShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode200) && (p.HasShortage)).Single() != null;
            var part300IsShort = dependencies.PartCache.Where(p => (p.StockCode == Globals.stockCode300) && (p.HasShortage)).Single() != null;
    
            Assert.AreEqual(true, part100IsNotShort &&
                                    part200IsShort &&
                                    part300IsShort);
        }
    

    CodeBehnd:

        public MainWindow()
        {
            InitializeComponent();
    
            this.Loaded += (s, e) =>
                {
                    this.viewModel = this.DataContext as MainViewModel;
    
                    var dependencies = GetDependencies();
                    this.viewModel.Register(dependencies);
    .
    .
    .
    

    ViewModel:

        public MyViewModel()
        {
    .
    .
    .
        public void Register(Inject dependencies)
        {
            try
            {
                this.Injected = dependencies;
    
                this.Injected.RefreshConfirmation.RequestConfirmation += (message, caption) =>
                    {
                        var result = MessageBox.Show(message, caption, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        return result;
                    };
    
                this.Injected.ExtendTimelineConfirmation.RequestConfirmation += (message, caption) =>
                    {
                        var result = MessageBox.Show(message, caption, MessageBoxButton.YesNo, MessageBoxImage.Question);
                        return result;
                    };
    
    .
    .
    .
            }
    
            catch (Exception ex)
            {
                Debug.WriteLine(ex.GetBaseException().Message);
            }
        }
    
    0 讨论(0)
提交回复
热议问题