You can use the “Generate Test Code” button to generate executable code from the test model.
Structure of Generated Code
In this section we describe the structure of the generated test code in detail. As an example we use the test code generated for the xunit framework from the test model “avgspeed.tmdl”.
The test code is composed of the following elements, the generated test code also listed at the bottom of this chapter:
Test model id comment
This comment is used to refer back to the test model from which this test code was generated. To enable this back referencing, this comment must not be changed under any circumstances.
/*
* DO NOT MODIFY THIS COMMENT
* Generated by devmate
* Test model: a8d26c27-a654-4d6d-857e-3e53b2919d0b
*/
Test Class
This is the test class which contains the test cases. The name of this class consists of the name of the method to be tested and the suffix “TestCase”.
public class AverageSpeedTestCase
{
...
}
Custom Factory Method
If custom factory methods are defined for the creation of complex data types, such as for the creation of a sensor stub, these are specified first in the test class. A custom factory method gets the name defined in the Equivalence Class Editor including the prefix “Create”. In the body of the method, a comment is written to remind you to revise the method to return the desired object. Additionally a NotImplementedException gets thrown.
private static AvgSpeed.ITrack CreateTrack(Int32 length)
{
// TODO implement factory
throw new NotImplementedException();
}
Test Case Sources
Test Case Sources
The tests created by devmate are also called parameterized test cases. The special thing about this type of test is that the values for these tests are given as a source. In our example the methods “AverageSpeedTest”, “AverageSpeedTestThrowingException” and “SideEffectOnThisTest” are the test methods and “PositiveTests”, “NegativeTests”, “TestsThrowingException” and “TestsAssertingSideEffectsOnThis” contain the data for the tests. For the data, devmate generates an object called ExpectedValueTestData. This object contains:
- the name of the test case
- the instance of the object whose method is being tested
- the parameters for the test
- the expected value
The example below shows the test source for positive tests.
private class PositiveTests : TheoryData<ExpectedValueTestData<UnitsNet.Speed>>
{
public PositiveTests()
{
Add(new ExpectedValueTestData<UnitsNet.Speed>
{
Name = @"p1",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(1, UnitsNet.Units.DurationUnit.Hour),
track = CreateTrack(0)
},
ExpectedValue = GeneratedDefaultForUnitsNetSpeed()
});
}
}
Test Case Method
The test case method gets as input the test data from the test case sources and executes the test. The test case method is characterized by the annotation “[Theory]” and by the reference to the test case source “[ClassData(typeof(“…”))]”. For positive, negative, for tests that expect an exception and for tests testing side effects, separate test case methods and test case sources are generated.
[Theory]
[ClassData(typeof(PositiveTests))]
[ClassData(typeof(NegativeTests))]
public void AverageSpeedTest(ExpectedValueTestData<UnitsNet.Speed> data)
{
var actual = data.ObjectUnderTest.AverageSpeed(ref data.Params.duration, data.Params.track);
Assert.Equal(data.ExpectedValue, actual);
}
Data comment
This comment is necessary to merge your manual changes to the test code with your changes deriving from the Equivalence Class Editor. It is important that you do not change this comment.
/*
WARNING: Modification of this comment will make it impossible to merge user-defined changes
BEGIN_CODEGEN_DATA
H4sIAAAAAAAAAN2WW2/aMBTHn4vEd7B4ChWEAoXe1koIaIs2LhLZwzTtISSn1GqwM9uhZdO++44NoSmh
Wdut2jqJi2P7+PzP75zYruzmc7ukMySDoUP6w07v/BNxLntj0h72+92Bo0cvgIFwFfhksiBO+0L3OSBV
T8HsmLiHfq3p1Q7KbrOxX973m375sHEA5To06pPaUfXI35ugRSWfy+ciSdmUjBcSLe02DwLwFOVM2sYF
9U7iKYOPjCr7XLgzuOXi5uSh6YleK4wmAfWIF7hSktYcFU5hHAL4WlrblZDPfc/ndkJB56idSOUqnN6a
T80ku2f+xsAkF6QtAOcsH8YqmlhFtNTWO5UKcYaIh87CAGbAFLlyPcXFQg+qa8FvCYPbOKQBV714Ivjd
Ow9CHZ9VRMU7P/CbIWgk4AoEMA/kSk+ix9I85ACUbRq2MdFNEppJAkMJTcdfUN5zhOvdrESbtmG4WrnH
VL1G5m4QQYlshPEB2FRdmzii19Oelt7rsmiGFTMJ4F1cLh1XuWdkxCVVdA66V96XwYJC4BMBKhLMuE5a
WbpjTcP88hkoEFa6rkrPyK39ngbLhUYgLnkkimiufa0NOhG+lzrUWgptPGQWMsalVIaaexkZufe+6dUI
tOpp4yzl9hjUAF9nqxBWC+apA9ITdJmqQiGj0B7PlsmSo+sBN4dE4t9K2n4rJ2oRAr+yWmIa6cpfh59k
zbax9lfuj8k3EHxJ/p9HVa5ml3ifskjB6wOtZQJlMHX19vFGoL5s28gE2kZy9FlE61uIKu3sTdXny1CW
q5kwzzlXT8W4/zjG/7IqWRQEKTQD7NyOp5GBB41GnOLlQSSPIfz5rMl8iRsa0Ride7jeg3tCoajnrC6k
c0791H3U2kaUUIYnHFIrpSGQeE8ppe5ZRnVp4zgmcBfiZRr89dm3MY73p8gNyOnaqZ3UaN27M8svC6Ul
JQhltwR0v6KxFfsorVZLntmPo9p+SP+aWfpc//MQHSyfNLpV3EaATERtFcnp2RMBxmzw8xMUKl7eZA0A
AA==
END_CODEGEN_DATA
*/
Here again the whole file:
/*
* DO NOT MODIFY THIS COMMENT
* Generated by devmate
* Test model: 49742f9d-a439-419f-8d49-21c60e5f4c8d
*/
namespace AvgSpeedTests
{
using System;
using System.Collections.Generic;
using Xunit;
public class AverageSpeedTestCase
{
private static AvgSpeed.ISpeedSensor NewFactory()
{
// TODO implement factory
throw new NotImplementedException();
}
private static AvgSpeed.Preferences GeneratedDefaultForAvgSpeedPreferences()
{
// TODO implement factory
throw new NotImplementedException();
}
private static AvgSpeed.ITrack CreateTrack(Int32 length)
{
// TODO implement factory
throw new NotImplementedException();
}
private static UnitsNet.Speed GeneratedDefaultForUnitsNetSpeed()
{
// TODO implement factory
throw new NotImplementedException();
}
private class PositiveTests : TheoryData<ExpectedValueTestData<UnitsNet.Speed>>
{
public PositiveTests()
{
Add(new ExpectedValueTestData<UnitsNet.Speed>
{
Name = @"p1",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(1, UnitsNet.Units.DurationUnit.Hour),
track = CreateTrack(0)
},
ExpectedValue = GeneratedDefaultForUnitsNetSpeed()
});
}
}
private class NegativeTests : TheoryData<ExpectedValueTestData<UnitsNet.Speed>>
{
public NegativeTests()
{
Add(new ExpectedValueTestData<UnitsNet.Speed>
{
Name = @"n1 - track: invalid",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(1, UnitsNet.Units.DurationUnit.Hour),
track = null
},
ExpectedValue = GeneratedDefaultForUnitsNetSpeed()
});
}
}
private class TestsThrowingException : TheoryData<TestThrowingExceptionData>
{
public TestsThrowingException()
{
Add(new TestThrowingExceptionData
{
Name = @"1",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(-1, UnitsNet.Units.DurationUnit.Day),
track = null
},
ExpectedException = typeof(Exception)
});
}
}
private class TestsAssertingSideEffectsOnThis : TheoryData<ExpectedValueTestData<AvgSpeed.Speedometer>>
{
public TestsAssertingSideEffectsOnThis()
{
Add(new ExpectedValueTestData<AvgSpeed.Speedometer>
{
Name = @"p1",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(1, UnitsNet.Units.DurationUnit.Hour),
track = CreateTrack(0)
},
ExpectedValue = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences())
});
}
}
[Theory]
[ClassData(typeof(PositiveTests))]
[ClassData(typeof(NegativeTests))]
public void AverageSpeedTest(ExpectedValueTestData<UnitsNet.Speed> data)
{
var actual = data.ObjectUnderTest.AverageSpeed(ref data.Params.duration, data.Params.track);
Assert.Equal(data.ExpectedValue, actual);
}
[Theory]
[ClassData(typeof(TestsThrowingException))]
public void AverageSpeedTestThrowingException(TestThrowingExceptionData data)
{
Assert.Throws(data.ExpectedException, () => data.ObjectUnderTest.AverageSpeed(ref data.Params.duration, data.Params.track));
}
[Theory]
[ClassData(typeof(TestsAssertingSideEffectsOnThis))]
public void SideEffectOnThisTest(ExpectedValueTestData<AvgSpeed.Speedometer> data)
{
try
{
_ = data.ObjectUnderTest.AverageSpeed(ref data.Params.duration, data.Params.track);
}
catch (Exception)
{
// Ignored since we only check side effects
}
Assert.Equal(data.ExpectedValue, data.ObjectUnderTest);
}
public class ExpectedValueTestData<TExpected>
{
public string Name;
public AvgSpeed.Speedometer ObjectUnderTest;
public Parameters Params;
public TExpected ExpectedValue;
public override string ToString()
{
return $"{Name}";
}
}
public class TestThrowingExceptionData
{
public string Name;
public AvgSpeed.Speedometer ObjectUnderTest;
public Parameters Params;
public Type ExpectedException;
public override string ToString()
{
return $"{Name}";
}
}
public class Parameters
{
public UnitsNet.Duration duration;
public AvgSpeed.ITrack track;
}
}
}
/*
WARNING: Modification of this comment will make it impossible to merge user-defined changes
BEGIN_CODEGEN_DATA
H4sIAAAAAAAAAO1YbW8iNxD+vEj8h1HUD8sJNkeOtkdSokZAenwIRGKvalVVlbM7C9suXmp7yaFT/nttr3fBsCSkyqW
q2igvxh7PyzOPZ+ycvqnX3sBgAuOJDzeTwej6Z/A/jKbQn9zcDMe+Wv0BKTIiMIS7NYS4WsixmveRC1ikISbn0Ol+2z
mLumGLdN51W512N2q9Dzvd1lk7+OYtfh11gveh3HNar9VrlCyQL0mAcLWaTZeIodLE67XP9ZqT8ZjOYLrmAhcXO5+9f
pokGIg4pdzTXsXBRuanjMbiQhlwltldEgcQJIRzaUR6P8PSUJ9w6b6jjDlLFq9kNMAFEXJD4Y830n+mSHnKYIz31yQQ
KVu7DbVJ73ROT8GfSODixTLBBVIBUS6kV8WcpfdA8R7GqRgVIhgOPwW4VAG4DeW586D9PejHLcMIGdIA+SYNA4xIloj
rlBVyW2Kv7uLIZyT4A/oM5aoeuyMq3p1BgnQm5q/kzkeZez5G4WmnqrAqJLTAF0cpp95tymMRr1DzG87Bn6PUOyCCfD
f8tJRMxvBHkmR6Xc/aYVxebrw0lLY05lEYAecqDF3l51Ga8y1mpzOWJxJ68P3Jsn3SNHOTu9+lmo80RKYPek+DUKZd/
04XKJC52wekeTRPG4WlW8LIghsD+oPSys1q4aMTZlKrBNwIlgENzLzbbm4m9aBcUp+8D2nGSpuO0KztWbx92zCrD4WY
haWUPo5XUkFOCsWKA9QY44y8LDUsjS9PDdqGFmjUziGmK5LE4X+NKzRLktdjiM6jr8qP7G5lubGpomT2RNTKPj2q1VX
z5KDaA9z4N1eN1hNUGJD185iwSVUPxHqJaeSWU89L/hXnyITMwTQOcRhFUj2fUH8eH1UwqkCvKBtPWHpOIam0+H+n+V
ud5qUhOEy8X3Im/arHfcU+lUzXUNe6cDQaB6Ss3mOkDL9WaRzu3cHdozoRhHJy66a2Igxk6BlJJEBqzduhjrdtx5Xx5
1J52r0irU1rVqfHwJIfA2/4pzThaiHLz6axbt34HoevuuoegdB+pT5YlHdhMlFoWW6HUe5rgtuA3uULo/hcYA6XnQqE
NjK5yCM8qixEuzAJc8M3x/63L8QpfdScgIhgDruNIDcN8ks+QkYzmjL5dpGPWfk0vkdIabKGYI6yenAZO2AO0JbWJ/l
aFZD9Xtl+KVeD6RfT+72DC6Ze3qqoX2zPV+EPO25YGza1OB9ya7X0wPYwf+uXUqnME1M4Ga/8dKoHdgdjKDJG4auTz8
rrh5OKRrwNyaM3oX8EC3l8YO84vwYUdr+0Yt/rkFCci2oozP8M9DEp2Kh+5PdfWnIwcJESAAA=
END_CODEGEN_DATA
*/
Preconditions for Generating Test Code
To generate test code, the following preconditions must be met:
- The test model must have at least one test case.
- In each test case a representative must be selected for each input factor.
If these preconditions are not met and you still try to generate test code, an error message pops up providing information about the unfulfilled preconditions.
Otherwise, a window opens allowing you to save the file to the desired location.
Adapt Test Code
Merge Test Code
You can modify the generated test code at any time. Your changes are not lost when you regenerate test code, because devmate takes care of merging your manual modifications to the test code and the changes derived from the Equivalence Class Editor. Let us look at a short example in this context.
First we generate test code from our AvgSpeed test model (avgspeed.tmdl) and store it with the name “Test.cs” in our test project.
Some factory methods are generated and one of them is:
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit preferredSpdUnit)
{
// TODO implement factory
throw new System.NotImplementedException();
}
Now we want to make an adjustment like implementing the factory method “CreatePreferences”.
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit preferredSpdUnit)
{
return new AvgSpeed.Preferences
{
PreferredLengthUnit = UnitsNet.Units.LengthUnit.KilolightYear,
PreferredSpeedUnit = UnitsNet.Units.SpeedUnit.KilometerPerHour
};
}
Then we switch back to the Equvilance Class Editor, define a second positive test and generate the code again. The test model remembers where you last generated the test code, that is why we will be offered to merge the file, overwrite it with the new one or to save it as a new file. We select the “Merge” option.

