git 贡献者代码统计
我很幸运用于作业公司 ,使得它能够有一个良好的技术职业生涯,让我没有成为一名经理只是为了得到更多的钱。
我喜欢成为个人贡献者,因为当我自己完成任务时,它给了我成就感。
无论如何,我相信在编写代码时,经理和个人贡献者之间的区别也是有用的。
让我以康威的《人生游戏 》为例进行说明。
婴儿脚步
在代码撤退中 ,您会发现从底部(单元)开始解决问题的人,以及从顶部(宇宙,世界,网格,游戏或任何他们称之为的人)开始解决问题的人。
有趣的是,两个群体都倾向于遇到类似的问题。
从底部开始,可以轻松快速地在单元中实施规则。 但是,问题就变成了如何将细胞连接到宇宙中。
这通常看起来似乎步骤繁琐,无法正确测试,例如每两分钟进行一次新的绿色测试。
从顶部开始,请确保我们没有做任何不必要的事情或构建任何无法正确连接的部件。 但是,在这里也很难保持步伐缓慢。
单一责任原则
要演化整个宇宙,至少需要发生以下事情:
- 遍历所有单元格
- 确定每个单元的邻居
- 计算存活邻居的数量
- 根据单元格的当前状态和存活邻居的数量确定单元格的下一个状态
根据某些实现选择(例如使Universe保持不变),可能还有更多工作要做。
然而, 单一责任原则告诉我们,班级应该只有一个改变的理由。 您可以将项目#2和#3视为一项责任(或#3和#4),但显然涉及的责任不止一项。
经理班和个人贡献者班以及如何对其进行测试
现在,请记住经理/个人贡献者(IC)的区别。 经理管理集成电路; 那是他们的全部工作。 我们也应该把管理其他班级当作责任 。
这意味着一个类要么实现某种逻辑(IC), 要么与其他类进行协调(管理器),但不能同时实现两者。
我认为这也与基于状态与基于交互的测试争论有关:我们应该对IC类使用基于状态的测试,对于经理类使用基于交互的测试( 使用模拟)。
这可以解释为什么两个代码撤退小组似乎都陷入困境:他们必须从基于状态的测试切换到基于交互的测试,而且似乎许多开发人员对后者都没有太多的经验。
试驾经理类
因此,让我们尝试对经理类进行基于交互的测试。 我将继续以“生命游戏”为例。
我想从一个体系结构的选择开始:我希望Universe是不可变的,以便以后引入多线程很容易。 随着游戏的发展,我必须为每一代人创建一个新的世界:
public class GameTest {
@Test
public void clonesImmutableUniverseWhenEvolving() {
Universe universe = mock(Universe.class, "old");
Universe nextGeneration = mock(Universe.class, "new");
when(universe.clone()).thenReturn(nextGeneration);
Game game = new Game(universe);
assertSame("Initial", universe, game.universe());
game.evolve();
assertSame("Next generation", nextGeneration,
game.universe());
}
}
我正在使用Mockito定义Game如何与Universe交互。 模拟与接口的配合效果最好,因此Universe是一个接口。
这意味着游戏不知道宇宙的类型,因此无法创建它的新实例。 Universe为此提供了一种工厂方法 clone() 。
如果Universe是不可变的,则应在其构造函数中提供其状态。 此构造函数由clone()调用,因此该方法需要新状态作为其输入。
该状态是什么样的? 在这一点上,除了宇宙包含细胞之外,我们对宇宙一无所知。 让我们保持简单,并提供单元格作为输入:
public class GameTest {
@SuppressWarnings("unchecked")
@Test
public void clonesImmutableUniverseWhenEvolving() {
Universe universe = mock(Universe.class, "old");
Universe nextGeneration = mock(Universe.class, "new");
when(universe.clone(any(Iterable.class)))
.thenReturn(nextGeneration);
Game game = new Game(universe);
assertSame("Initial", universe, game.universe());
game.evolve();
assertSame("Next generation", nextGeneration,
game.universe());
}
}
好吧,那么谁来提供这些单元格给clone() ? 游戏是经理,因此它应该只进行管理,而不提供逻辑。 宇宙是负责电池及其互连的IC,因此它已经承担了责任。
因此,似乎我们需要一种新的类型。 新类型应该由游戏来管理,因为这是我们唯一的经理类。 因此,我们应该在GameTest中为此交互添加一个新测试。
新类型应负责根据旧状态确定宇宙的新状态。 这是游戏规则起作用的地方,所以我们将新类型称为Rules 。
让我们从测试游戏管理规则开始:
public class GameTest {
@Test
public void consultsRulesWhenEvolving() {
Rules rules = mock(Rules.class);
Game game = new Game(null, rules);
assertSame("Rules", rules, game.rules());
}
}
现在我们要测试一下evolve()参考了规则。 由于游戏是经理,因此它只能请求协作者的输出,并将其作为输入提供给另一个协作者。 它本身不应该包含任何逻辑,因为那样会使它成为IC。
游戏当前唯一拥有的其他协作者是宇宙,并且似乎是提供单元的正确候选者。 但是,我们需要比单元更多的东西来实现规则:我们还需要每个单元的存活邻居数。
显然,宇宙是确定细胞邻居的正确地方。 还应该算活着的吗?
我猜是可以的,但是我不是特别喜欢:生活规则使用活着的邻居的数量,但是不难想像另一套规则也取决于死去的邻居的数量。 因此,我认为规则应该计算在内。
这意味着规则的输入是一个单元格及其邻居。 由于Java不允许返回两条信息,因此我们需要将它们组合在一起。 让我们将组合称为邻域:
public class NeighborhoodTest {
@Test
public void holdsACellAndItsNeighbors() {
Cell cell = mock(Cell.class, "cell");
List<Cell> neighbors = Arrays.asList(
mock(Cell.class, "neighbor1"),
mock(Cell.class, "neighbor2"));
Neighborhood neighborhood = new Neighborhood(cell,
neighbors);
assertSame("Cell", cell, neighborhood.cell());
assertEquals("Neighbors", neighbors,
neighborhood.neighbors());
}
}
现在,我们可以使Universe返回它包含的每个单元的邻域,并验证这些邻域是否用作规则的输入:
public class GameTest {
@SuppressWarnings("unchecked")
@Test
public void clonesImmutableUniverseWhenEvolving() {
Universe universe = mock(Universe.class, "old");
Universe nextGeneration = mock(Universe.class, "new");
when(universe.clone(any(Iterable.class)))
.thenReturn(nextGeneration);
when(universe.neighborhoods()).thenReturn(
new ArrayList<Neighborhood>());
Game game = new Game(universe, mock(Rules.class));
assertSame("Initial", universe, game.universe());
game.evolve();
assertSame("Next generation", nextGeneration,
game.universe());
}
@Test
public void consultsRulesWhenEvolving() {
Universe universe = mock(Universe.class);
Neighborhood neighborhood1 = new Neighborhood(
mock(Cell.class),
Arrays.asList(mock(Cell.class)));
Neighborhood neighborhood2 = new Neighborhood(
mock(Cell.class),
Arrays.asList(mock(Cell.class)));
when(universe.neighborhoods()).thenReturn(
Arrays.asList(neighborhood1, neighborhood2));
Rules rules = mock(Rules.class);
Game game = new Game(universe, rules);
assertSame("Rules", rules, game.rules());
game.evolve();
verify(rules).nextGeneration(neighborhood1);
verify(rules).nextGeneration(neighborhood2);
}
}
下一步是确保规则的输出用于构造新的Universe:
public class GameTest {
@Test
public void consultsRulesWhenEvolving() {
Universe universe = mock(Universe.class);
Neighborhood neighborhood1 = new Neighborhood(
mock(Cell.class),
Arrays.asList(mock(Cell.class)));
Neighborhood neighborhood2 = new Neighborhood(
mock(Cell.class),
Arrays.asList(mock(Cell.class)));
when(universe.neighborhoods()).thenReturn(
Arrays.asList(neighborhood1, neighborhood2));
Rules rules = mock(Rules.class);
Cell cell1 = mock(Cell.class, "cell1");
Cell cell2 = mock(Cell.class, "cell2");
when(rules.nextGeneration(neighborhood1))
.thenReturn(cell1);
when(rules.nextGeneration(neighborhood2))
.thenReturn(cell2);
Game game = new Game(universe, rules);
assertSame("Rules", rules, game.rules());
game.evolve();
verify(universe).clone(eq(
Arrays.asList(cell1, cell2)));
}
}
至此,我们完成了Game类。 剩下要做的就是为我们介绍的Universe , Cell和Rules的三个接口创建实现。 这些中的每一个都是IC类,因此使用基于状态的测试来进行测试驱动相当简单。
结论
我发现经理和个人贡献者类别之间的区别有助于我确定下一个测试应该是什么。
你怎么看? 这可能是正确的测试驱动开发理论所缺少的一部分吗?
翻译自: https://www.javacodegeeks.com/2014/04/managers-and-individual-contributors-in-code.html
git 贡献者代码统计