如何编写具有渐进状态的 JUnit 测试
How to write JUnit tests with progressive state
我是 JUnit 的新手,正在寻求建议。首先,我选择了一个简单的辅助对象,用于管理 Table 个字符串。这些测试的进展与我读过的指南中鼓励的粒度相悖。
为了在不重复测试的情况下保持良好的粒度,我为相关方法(如 AddAndSize 或 GetAndPut)创建了一些测试。然而,让代码在多个测试中设置测试似乎很奇怪,我真的想用 jUnit 站稳脚跟并找到测试粒度的平衡。这是我的目标和测试用例 -
- 我有没有养成应该避免的坏习惯?
- 这是 "typical" jUnit 的使用吗?
- 测试摘要 类 时,我应该将 "Stub" 目标放在测试或主源文件夹中吗?
数据Table是一个Class待测
public class DataTable {
private ArrayList<String> columnNames = new ArrayList<String>();
private ArrayList<ArrayList<String>> theData = new ArrayList<ArrayList<String>>();
public DataTable() {
}
public int size() {
return theData.size();
}
public int cols() {
return columnNames.size();
}
public void addCol(String name) {
this.columnNames.add(name);
}
public int getCol(String name) {
return columnNames.indexOf(name);
}
public String getCol(int index) {
if (index < 0 | index >= columnNames.size()) {return "";}
return columnNames.get(index);
}
public String getValue(int row, String name) {
return getValue(row,this.getCol(name));
}
public String getValue(int row, int col) {
if (row < 0 | row >= theData.size()) {return "";}
if (col < 0 | col >= theData.get(row).size()) {return "";}
return theData.get(row).get(col);
}
public ArrayList<String> getNewRow() {
ArrayList<String> newRow = new ArrayList<String>();
this.theData.add(newRow);
return newRow;
}
}
这是我写的测试用例。
public class DataTableTest {
/**
* Test Constructor
*/
@Test
public void testDataTableConstruction() {
DataTable table = new DataTable();
assertNotNull(table);
}
/**
* Test GetNewRow and Size
*/
@Test
public void testGetNewRowAndSize() {
DataTable table = new DataTable();
assertEquals(0, table.size());
ArrayList<String> row = table.getNewRow();
assertNotNull(row);
assertEquals(1, table.size());
}
/**
*
*/
@Test
public void testColsAndAddCol() {
DataTable table = new DataTable();
assertEquals(0, table.cols());
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(3, table.cols());
}
/**
*
*/
@Test
public void testGetColInt() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals("One", table.getCol(0));
assertEquals("Two", table.getCol(1));
assertEquals("Three", table.getCol(2));
}
/**
*
*/
@Test
public void testGetColString() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(0, table.getCol("One"));
assertEquals(1, table.getCol("Two"));
assertEquals(2, table.getCol("Three"));
assertEquals(-1, table.getCol("Four"));
}
/**
*
*/
@Test
public void testGetValueIntString() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
assertEquals("R1C1", table.getValue(0, "One"));
assertEquals("R1C3", table.getValue(0, "Three"));
assertEquals("R2C2", table.getValue(1, "Two"));
assertEquals("", table.getValue(2, "One"));
assertEquals("", table.getValue(0, "Four"));
}
/**
*
*/
@Test
public void testGetValueIntInt() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
assertEquals("R1C1", table.getValue(0, 0));
assertEquals("R1C3", table.getValue(0, 2));
assertEquals("R2C2", table.getValue(1, 1));
assertEquals("", table.getValue(2, 0));
assertEquals("", table.getValue(0, 3));
}
}
我建议使用 the Enclosed
runner,并为每个要用于测试的初始状态设置不同的静态嵌套 类:
@RunWith(Enclosed.class)
public class DataTableTest {
@RunWith(JUnit4.class)
public static class WhenTableIsEmpty {
private final DataTable table = new DataTable();
@Test
public void rowCountShouldReturnZero() {
assertEquals(0, table.rowCount());
}
@Test
public void addingRowsShouldSucceed() {
table.addRow("row1");
assertEquals(1, table.rowCount());
}
...
}
@RunWith(JUnit4.class)
public static class WhenTableHasOneRow {
private final DataTable table = new DataTable();
@Before
public void addOneRow() {
table.addRow("row1");
}
@Test
public void rowCountShouldReturnOne() {
assertEquals(1, table.rowCount());
}
...
}
}
请注意,嵌套的 类 需要是 static
(我忽略了在此答案的初始版本中添加 static
关键字)
当您 运行 中的测试 IDE 时,测试用例将具有如下可读的名称:
- DataTableTest.WhenTableIsEmpty.rowCountShouldReturnZero
- DataTableTest.WhenTableIsEmpty.addingRowsShouldSucceed
请注意,您不需要为嵌套的 类 使用 JUnit4
运行ner。您也可以使用 Parameterized
或 Theories
.
我会像任何软件一样,将通用部分复制到一个地方等等。我的建议:
public class MyTests {
private DataTable table;
@Before
public void setup() {
table = new DataTable();
assertEquals(0, table.size());
assertEquals(0, table.cols());
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(3, table.cols());
}
/**
* Test GetNewRow and Size
*/
@Test
public void testGetNewRowAndSize() {
ArrayList<String> row = table.getNewRow();
assertNotNull(row);
assertEquals(1, table.size());
}
@Test
public void testGetColInt() {
assertEquals("One", table.getCol(0));
assertEquals("Two", table.getCol(1));
assertEquals("Three", table.getCol(2));
}
@Test
public void testGetColString() {
assertEquals(0, table.getCol("One"));
assertEquals(1, table.getCol("Two"));
assertEquals(2, table.getCol("Three"));
assertEquals(-1, table.getCol("Four"));
}
private void addRows() {
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
}
@Test
public void testGetValueIntString() {
addRows();
assertEquals("R1C1", table.getValue(0, "One"));
assertEquals("R1C3", table.getValue(0, "Three"));
assertEquals("R2C2", table.getValue(1, "Two"));
assertEquals("", table.getValue(2, "One"));
assertEquals("", table.getValue(0, "Four"));
}
@Test
public void testGetValueIntInt() {
addRows();
assertEquals("R1C1", table.getValue(0, 0));
assertEquals("R1C3", table.getValue(0, 2));
assertEquals("R2C2", table.getValue(1, 1));
assertEquals("", table.getValue(2, 0));
assertEquals("", table.getValue(0, 3));
}
}
不确定前面的断言是否有用,如果这些断言不成立,任何测试都会失败。无论如何,在我看来,编写测试没有什么特别的魔法..
只有少数几项我认为值得测试:
getCol(int)
getValue(int, String)
getValue(int, int)
- 可能
getNewRow()
我这样说的原因:你的大部分功能都依赖 ArrayList
,你不应该测试任何专门委托给已知和测试的 class.
测试时你想在这里做的主要事情是:
- 确定您正在测试的事物的状态
- 只测试那个东西
让我们来看看 getCol(int)
的测试。为了清楚起见,我将在此处重新发布代码。
public String getCol(int index) {
if (index < 0 | index >= columnNames.size()) {return "";}
return columnNames.get(index);
}
您可以测试四项内容:
index < 0 && index >= columnNames.size()
index >= 0 && index >= columnNames.size()
index < 0 && index < columnNames.size()
index >= 0 && index < columnNames.size()
或者,如果您将 |
更改为 ||
,您只需测试三件事:
index < 0
index >= columnNames.size()
index >= 0 && index < columnNames.size()
原因是 |
没有短路,条件的两边都会被计算。
设置您的状态必须在逐个测试的基础上进行。通过这种方式,您可以清楚地了解您正在测试的内容、失败的原因,并且可以更轻松地解决问题。我不会详细说明您需要的每项测试(因为有很多),但他们读的是这样的:
@Test
public void testGetColWithIndexLessThanZero() {}
确定每一项填写你的考试状态。如果您注意到自己复制了状态生成,那么 仅 那么您可以在测试本身中创建一个辅助方法来帮助解决这个问题。
When testing abstract classes, should I place the "Stub" target in the Tests or Main source folder?
您不能直接实例化一个抽象 class,因此您能够测试它的唯一方法是创建一个扩展它的 class。您可以直接执行此操作(扩展抽象 class 的 actual class),或在测试中(创建扩展的匿名 class摘要 class).
我是 JUnit 的新手,正在寻求建议。首先,我选择了一个简单的辅助对象,用于管理 Table 个字符串。这些测试的进展与我读过的指南中鼓励的粒度相悖。
为了在不重复测试的情况下保持良好的粒度,我为相关方法(如 AddAndSize 或 GetAndPut)创建了一些测试。然而,让代码在多个测试中设置测试似乎很奇怪,我真的想用 jUnit 站稳脚跟并找到测试粒度的平衡。这是我的目标和测试用例 -
- 我有没有养成应该避免的坏习惯?
- 这是 "typical" jUnit 的使用吗?
- 测试摘要 类 时,我应该将 "Stub" 目标放在测试或主源文件夹中吗?
数据Table是一个Class待测
public class DataTable {
private ArrayList<String> columnNames = new ArrayList<String>();
private ArrayList<ArrayList<String>> theData = new ArrayList<ArrayList<String>>();
public DataTable() {
}
public int size() {
return theData.size();
}
public int cols() {
return columnNames.size();
}
public void addCol(String name) {
this.columnNames.add(name);
}
public int getCol(String name) {
return columnNames.indexOf(name);
}
public String getCol(int index) {
if (index < 0 | index >= columnNames.size()) {return "";}
return columnNames.get(index);
}
public String getValue(int row, String name) {
return getValue(row,this.getCol(name));
}
public String getValue(int row, int col) {
if (row < 0 | row >= theData.size()) {return "";}
if (col < 0 | col >= theData.get(row).size()) {return "";}
return theData.get(row).get(col);
}
public ArrayList<String> getNewRow() {
ArrayList<String> newRow = new ArrayList<String>();
this.theData.add(newRow);
return newRow;
}
}
这是我写的测试用例。
public class DataTableTest {
/**
* Test Constructor
*/
@Test
public void testDataTableConstruction() {
DataTable table = new DataTable();
assertNotNull(table);
}
/**
* Test GetNewRow and Size
*/
@Test
public void testGetNewRowAndSize() {
DataTable table = new DataTable();
assertEquals(0, table.size());
ArrayList<String> row = table.getNewRow();
assertNotNull(row);
assertEquals(1, table.size());
}
/**
*
*/
@Test
public void testColsAndAddCol() {
DataTable table = new DataTable();
assertEquals(0, table.cols());
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(3, table.cols());
}
/**
*
*/
@Test
public void testGetColInt() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals("One", table.getCol(0));
assertEquals("Two", table.getCol(1));
assertEquals("Three", table.getCol(2));
}
/**
*
*/
@Test
public void testGetColString() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(0, table.getCol("One"));
assertEquals(1, table.getCol("Two"));
assertEquals(2, table.getCol("Three"));
assertEquals(-1, table.getCol("Four"));
}
/**
*
*/
@Test
public void testGetValueIntString() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
assertEquals("R1C1", table.getValue(0, "One"));
assertEquals("R1C3", table.getValue(0, "Three"));
assertEquals("R2C2", table.getValue(1, "Two"));
assertEquals("", table.getValue(2, "One"));
assertEquals("", table.getValue(0, "Four"));
}
/**
*
*/
@Test
public void testGetValueIntInt() {
DataTable table = new DataTable();
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
assertEquals("R1C1", table.getValue(0, 0));
assertEquals("R1C3", table.getValue(0, 2));
assertEquals("R2C2", table.getValue(1, 1));
assertEquals("", table.getValue(2, 0));
assertEquals("", table.getValue(0, 3));
}
}
我建议使用 the Enclosed
runner,并为每个要用于测试的初始状态设置不同的静态嵌套 类:
@RunWith(Enclosed.class)
public class DataTableTest {
@RunWith(JUnit4.class)
public static class WhenTableIsEmpty {
private final DataTable table = new DataTable();
@Test
public void rowCountShouldReturnZero() {
assertEquals(0, table.rowCount());
}
@Test
public void addingRowsShouldSucceed() {
table.addRow("row1");
assertEquals(1, table.rowCount());
}
...
}
@RunWith(JUnit4.class)
public static class WhenTableHasOneRow {
private final DataTable table = new DataTable();
@Before
public void addOneRow() {
table.addRow("row1");
}
@Test
public void rowCountShouldReturnOne() {
assertEquals(1, table.rowCount());
}
...
}
}
请注意,嵌套的 类 需要是 static
(我忽略了在此答案的初始版本中添加 static
关键字)
当您 运行 中的测试 IDE 时,测试用例将具有如下可读的名称:
- DataTableTest.WhenTableIsEmpty.rowCountShouldReturnZero
- DataTableTest.WhenTableIsEmpty.addingRowsShouldSucceed
请注意,您不需要为嵌套的 类 使用 JUnit4
运行ner。您也可以使用 Parameterized
或 Theories
.
我会像任何软件一样,将通用部分复制到一个地方等等。我的建议:
public class MyTests {
private DataTable table;
@Before
public void setup() {
table = new DataTable();
assertEquals(0, table.size());
assertEquals(0, table.cols());
table.addCol("One");
table.addCol("Two");
table.addCol("Three");
assertEquals(3, table.cols());
}
/**
* Test GetNewRow and Size
*/
@Test
public void testGetNewRowAndSize() {
ArrayList<String> row = table.getNewRow();
assertNotNull(row);
assertEquals(1, table.size());
}
@Test
public void testGetColInt() {
assertEquals("One", table.getCol(0));
assertEquals("Two", table.getCol(1));
assertEquals("Three", table.getCol(2));
}
@Test
public void testGetColString() {
assertEquals(0, table.getCol("One"));
assertEquals(1, table.getCol("Two"));
assertEquals(2, table.getCol("Three"));
assertEquals(-1, table.getCol("Four"));
}
private void addRows() {
ArrayList<String> row = table.getNewRow();
row.add("R1C1");
row.add("R1C2");
row.add("R1C3");
row = table.getNewRow();
row.add("R2C1");
row.add("R2C2");
row.add("R2C3");
}
@Test
public void testGetValueIntString() {
addRows();
assertEquals("R1C1", table.getValue(0, "One"));
assertEquals("R1C3", table.getValue(0, "Three"));
assertEquals("R2C2", table.getValue(1, "Two"));
assertEquals("", table.getValue(2, "One"));
assertEquals("", table.getValue(0, "Four"));
}
@Test
public void testGetValueIntInt() {
addRows();
assertEquals("R1C1", table.getValue(0, 0));
assertEquals("R1C3", table.getValue(0, 2));
assertEquals("R2C2", table.getValue(1, 1));
assertEquals("", table.getValue(2, 0));
assertEquals("", table.getValue(0, 3));
}
}
不确定前面的断言是否有用,如果这些断言不成立,任何测试都会失败。无论如何,在我看来,编写测试没有什么特别的魔法..
只有少数几项我认为值得测试:
getCol(int)
getValue(int, String)
getValue(int, int)
- 可能
getNewRow()
我这样说的原因:你的大部分功能都依赖 ArrayList
,你不应该测试任何专门委托给已知和测试的 class.
测试时你想在这里做的主要事情是:
- 确定您正在测试的事物的状态
- 只测试那个东西
让我们来看看 getCol(int)
的测试。为了清楚起见,我将在此处重新发布代码。
public String getCol(int index) {
if (index < 0 | index >= columnNames.size()) {return "";}
return columnNames.get(index);
}
您可以测试四项内容:
index < 0 && index >= columnNames.size()
index >= 0 && index >= columnNames.size()
index < 0 && index < columnNames.size()
index >= 0 && index < columnNames.size()
或者,如果您将 |
更改为 ||
,您只需测试三件事:
index < 0
index >= columnNames.size()
index >= 0 && index < columnNames.size()
原因是 |
没有短路,条件的两边都会被计算。
设置您的状态必须在逐个测试的基础上进行。通过这种方式,您可以清楚地了解您正在测试的内容、失败的原因,并且可以更轻松地解决问题。我不会详细说明您需要的每项测试(因为有很多),但他们读的是这样的:
@Test
public void testGetColWithIndexLessThanZero() {}
确定每一项填写你的考试状态。如果您注意到自己复制了状态生成,那么 仅 那么您可以在测试本身中创建一个辅助方法来帮助解决这个问题。
When testing abstract classes, should I place the "Stub" target in the Tests or Main source folder?
您不能直接实例化一个抽象 class,因此您能够测试它的唯一方法是创建一个扩展它的 class。您可以直接执行此操作(扩展抽象 class 的 actual class),或在测试中(创建扩展的匿名 class摘要 class).