We can now see that our manual change has been retained and that the second positive test has also been created.
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit preferredSpdUnit)
{
return new AvgSpeed.Preferences
{
PreferredLengthUnit = UnitsNet.Units.LengthUnit.KilolightYear,
PreferredSpeedUnit = UnitsNet.Units.SpeedUnit.KilometerPerHour
};
}
...
private class PositiveTests : TheoryData<ExpectedValueTestData<UnitsNet.Speed>>
{
public PositiveTests()
{
Add(new ExpectedValueTestData<UnitsNet.Speed>
{
Name = @"p1",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(1, UnitsNet.Units.DurationUnit.Hour),
track = CreateTrack(0)
},
ExpectedValue = GeneratedDefaultForUnitsNetSpeed()
});
Add(new ExpectedValueTestData<UnitsNet.Speed>
{
Name = @"p2",
ObjectUnderTest = new AvgSpeed.Speedometer(NewFactory(), GeneratedDefaultForAvgSpeedPreferences()),
Params = new Parameters
{
duration = new UnitsNet.Duration(6, UnitsNet.Units.DurationUnit.Hour),
track = CreateTrack(0)
},
ExpectedValue = GeneratedDefaultForUnitsNetSpeed()
});
}
}
Merge Conflicts
Nevertheless you might run into a situation where changes cannot be merged automatically. This is the case when both changes take place at one and the same location. Let us look at this again with a short example.
The starting point is to manually implement the “CreatePreferences” except that we now also change the name of the parameter to “speedUnit”. Now we make a change in the Equivalence Class Editor at the same place, namely we change the name of the parameter from “Perferences” to “firstParameter”.

