-2

I work on WPF applications used in testing hardware. I need to make my code base more testable and re-usable. I believe DI would help, but I'm not quite sure where to start. I've done my best with the following to remove anything too proprietary while still giving a good idea of the current structure. There are models representing data, views that act on the data, and a TestStateManager that...does a ton of stuff. Probably too much. The TestStateManager is the DataContext for my MainWindow.

NOTE: Although a hardware device is considered the same device, it's processor will be programmed differently depending on the SKU selected by the user. Based on programming, it might need one set of tests versus another. I am currently using JSON read in from a config file to build the appropriate set of tests and associate them with the device.

// Models
public class Test
{
    public Exception Error { get; set; }
    public short DefaultFailCode { get; set; }
    public short? ResultCode { get; set; }
    public string TestName { get; set; }
    public string ResultXML { get; set; }
}

public class Programming : Test
{
    public string FilePath { get; set; }
}

public class ToleranceTest : Test
{
    public double MaxTolerance { get; set; }
    public double MinTolerance { get; set; }
    public double? Measurement { get; set; }
}

// Views
public class TestView : INotifyPropertyChanged
{
    // If the state isn't set to start, you won't notice a state name change in the UI
    // because it's ignored
    public enum States { Errored, Failed, Pending, Passed, Testing }
    public Test Data { get; protected set; }
    private enum StateTriggers { Start, Error, Fail, Pass, Reset }
    public string StateName;
    private StateMachine<States, StateTriggers> _testState;

    public TestView(short defaultFailCode, string testName)
    {
        // Errored state is used for presenting the user with error information before failing
        Data = new Test();
        Data.TestName = testName;
        Data.DefaultFailCode = defaultFailCode;
        _testState = new StateMachine<States, StateTriggers>(States.Pending);
        _testState.Configure(States.Pending)
            .Permit(StateTriggers.Start, States.Testing)
            .Ignore(StateTriggers.Fail)
            .Ignore(StateTriggers.Reset);
        _testState.Configure(States.Testing)
            .OnEntry(o => OnPropertyChanged("State"))
            .Permit(StateTriggers.Error, States.Errored)
            .Permit(StateTriggers.Fail, States.Failed)
            .Permit(StateTriggers.Pass, States.Passed);
        _testState.Configure(States.Errored)
            .Permit(StateTriggers.Reset, States.Pending)
            .Permit(StateTriggers.Fail, States.Failed);
        _testState.Configure(States.Failed)
            .Permit(StateTriggers.Reset, States.Pending)
            .Ignore(StateTriggers.Fail)
            .Ignore(StateTriggers.Pass);
        _testState.Configure(States.Passed)
            .Permit(StateTriggers.Reset, States.Pending);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged(string name)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    public States State
    {
        get { return _testState.State; }
        set { OnPropertyChanged("State"); }
    }

    public void Error(Exception err)
    {
        Data.Error = err;
        _testState.Fire(StateTriggers.Error);
        State = _testState.State;
    }

    public void Start()
    {
        _testState.Fire(StateTriggers.Start);
        State = _testState.State;
    }

    public void Fail(short? code = null)
    {
        if (code.HasValue)
            Data.ResultCode = code;
        else
            Data.ResultCode = Data.DefaultFailCode;
        _testState.Fire(StateTriggers.Fail);
        State = _testState.State;
    }

    public void Pass(short code = 0)
    {
        Data.ResultCode = code;
        _testState.Fire(StateTriggers.Pass);
        State = _testState.State;
    }

    public virtual void Reset()
    {
        Data.ResultCode = null;
        Data.ResultXML = string.Empty;
        Data.Error = null;
        _testState.Fire(StateTriggers.Reset);
        State = _testState.State;
    }

    public virtual void GenerateResultXML()
    {
        if (_testState.State == States.Passed)
            StateName = "True";
        else
            StateName = "False";
        Data.ResultXML = "<" + Data.TestName + ">" + "<Pass>" + StateName + "</Pass></" + Data.TestName + ">";
    }
}

public class ProgrammingView : TestView
{
    public ProgrammingView(short defaultFailCode, string testName, string filePath) : base(defaultFailCode, testName)
    {
        Data = new Programming();
        ((Programming)Data).FilePath = filePath;
        ((Programming)Data).DefaultFailCode = defaultFailCode;
        ((Programming)Data).TestName = testName;
    }
}

public class ToleranceTestView : TestView
{
    public ToleranceTestView(short defaultFailCode, string testName, double minimumTolerance, double maximumTolerance) : base(defaultFailCode, testName)
    {
        Data = new ToleranceTest();
        ((ToleranceTest)Data).MaxTolerance = maximumTolerance;
        ((ToleranceTest)Data).MinTolerance = minimumTolerance;
        ((ToleranceTest)Data).DefaultFailCode = defaultFailCode;
        ((ToleranceTest)Data).TestName = testName;
    }

