[英]How to unit test private properties?
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) . 我是TDD的新手,我很难理解如何测试班级的私有成员(我知道!它是私有的,不应该测试-但请继续阅读) 。 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. 在此示例中,
Cells
是私有的,因为没有理由将它们公开。 I don't need to know what's the value of particular Cell
outside this class. 我不需要知道此类以外的特定
Cell
的值是什么。 I just need an information if neighbor cells are empty. 如果邻居单元格为空,我只需要一个信息。
1. How can I test Reset
method? 1.如何测试
Reset
方法?
Technically I should create a Table with mocked array of cells. 从技术上讲,我应该使用模拟的单元格数组创建表。 Call
Reset
and then assert if every cell has empty Value
. 调用
Reset
,然后断言每个单元格的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? 2.在这种情况下,我会(对于每个单元格)多次调用
Assert
-这是一个好习惯吗? I've read that "It's not!", but Reset resets all cells, so I have to somehow check every cell. 我读过“不是!”,但是“重置”会重置所有单元格,因此我必须以某种方式检查每个单元格。
EDIT: Option 2: 编辑:选项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). 恐怕您没有执行TDD,因为您正在尝试测试已实现的逻辑(
for
内部成员的循环)。 With TDD you should write unit tests by using only public API of class under test. 使用TDD时,您应仅使用被测类的公共API编写单元测试。
When you test Reset
method you should think how it affect on results of other public methods. 在测试
Reset
方法时,您应该考虑它如何影响其他公共方法的结果。 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. Table
类只有一个返回一些我们可以观察到的值的方法bool AreNeighborCellsSet
因此看来这是唯一可以执行断言的方法。
For Reset
method you need to set cells so that AreNeighborCellsSet
returns true
. 对于
Reset
方法,您需要设置单元格,以便AreNeighborCellsSet
返回true
。 Then execute Reset
and assert that now AreNeighborCellsSet
returns false. 然后执行
Reset
并断言现在AreNeighborCellsSet
返回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. 这是TDD(测试驱动开发)的一个很好的例子,其中测试问题表明设计存在问题。
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. 实际上,我认为,就您而言,您根本不需要
Reset
方法-每次需要重设Table
时,只需创建一个新的Table
实例即可。
The answer of Ignas my be a workaround for the problem but I feel a need to clarify some design issues here: Ignas的答案是解决该问题的方法,但我认为有必要在此澄清一些设计问题:
Basically there is no need to check if loop iterates through whole collection. 基本上不需要检查循环是否遍历整个集合。 That is tested by the framework team in MS.
这是由MS中的框架团队测试的。 What you need to do is to check if your new type (in this case
Cell
) behaves properly. 您需要做的是检查您的新类型(在本例中为
Cell
)是否运行正常。
In my opinion you're violating the SRP. 我认为您违反了SRP。 There is really no need for
Table
class to know how to reset this particular implementation of Cell
. 实际上,
Table
类不需要知道如何重置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. 假设有一天,如果您决定创建一个能够包含图片的单元格,则很可能需要以其他方式清除它,而不是为其
Value
属性设置一个空字符串。
Start with abstracting Cell
to an interface. 从将
Cell
抽象到接口开始。 Then just add method Reset()
to the Cell
and call it in the loop in Table class for every cell. 然后只需将方法
Reset()
添加到Cell
并在Table类的循环中为每个单元调用它。
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 :-) 这将允许您为
Cell
的实现创建测试,然后可以在调用Reset()
之后检查是否将cell的值真正变为null或为空,或者您需要什么:-)
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: 例如,在调用reset方法后,我使用Typemock更改了Table c'tor的逻辑以创建填充表并获取私有属性Cells:
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. 正如Zegar在评论中提到的那样,可能有一些代码设计方面的考虑,并且可能首先使用TDD编写测试也可能不会出现这种情况,但是我认为也有一个简单的解决方法。
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. Table
类中,而不是覆盖它,因此,即使您在类中进行了修改,也可以在Table
类之外访问该数组。 FluentAssertions
and specifically ShouldBeEquivalentTo()
which is a very nice solution for arrays comparison. FluentAssertions
,特别是ShouldBeEquivalentTo()
,这是一个很好的数组比较解决方案。 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. cells
数组传递给构造函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.