Then we generate the test code again, select the file “Test.cs” and choose the function “Merge”. You can now see that the merging has led to a conflict which we have to solve ourselves manually. Since the “three-way-diff” algorithm is used on merging, 3 different states are displayed.
- “.base” represents the state before the change.
- “.mine” is the change from the Equivalence Class Editor.
- “.theirs” stands for the manual change.
<<<<<<< .mine
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit firstParameter)
||||||| .base
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit preferredSpdUnit)
=======
private static AvgSpeed.Preferences CreatePreferences(UnitsNet.Units.SpeedUnit speedUnit)
>>>>>>> .theirs
{
return new AvgSpeed.Preferences
{
PreferredLengthUnit = UnitsNet.Units.LengthUnit.KilolightYear,
PreferredSpeedUnit = UnitsNet.Units.SpeedUnit.KilometerPerHour
};
}
To resolve the conflict, we remove everything manually except the desired code.
Generate boilerplate code
With just one click you can generate a boilerplate for your method to be tested where you can then enter your test data. To do this, right-click on the method, move the mouse over “Create Boilerplate with devmate” and then select your preferred test framework from the submenu. Alternatively, you can also use defined shortcuts:
- XUnit – ALT+D, ALT+X
- NUnit – ALT+D, ALT+N
- MSTest – ALT+D, ALT+M