    public void Fail(double actualValue, short? code = null)
    {
        Fail(code);
        ((ToleranceTest)Data).Measurement = actualValue;
    }

    public override void GenerateResultXML()
    {
        base.GenerateResultXML();
        Data.ResultXML = "<" + Data.TestName + ">" + "<Pass>" + StateName + "</Pass><value>" + ((ToleranceTest)Data).Measurement.ToString() + "</value></" + Data.TestName + ">";
    }

    public void Pass(double measurement, short code = 0)
    {
        Pass(code);
        ((ToleranceTest)Data).Measurement = measurement;
    }

    public override void Reset()
    {
        base.Reset();
        ((ToleranceTest)Data).Measurement = null;
    }
}

public class TestSet
{
    // NOTE: Because ToleranceTest is still a test, it can still be added to the same list.
    public bool? AllTestsPassed
    {
        get
        {
            if (FailureCode != null)
                if (FailureCode == 0)
                    return true;
                else
                    return false;
            else
                return null;
        }
    }
    public short? FailureCode { get; private set; }
    public string TestResultXML;
    public List<TestView> Tests;
    private string _platformName;
    private string _XMLTestName;
    private StringBuilder results = new StringBuilder();

    public TestSet(string platformName, string XMLTestName)
    {
        _platformName = platformName;
        _XMLTestName = XMLTestName;
        Tests = new List<TestView>();
    }

    public void CreateTestResultXML()
    {
        foreach (TestView test in Tests)
        {
            if (test.State == TestView.States.Pending || test.State == TestView.States.Testing)
                continue;
            if (test.State == TestView.States.Passed || test.State == TestView.States.Failed)
                results.Append(test.Data.ResultXML);
            else
            {
                test.State = TestView.States.Failed;
                results.Append(test.Data.ResultXML);
            }
            if (test.Data.ResultCode != 0)
                FailureCode = test.Data.ResultCode;
        }
        string _results = results.ToString();
        string _docType = "<?xml version=\"1.0\"?>";
        string _testNameNode = "<Test>" + _XMLTestName + "</Test>";
        string _platformNode = "<Platform>" + _platformName + "</Platform>";
        string _dataVersionNode = "<DataVersion>1.0</DataVersion>";
        string _testDataNode = "<TestData>" + _results + "</TestData>";
        TestResultXML = _docType + _testNameNode + _platformNode + _dataVersionNode + _testDataNode;
    }

    public void Reset()
    {
        foreach (TestView toReset in Tests)
        {
            toReset.Reset();
        }
        FailureCode = 0;
        TestResultXML = string.Empty;
    }
}

// State Manager - uses config data, gets user input and related data, 
// runs hardware tests, changes test-related states accordingly saves test data
class TestStateManager : INotifyPropertyChanged
{
    private ConfigView _cfgObj;
    private DeviceUnderTestView _deviceView;
    private Dictionary<string, Task> _testLogic;    // Full list of all tests. String needs to match the name of the test in order to insert the correct task logic into the test.
    private Models.Config _cfg;
    private HardwareView;
    private HardwareView;
    private HardwareView;
    private WebService _web;

    public TestStateManager()
    {
        
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string name = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }

    public ObservableCollection<string> _skuRevs;
    public ObservableCollection<string> SKURevs
    {
        get { return _skuRevs; }
        set { _skuRevs = value; OnPropertyChanged(); }
    }

    private string _appStateText;
    public string AppStateText
    {
        get { return _appStateText; }
        set
        {
            _appStateText = value;
            if (!string.IsNullOrEmpty(_appStateText))
                _appStateText += Environment.NewLine;
            OnPropertyChanged();
        }
    }

    public bool DevicePower
    {
        get { return _deviceView.Power; }
        set { _deviceView.Power = value; OnPropertyChanged(); }
    }

    public string ExternalSerial
    {
        get { return _deviceView.ExternalSerial; }
        set
        {
            _deviceView.ExternalSerial = value;
            OnPropertyChanged();
        }
    }

