I'm pretty new to TDD and I have a hard time to understand how to test private members of the class (I know! It's private, shouldn't be tested - but please keep reading) . We might have a public function which sets private property and other public function that returns "something" based on that private property.
Let me show you a basic example:
public class Cell
{
public int X { get; set; }
public int Y { get; set; }
public string Value { get; set; }
}
public class Table
{
private Cell[,] Cells { get; }
public Table(Cell[,] cells)
{
Cells = cells;
}
public void SetCell(int x, int y, string value)
{
Cells[x, y].Value = value;
}
public void Reset()
{
for (int i = 0; i < Cells.GetLength(0); i++)
{
for (int j = 0; j < Cells.GetLength(1); j++)
{
Cells[i, j].Value = "";
}
}
}
public bool AreNeighborCellsSet(int x, int y)
{
bool areNeighborCellsSet = false;
// checking...
return areNeighborCellsSet;
}
}
In this example Cells
are private, because there's no reason to make them public. I don't need to know what's the value of particular Cell
outside this class. I just need an information if neighbor cells are empty.
1. How can I test Reset
method?
Technically I should create a Table with mocked array of cells. Call Reset
and then assert if every cell has empty Value
. But I can't actually check if they are empty or not.
2. In this case I would call Assert
many times (for every cell) - is it a good practice? I've read that "It's not!", but Reset resets all cells, so I have to somehow check every cell.
EDIT: Option 2:
public class Table
{
private Cell[,] Cells { get; }
public Table(int height, int width, ICellFactory cellFactory)
{
Cells = new ICell[height, width];
for (int i = 0; i < Cells.GetLength(0); i++)
{
for (int j = 0; j < Cells.GetLength(1); j++)
{
Cells[i, j].Value = cellFactory.Create(i, j);
}
}
}
// Rest is the same...
}
Your class have three public methods
void SetCell
void Reset
bool AreNeighborCellsSet
So all functionality should be tested only through those methods and with possible help of constructor input arguments.
I am afraid you are not doing TDD, because you are trying to test already implemented logic ( for
loop of internal member). With TDD you should write unit tests by using only public API of class under test.
When you test Reset
method you should think how it affect on results of other public methods. Table
class has only one method which return some value we can observe - bool AreNeighborCellsSet
- so seems like this is the only method against which we can execute our asserts.
For Reset
method you need to set cells so that AreNeighborCellsSet
returns true
. Then execute Reset
and assert that now AreNeighborCellsSet
returns false.
[Test]
public void AfterResetGivenCellShouldNotHaveNeighbors()
{
// Arrange
var cell = new Cell { X = 1, Y = 1, Value = "central" };
var neighborCell = new new Cell { X = 1, Y = 2, Value = "neighbor" };
var table = new Table(new[] { cell, neighborCell });
// table.AreNeighborCellsSet(cell.X, cell.Y) - should return true at this moment
// Act
table.Reset();
// Assert
table.AreNeighborCellsSet(cell.X, cell.Y).Should().BeFalse();
}
This is a good example of TDD (Test-Driven Development), where problems with testing is good sign that something wrong with design.
Actually, I think, in your case you don't need Reset
method at all - just create a new instance of Table
every time you need to reset it.
The answer of Ignas my be a workaround for the problem but I feel a need to clarify some design issues here:
Basically there is no need to check if loop iterates through whole collection. That is tested by the framework team in MS. What you need to do is to check if your new type (in this case Cell
) behaves properly.
In my opinion you're violating the SRP. There is really no need for Table
class to know how to reset this particular implementation of Cell
. If some day you decide to create a cell able to contain a picture let's say, you'll most likely feel a need to clear it in some other way than by setting an empty string to it's Value
property.
Start with abstracting Cell
to an interface. Then just add method Reset()
to the Cell
and call it in the loop in Table class for every cell.
That would allow you to create tests for your implementation of Cell
and there you can check if after calling Reset()
cell's value truly becomes null or empty or whatever you need :-)
There are ways to test private properties with no need for changing your code or adding extra code to your tested class, you can use testing tools that allows you to do so.
for example i used Typemock to change the logic of the Table c'tor to create a populated table and to get the private property Cells after calling the reset method:
public void TestMethod1()
{
var handle = Isolate.Fake.NextInstance<Table>(Members.CallOriginal, context =>
{
var tempcells = context.Parameters[0] as Cell[,];
for (int i = 0; i < tempcells.GetLength(0); i++)
{
for (int j = 0; j < tempcells.GetLength(1); j++)
{
tempcells[i, j] = cellFactory.Create(i, j);
}
}
context.Parameters[0] = tempcells;
//calling the original ctor with tempcells as the parameter
context.WillCallOriginal();
});
// calling the ctor with the custom logic
var testTable = new Table(new Cell[2,2]);
testTable.Reset();
// calling the private property
var resTable = Isolate.Invoke.Method(testTable, "get_Cells") as Cell[,];
// for asserting
var emptyCell = new Cell { Value = string.Empty };
for (int i = 0; i < 2; i++)
{
for(int j=0; j<2; j++)
{
Assert.AreEqual(emptyCell.Value, resTable[i, j].Value);
}
}
}
As Zegar mentioned in comments there could be several code design considerations, and probably writing tests first aka using TDD would help not to even run into such situations, however I think there is a simple workaround as well.
Table
class and not overriding it, therefore you can access the array outside of Table
class even though you are modifying it inside the class. FluentAssertions
and specifically ShouldBeEquivalentTo()
which is a very nice solution for arrays comparison. Nuget package . Sample test below.
[TestMethod]
public void TestMethod1()
{
// Arrange
var expectedCells = new Cell[2, 2];
expectedCells[0, 0] = new Cell { Value = string.Empty };
expectedCells[0, 1] = new Cell { Value = string.Empty };
expectedCells[1, 0] = new Cell { Value = string.Empty };
expectedCells[1, 1] = new Cell { Value = string.Empty };
var cells = new Cell[2,2];
cells[0,0] = new Cell { Value = "00" };
cells[0,1] = new Cell { Value = "01" };
cells[1,0] = new Cell { Value = "10" };
cells[1,1] = new Cell { Value = "11" };
var table = new Table(cells);
// Act
table.Reset();
// Assert
cells.ShouldBeEquivalentTo(expectedCells); // using FluentAssertions
}
To summarize and answer your questions.
cells
array you pass into the constructor.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.