Suppose I have several small test cases. Easy enough to setup and easy enough to execute.
Suppose each small test case could perform multiple checks.
As I buy into the fact that one test case should ideally check one condition, I believe to have a few options to implement such a requirement.
- Create a base class with all the test cases' setup and execute parts. Descendant classes each do a verification of one requirement. (solution 1)
- Keep everything into one class, write verification methods for each requirement and let each verification method call a shared setup/exercise routine. (solution 2)
- (ab)Use NUnit's Sequential attribute to achieve one test for one condition. (solution 3)
As I don't really like solution 1 or 2 because it requires you to lookup what the setup and excercise part of the test does, would you categorize using NUnit's Sequential attribute in such a scenario to be a smell or a solution.
Original: multiple checks/testmethod
[Test]
public void MyTest1()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination X */)
this.myObjectList.Add(object1);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
Assert.AreSame(null, result.Object);
Assert.AreEqual(-1, result.Number);
Assert.True(result.Messages.Contains(ErrorMessage1));
}
[Test]
public void MyTest2()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination Y */)
IMyObject object2 = new MyObject(/* Parameter combination Y */)
this.myObjectList.Add(object1);
this.myObjectList.Add(object2);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
Assert.AreSame(object2, result.Object);
Assert.AreEqual(8, result.Number);
Assert.True(result.Messages.Contains(ErrorMessage1));
Assert.True(result.Messages.Contains(ErrorMessage2));
}
Solution 1: base class with setup and excerise code
[Test]
public virtual void MyTest1()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination X */)
this.myObjectList.Add(object1);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
MyTest1Check();
}
[Test]
public virtual void MyTest2()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination Y */)
IMyObject object2 = new MyObject(/* Parameter combination Y */)
this.myObjectList.Add(object1);
this.myObjectList.Add(object2);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
MyTest2Check();
}
...
/* Class ObjectCheck */
/* Check result object */
public override MyTest1Check()
{
Assert.AreSame(null, this.result.Object);
}
public override MyTest2Check()
{
Assert.AreSame(this.myObjectList[1], this.result.Object);
}
...
/* Class NumberCheck */
/* Check result number */
public override MyTest1Check()
{
Assert.AreSame(-1, this.result.Number);
}
public override MyTest2Check()
{
Assert.AreSame(8, this.result.Number);
}
...
/* and so on */
Solution 2: test methods calling setup/excercise methods
public void SetupMyTest1()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination X */)
this.myObjectList.Add(object1);
// Exercise
this.result = this.LPSolve(this.myObjectList);
}
[Test]
public void MyTest1ObjectShouldBeNull()
{
SetupMyTest1();
// Check
Assert.AreSame(null, this.result.Object);
}
[Test]
public void MyTest1ResultShouldBeMinus1()
{
SetupMyTest1();
// Check
Assert.AreEqual(-1, this.result.Number);
}
[Test]
public void MyTest1MessageShouldContainErrorMessage1()
{
SetupMyTest1();
// Check
Assert.True(this.result.Messages.Contains(ErrorMessage1));
}
public void SetupMyTest2()
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination Y */)
IMyObject object2 = new MyObject(/* Parameter combination Y */)
this.myObjectList.Add(object1);
this.myObjectList.Add(object2);
// Exercise
this.result = this.LPSolve(this.myObjectList);
}
[Test]
public void MyTest2ObjectShouldBeObject2()
{
SetupMyTest2();
// Check
Assert.AreSame(this.myObjectList[1], this.result.Object);
}
[Test]
public void MyTest2ResultShouldBeEight()
{
SetupMyTest2();
// Check
Assert.AreEqual(8, this.result.Number);
}
[Test]
public void MyTest2MessageShouldContainErrorMessage1()
{
SetupMyTest2();
// Check
Assert.True(this.result.Messages.Contains(ErrorMessage1));
}
[Test]
public void MyTest2MessageShouldContainErrorMessage2()
{
SetupMyTest2();
// Check
Assert.True(this.result.Messages.Contains(ErrorMessage2));
}
Solution 3: using attributes keeps the setup/excercise/check in one place and performs one check/test (the two methods are executed 3 & 4 times respectively)
[Test, Sequential]
public void MyTest1([Values("CheckObject", "CheckNumber", "CheckErrorMessage1") string check)
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination X */)
this.myObjectList.Add(object1);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
if (check == "CheckObject") Assert.AreSame(null, result.Object);
else if (check = "CheckNumber") Assert.AreEqual(-1, result.Number);
else if (check = "CheckErrorMessage1") Assert.True(result.Messages.Contains(ErrorMessage1));
else throw new ArgumentOutOfRangeException(check);
}
[Test, Sequential]
public void MyTest2([Values("CheckObject", "CheckNumber", "CheckErrorMessage1", "CheckErrorMessage2") string check)
{
// Setup
IMyObject object1 = new MyObject(/* Parameter combination Y */)
IMyObject object2 = new MyObject(/* Parameter combination Y */)
this.myObjectList.Add(object1);
this.myObjectList.Add(object2);
// Exercise
result = this.LPSolve(this.myObjectList);
// Check
if (check == "CheckObject") Assert.AreSame(object2, result.Object);
else if (check = "CheckNumber") Assert.AreEqual(8, result.Number);
else if (check = "CheckErrorMessage1") Assert.True(result.Messages.Contains(ErrorMessage1));
else if (check = "CheckErrorMessage2") Assert.True(result.Messages.Contains(ErrorMessage2));
else throw new ArgumentOutOfRangeException(check);
}