    private bool? _overallStatus;
    public bool? OverallStatus
    {
        get { return _overallStatus; }
        set { _overallStatus = value; OnPropertyChanged(); }
    }

    private bool _pcaTextBoxHintVisible;
    public bool PCATextBoxHintVisible
    {
        get { return _pcaTextBoxHintVisible; }
        set { _pcaTextBoxHintVisible = value; OnPropertyChanged(); }
    }

    private bool _programDevice;
    public bool ProgramDevice
    {
        get { return _programDevice; }
        set { _programDevice = value; OnPropertyChanged(); }
    }

    private bool _revControl;
    public bool RevControl
    {
        get { return _revControl; }
        set { _revControl = value; OnPropertyChanged(); }
    }

    public bool RevCtrlCheckBoxVisible
    {
        get { return _cfg.Settings.EnableRevCtrlCheckBox; }
    }

    private string _selectedBPart;
    public string SelectedBPart
    {
        get { return _selectedBPart; }
        set { _selectedBPart = value; OnPropertyChanged(); }
    }

    private string _selectedXPart;
    public string SelectedXPart
    {
        get { return _selectedXPart; }
        set { _selectedXPart = value; OnPropertyChanged(); }
    }

    private string _sku;
    public string SKU
    {
        get { return _sku; }
        set
        {
            _sku = value;
            OnPropertyChanged();
        }
    }

    private bool _skuSelected;
    public bool SKUSelected
    {
        get { return _skuSelected; }
        set { _skuSelected = value; OnPropertyChanged(); }
    }

    public int? PartSerial
    {
        get { return _deviceView.PartSerial; }
        set
        {
            _deviceView.PartSerial = value;
            OnPropertyChanged();
        }
    }

    public int? PCASerial
    {
        get { return _deviceView.PCASerial; }
        set
        {
            _deviceView.PCASerial = value;
            OnPropertyChanged();
        }
    }

    private void CheckForLinkedPCASerial()
    {
            ...
            PCASerial = _web.GetPCASerialForLinkedSerial(ExternalSerial);
            ...
    }

    public long CreateAndSavePCATestResultXML()
    {
        ...
        _web.SavePCATestResults((int)PCASerial, _cfg.Settings.TestSiteID, 0, "PCA_Test", "End", PCAResultXML);
        ...
    }

    public void DevicePowerButton_Click()
    {
        if (DevicePower)
        {
            DevicePower = false;
        }
        else
        {
            DevicePower = true;
        }
    }

    public void ExecuteDeviceTests()
    {
        // Before this runs, need to associate a Task with each test,
        // then run them here.
    }

    public void PopulateSKURevList(string EnteredSKU)
    {
        ...
            _web.GetBOMItemsForLevelPart(SKU);
        ...
            SKURevs.Add(item.ID + " " + item.PartNumber + " " + item.Rev);
        ...
        return;
    }

    private void RequestNewPartSerial()
    {
        ...
        _web.RequestPCASerialWithLinkedSerial((int)_deviceView.BBOMItemID, ExternalSerial);
        ...
    }

    public void SetExternalSerial(bool isExternal)
    {
        _isExternalSerial = isExternal;
    }

    public void SKURevs_SelectionChanged(int bomID)
    {
        ...
        _web.GetBOMItems(bomID);
        _deviceView.DeviceTestSetup(_cfg.Devices.Where(d => d.XPartNumber == x_item.PartNumber).FirstOrDefault());
        _deviceView.SetXBOMData(x_item.ID, x_item.Rev, x_item.PartNumber);
        ...
        _deviceView.SetBBOMData(b_item.ID, b_item.Rev, b_item.PartNumber);
        ...
        return;
    }

    public void Destroy()
    {

    }
}
0

1 Answer 1

0

I believe DI would help

I expect it's more than mere "help", it's the key. But you already use DI if using parameters in constructors and other methods because that's all DI is. Fortunately .NET provides classes for dynamically DI-ing large, complex sets of dependency code.


not quite sure where to start

Have you read MSDN's DI article? Use .NET provided classes to bundle your interfaces and base classes into 'service containers.' These service providers instantiate concretions that are then passed into the using class' constructor. Reading this feels at bit like using a parts catalog to scratch-build a car; this book will help: Dependency Injection in .NET

Not the answer you're looking for? Browse other questions tagged or ask your own question.