0

When debugging and stepping through .net code we can change the value of any variable (after instruction has been executed). Is it possible to do that, for testing purposes, programmatically?

Let's assume we need to programm of software for a device that measures gamma rays and warns if some thresholds are exceeded:

public class GeigerCounter
{
    var _instrument = new Instrument();
    var _warn = WarnModule();
    
    int MeasureRadiation()
    {
        int measurement = _instrument.MeasureRadiation();
        
        if (measurement > 1000){
            _warn.StartWarningLvl1();
            
        if (measurement > 2000)
            _warn.StartWarningLvl2();
            
        if (measurement > 3000)
            _warn.StartWarningLvl3()
    }
}

The Unit Test (MSTest v2) to test if an alarm is triggered or not would look like this:

[TestMethod]
public void measure_Test()
{
   GeigerCounter gc = new GeigerCounter();
   WarnModule wm = WarnModule();
   gc.measure();
   var warnLvl = wm.getWarnLvl();
   Assert.AreEqual(Lvl0,warnLvl);
}

In the test method above we are only testing that there is no dangerous radiation is measured and since I don't want to hold some radium to test the code if an Alarm/Warning is triggered I could manually, when debugging, change the value of measurement in the MeasureRadiation() Method and of course the value in Assert.AreEqual(...,warnLvl).

Is there a way do it programmatically without changing the source code of the class GeigerCounter and WarnModule?

It would be nice if somehow we could change the value by accessing the stack or at least get the memory address of the measurement variable so we could change its value.

SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • Have you looked at using a mocking library like Moq/Nsubstitute? – Tjaart Mar 22 '22 at 10:43
  • Sounds like you need to [mock](https://stackoverflow.com/questions/2665812/what-is-mocking) `Instrument`. But I guess you should decouple `Instrument` from `GeigerCounter` before anything else. – Sweeper Mar 22 '22 at 10:43

2 Answers2

2

You need an interface for your instrument:

public interface IInstrument
{
   int MeasureRadiation();
}

Then you GeigerCounter class should depend on this interface

public class GeigerCounter
{
    IInstrument _instrument;
    WarnModule _warn = WarnModule();

    public GeigerCounter() : this(new Instrument())

    public GeigerCounter(IInstrument instrument)
    {
        _instrument = instrument;
    }


    
    int MeasureRadiation()
    {
        int measurement = _instrument.MeasureRadiation();
        
        if (measurement > 1000){
            _warn.StartWarningLvl1();
            
        if (measurement > 2000)
            _warn.StartWarningLvl2();
            
        if (measurement > 3000)
            _warn.StartWarningLvl3()
    }
}

This approach is called dependency injection and is quite common. See for example the very interesting book "Dependency Injection in .NET" by Mark Seeman. You can now make a dummy instrument that delivers any value:

public class DummyInstrument : IInstrument
{
     private int _value;
     public DummyInstrument(int value)
     {
        _value = value;
     }

    public int MeasureRadiation()
    {
       return _value;
    }
}

There are also frameworks which help you create these dummy implementations (which are also called mocks or stubs). For example, I use Moq to create such implementations.

The unit test now looks like this:

[TestMethod]
public void measure_Test()
{
   IInstrument instrument = new DummyInstrument(1000);
   GeigerCounter gc = new GeigerCounter(instrument );
   WarnModule wm = WarnModule();
   gc.measure();
   var warnLvl = wm.getWarnLvl();
   Assert.AreEqual(Lvl0,warnLvl);
}

By the way, I think it is unusual that the instance of your WarnModule in your test is different then the one in the GeigerCounter class. Does this class have any static properties or fields? If not, you have to inject your WarnModule similiar as your Instrument.

SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • 1
    You could mention that this is known as [Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) – Matthew Watson Mar 22 '22 at 11:13
  • thank you for your quick response."Does this class have any static properties or fields?" the Class serves as an example. The issue with this solution is it needs more code for each method. For example knowing the address of measurement we could just make something like that: var intPtr = new IntPtr(0xBADEAFFE); //address of measurement int* addr = (int*)intPtr.ToPointer(); *addr = 1000; – Sanfour ben sanfer Mar 22 '22 at 11:15
  • I strongly recommend doing it the way I showed you - this is a quite common approach used by the bigger part of programers. I don't know if your approach even works, but I strongly doubt it. And even if it worked, the test code would become very hard to understand and also very brittle - what happens if you have to change your GeigerCounter class? Your test might point to the wrong memory location leading to unexpected behaviour. – SomeBody Mar 23 '22 at 06:25
  • the issue with your solution is that if the object is created @[ClassInitialize] it becomes a hassle with other methods that needs to be implemented. The Idea behind is that i need just to change that one value while testing in that just one Test Case – Sanfour ben sanfer Mar 30 '22 at 06:30
0

In my opinion the easiest way with less code to just change a single values looks like this:

public class GeigerCounter
{
    var _instrument = new Instrument();
    var _warn = WarnModule();
    
    #if UnitTest
        public static int testMeasurement;
    #endif

    int MeasureRadiation()
    {            
        #if UnitTest
            int measurement = _instrument.MeasureRadiation();
        #else
            int measurement = testMeasurement;
        #endif

        if (measurement > 1000){
            _warn.StartWarningLvl1();
            
        if (measurement > 2000)
            _warn.StartWarningLvl2();
            
        if (measurement > 3000)
            _warn.StartWarningLvl3()
    }
}

The unit test:

[TestMethod]
public void measure_Test()
{
   GeigerCounter gc = new GeigerCounter();
   WarnModule wm = WarnModule();
   gc.testMeasurement = 3333;
   gc.measure();
   var warnLvl = wm.getWarnLvl();
   Assert.AreEqual(Lvl0,warnLvl);
}

If using Visual Studio we just need to add the UnitTest directive in Project Properties->Build in Conditional compilation symbols.