cleancodersのStack Kataに解説を付けた
cleancoders.comというサイトのプログラミング動画を見て勉強しています。 その中で「Stack Kata」という、StackクラスをTDDで作成するビデオがあります。このビデオは無料で公開されています。
https://cleancoders.com/episode/clean-code-episode-4-sc-1-stack
このビデオの中にもある程度の解説はあるのですが、TDD初心者にとっては早すぎてついていけません。
そこで自分の理解をもとに解説を付けてみました。35分の動画に対する詳細解説なのでかなりの長文です。「私個人の理解」なので合っているとは限りませんが、もし同じビデオで勉強している人がいれば参考にしてみてください。
正解が知りたい人はcleancoders.comのAdvanced TDDというシリーズで勉強してください。こちらの動画は有料です。(そして私に教えてください)
テストクラス作成
試験名:nothing
最初にテストクラスを作成して環境の動作確認を行う。
Stackクラスを作成する予定なのでStackTestクラスを作成する。 クラス名をTestStackとしなかったのはUncle Bobの好みと思われる。 想像するにテストコードとコードを同じpackageに配置したとき、StackクラスとStackTestクラスがアルファベット順にソートされて上下に並んで表示されることを期待しているのだろう。 テストコード用の別ディレクトリにテストコードを配置する場合はこの命名規則は意味がない。
package名は仮にstackとする。 気に入らなければ後で移動する。
StackTest#nothingを作成する。 @Testのためにjunitをimportする。 実行してテスト環境が整っていることを確認する。
package stack; import org.junit.*; public class StackTest { @Test public void nothing() throws Exception { }
無事、確認ができたらStackTest#nothingは削除する。
空のStackオブジェクトを作成する
試験名:createStack
Red
Stackクラスを作りたい。 しかしTDDでは先に試験をしなくてはコードは書けない。 Stackクラスを作成する試験として、Stackオブジェクトを作成する試験を実施する。
StackTest#createStackを作成する。 既存のStackクラスと衝突するので最初はMyStackクラスと命名する。
package stack; import org.junit.*; public class StackTest { @Test public void createStack() throws Exception { Stack stack = new Stack() }
Green
compile error
MyStackクラスがないのでIDEが警告を出す。 コンパイラを通すためにMyStackクラスを作成する。 実装はコンパイラを通す最小限のものに留めたいのでMyStackの中身は空にする。 StackTest#createStackに戻ってMyStackをStackにRenameする。 テストを実行してPassすることを確認する。
package stack; public class Stack { }
空のStackオブジェクトが空なことを確認する1
試験名:createStack -> newlyCreateStack_ShouldBeEmpty
Red
createStackは現時点でなにもAssertしていない。次にStackが空であることの確認を行うが、なぜこの試験を選んだのか、考え方が不明。仮説を以下に上げる。
・考え方1
複数より単数、単数より空の方が原始的。 空オブジェクトで試験できることを考える。 Stackが空であることの確認から行う。
・考え方2
最も周辺的(peripheral)な機能に対して最も原始的(degenerate)な確認を行うこととする。 push/popなどの核心機能は後回しにしたい。 topやfindも核心機能がないと試験できないので後回しにしたい。 isEmptyやgetSizeは核心部分が不在でも試験できる。
・考え方3
push/popの周辺的な部分を試験するにはまずはサイズを確認する機能が必要だ。 「値を格納する、取り出す」という核心部分は基本的な構造が見えてからにしたい。 そこでサイズ確認から行うこととする。
この試験の中でオブジェクトを作成するためcreateStackは不要となる。 よってcreateStackは削除する。 後々、削除することがわかっている試験を作成するのはTDDではよくある。
Assert.assertTrueのためにorg.junit.Assert.*をstatic importする。 名前をStack#newlyCreateStack_ShouldBeEmptyに変更する
@Test public void newlyCreateStack_ShouldBeEmpty() throws Exception { Stack stack = new Stack() assertTrue(stack.isEmpty()); }
Green
compile error
Stack#isEmptyがないのでコンパイルエラーが出る。 コンパイラを通すためにStack#isEmptyをGetterとして作成する。 実装は最小限に留めたいのでemptyは初期化しない(= false) 実行してAssertion ErrorでFailすることを確認する。
package stack; public class Stack { private boolean empty; public boolean isEmpty() { return empty; } }
assertion error
Assertion Errorを解決するために必要最小限の変更を行う。 emptyをtrueで初期化する。 実行してPassすることを確認する。
private boolean empty = true;
Stackクラスの最終形を考えるとtrueというリテラルを書くのは間違っている。 サイズを管理するフィールドを作成してそのサイズから真偽値を判定すべき。 しかしそれはTDDではやりすぎ。 GreenではTestをPassさせるための最小限、かつもっとも非汎用的な実装を行うことが求められる。 突然、汎化されたコードを作成するとTestが書かれていないパターンが発生し、それらを後追いで書くことになってしまう。 後々、Stuckする可能性や最適でないアルゴリズムが生まれる可能性も高くなる。 今回のテストは変数、計算、分岐など汎用的実装を導入する前に、リテラル/定数でPassできる。 TDDではリテラル/定数でPassさせるのが正しい。
空のStackオブジェクトが空なことを確認する2
試験名:newlyCreateStack_ShouldBeEmpty
Red
サイズを確認するgetSizeメソッドも必要だ。 まずはもっとも原始的な空オブジェクトで試験をする。 newlyCreateStack_ShouldBeEmptyと実質、同じ試験なので同じテストに含める。 実質、同じ試験なのでSingle Assertion Ruleの違反ではない。
@Test public void newlyCreateStack_ShouldBeEmpty() throws Exception { assertTrue(stack.isEmpty()); assertEquals(0, stack.getSize()); }
Green
compile error
コンパイラを通すためにStack#getSizeをGetterとして作成する。 実装は最小限に留めたいのでsizeは初期化しない(= 0) 実行してPassすることを確認する。
1回Pushするとサイズが1になることを確認
試験名:AfterOnePush_StackSizeShouldBeOne
Red
この試験を選んだ考え方も不明。仮説を示す。
・考え方1
核心機能の周辺的な確認を行う準備が整ったのでpush/popの周辺部分の試験を行う。 「値を格納/取り出す」はpush/popの核心部分なので後回し。 push/popしてサイズが変わるかを単数から確認する。
・考え方2
空ケースが完了したので次に原始的な単数ケースへ進む。 Stackのサイズを1増減させるにはpush/popが必要だ。 よってこの試験は核心機能(push, pop)の最も周辺部分の試験という意味合いもある。
pushできないとpopの試験ができないためpushの単数から着手する。
StackTest#AfterOnePush_StackSizeShouldBeOneを作成する Stack stack = new Stack()が重複しているがまだリファクタしない。 Greenが終了してからリファクタする。
@Test public void AfterOnePush_StackSizeShouldBeOne() throws Exception { Stack stack = new Stack() stack.push(1); assertEquals(1, stack.getSize()); }
Green
compile error
コンパイラを通すためにStack#pushを作成する。 実装は最小限に留めたいのでpushの中身は空。 実行してAssertion ErrorでFailすることを確認する。
public void push(int element) { }
assertion error
Stack#pushでsize++する。 実行してPassすることを確認する。
public void push(int element) { size++; }
Red
空ではないことも追加で確認する。 論理的には同じテストなのでSingle Assertion Ruleの違反ではない。
@Test public void AfterOnePush_StackSizeShouldBeOne() throws Exception { Stack stack = new Stack() stack.push(1); assertEquals(1, stack.getSize()); assertFalse(stack.isEmpty()); }
実行してassertion errorでFailすることを確認する。
Green
assertion error
sizeを1で初期化すると以前のテストがFailしてしまう。 リテラル/定数でPassさせることは限界。 試験の具象度が1段上がっているので、コード汎化度も上げて、計算で対応することを考える。
Stack#isEmptyを修正。 sizeが1でなければfalseを返すようにする。 戻り値はBooleanなのでif/elseや三項演算子ではなくシンプルにreturn + <評価式>とする
public boolean isEmpty() { return size == 0; }
emptyフィールドが不要になったので削除する。 不要になったものを削除することはリファクタリングではない。
実行してPassすることを確認する。 これでisEmptyが汎用的になった。
Blue
テストコードの重複をリファクタする。 stackをフィールドにしてsetUpメソッドで初期化する。 各テストメソッド内からオブジェクト作成コードを削除する。
@Before public void setUp() throws Exception { stack = new Stack(); }
実行してPassすることを確認する。
1回Popすると空になることを確認
試験名:afterOnePushAndOnePop_ShouldBeEmpty
Red
pushの単数ケースが完了した時点でコードは既に複数ケースに対応できる。 よってpushの複数ケースの試験はスキップする。 次はpopの単数に着手する。
StackTest#afterOnePushAndOnePop_ShouldBeEmptyを作成する。 確認はisEmtpyのみ。getSizeもisEmptyもsizeフィールドに依存しているので結果は同じになる。
@Test public void afterOnePushAndOnePop_ShouldBeEmpty() throws Exception { stack.push(1); stack.pop(); assertTrue(stack.isEmpty()); }
Green
compile error
コンパイラを通すためにStack#popを作成する。 実装は最小限に留めたいのでpopの中身は空。 popは最終的に整数(intかInteger)を返すはずだがこの時点では不要なので型はvoidにする。 実行してAssertion ErrorでFailすることを確認する。
assertion error
Stack#popでsizeを減算させる。 実行してPassすることを確認する。
public void pop() { size--; }
Uncle Bobが--sizeにしている。 これはStackの一番上のデータを返すpopの最終的な挙動を想像して、一番上のデータ位置を表すsize - 1にしたと思われる。
Overflowのエラー処理を確認する
試験名:WhenPushedPastLimit_StackOverflows
Blue
popのコードは既に複数ケースに対応できているので複数ケースの試験はスキップする。
周辺部分(サイズの増減と確認)は動いてきた。 当該周辺部分のエラー処理を試験する。 まずは上限超過の試験を行う。
pushのエラーはOverflowなのでOverflowの試験をする。 複数回、PushしてLimitを超過させたい。 そのためにはStackにLimitが必要。
Limitはインスタンス作成時に設定する。 コンストラクタに渡してもよいが、後々、Special Case Patternを使いたいのでmakeファクトリメソッドを使う。
まずはファクトリメソッドを実装してsetUpを置き換える。 コンストラクタはprivateにする。中身は空。
public static Stack make(int capacity) { return new Stack(2); } private Stack(int capacity) { }
Red
OverflowのExceptionクラスはStackクラスの内部クラスとする。 ExceptionのFQCNでなんのエラーかわかる。 実行してFailすることを確認する。
@Test(expected = Stack.Overflow.class) public void WhenPushedPastLimit_StackOverflows() throws Exception { stack.push(1); stack.push(1); stack.push(1); }
Green
compile error
ExceptionはRuntimeExceptionを継承する。 Checked Exceptionはreverse dependencyが発生するので使わない。 ある子クラスで新しいExceptionをthrowするとBaseクラスの変更が必要になり全ての子クラスが影響を受けてしまう。
public class Overflow extends RuntimeException{ }
assertion error
空、マジックナンバー、計算では対応できない。 Stack#pushでsizeとcapacityを比較を比較するしかない。 汎化度を上げてif文を使う。
capacityをフィールドにする。 Stack#pushでsizeとcapacityを比較して、Limit超過時にはExceptionを投げるようにする。 実行してPassすることを確認する。
private int capacity; private Stack(int capacity) { this.capacity = capacity; } public void push(int element) { if (size == capacity) throw new Overflow(); size++; }
Underflowのエラー処理を確認する
試験名:WhenEmptyStackIsPopped_ShouldThrowUnderflow
Red
同様に下限超過の試験を作成する。
pop1回でUnderflowが発生する。
@Test(expected = Stack.Underflow.class) public void WhenEmptyStackIsPopped_ShouldThrowUnderflow() throws Exception { stack.pop();
Green
compile error
Underflowクラスを同様に実装する。 実行してFailすることを確認する。
public class Underflow extends RuntimeException{ }
assertion error
空、マジックナンバー、計算では対応できない。 Stack#popでsizeを確認するように変更する。 実行してPassすることを確認する。
public void pop() { if (size == 0) throw new Underflow(); size--; }
pushしたものをpopできることを確認する
試験名:WhenOneIsPushed_OneIsPopped
Red
周辺部分の単数ケース、複数ケース、エラーが完了した。 Stackの核心部分(値を入れる、取り出す)を試験する準備が整った。 まずは単数ケースから始める。 pushとpopが互いの動作に依存するので一緒に試験する。 つまり1つpushしてpopする。
StackTest#WhenOneIsPushed_OneIsPoppedを作成する。
@Test public void WhenOneIsPushed_OneIsPopped() throws Exception { stack.push(1); assertEquals(1, stack.pop()); }
Green
compile error
コンパイラを通すためにStack#popをint型にし、return -1を返すようにする。 -1を返すのは意図的にAssertion ErrorでFailさせたいから。nullやfalseと同じ。 コンパイラエラーが消えたら実行してAssertion ErrorでFailすることを確認する。
public int pop() { if (size == 0) throw new Underflow(); size--; return -1; }
assertion error
Stack#popがint型のelementフィールドを返すようにする。 Stack#pushがint型のelementフィールドに引数elementを代入するようにする。 この時点では配列やCollectionはまだ使わない。 実行してPassすることを確認する。
private int element; public void push(int element) { if (size == capacity) throw new Overflow(); this.element = element; size++; } public int pop() { if (size == 0) throw new Underflow(); size--; return element; }
2つpushしたら2つpopできることを確認
試験名:WhenOneAndTwoArePushed_TwoAndOneArePopped
Red
単数ケースが成功したので複数ケースを試験する。
StackTest#WhenOneAndTwoArePushed_TwoAndOneArePoppedを作成する。 実行してFailすることを確認する。
@Test public void WhenOneAndTwoArePushed_TwoAndOneArePopped() throws Exception { stack.push(1); stack.push(2); assertEquals(2, stack.pop()); assertEquals(1, stack.pop()); }
Green
assertion error
変数でも分岐でも対応できないので配列を導入する。
elementをint型配列elementsに変える。 Stack#pushでelements[size]に格納するよう変更する。 Stack#popでelements[--size]を返すように変更する。 実行してPassすることを確認する。
private int elements[]; private Stack(int capacity) { this.capacity = capacity; elements = new int[capacity]; } public void push(int element) { if (size == capacity) throw new Overflow(); this.elements[size++] = element; } public int pop() { if (size == 0) throw new Underflow(); return elements[--size];
capacityが負の整数のときのエラー処理を確認する
試験名:WhenCreatingStackWithNegativeSize_ShouldThrowIllegalCapacity
Red
核心機能のエラーについて考える。 配列を導入したので負の整数をcapacityに指定されるとエラーが発生する可能性がある。 負の整数をcapacityに指定された場合のエラー処理の試験を行う。
実行してFailすることを確認する。
@Test(expected = Stack.IllegalCapacity.class) public void WhenCreatingStackWithNegativeSize_ShouldThrowIllegalCapacity() throws Exception { Stack.make(-1); }
Green
compile errorr
public class IllegalCapacity extends RuntimeException { }
assertion error
makeファクトリメソッドでcapacityのチェックをする。 コンストラクタでcapacityのチェックをすると無駄なcallが発生する。
makeファクトリメソッドはstaticなのでIllegalCapacityもstaticにする。 実行してPassすることを確認する。
public static Stack make(int capacity) { if (capacity < 0) throw new IllegalCapacity(); return new Stack(2); } public static class IllegalCapacity extends RuntimeException { }
Uncle Bobは中身が1行のif文には{}をつけない。 是非について賛否がわかれそうだが、これはコードの表示をコンパクトにしたいということとif文の中身を1行に収めることを推奨するためのPracticeと思われる。
capacityが0のときの挙動を確認する
試験名:WhenCreatingStackWithZeroCapacity_AnyPushShouldOverflow
Red
引き続きエラーについて考慮する。 capacity=0を指定された場合、どのように振る舞うべきか考える。 サイズ0のStackの作成は許容されるべきで、サイズ0のStackとして振る舞う。 ただしSpecial Case Patternで効率よく動作させたい。
StackTest#WhenCreatingStackWithZeroCapacity_AnyPushShouldOverflowを作成する。
@Test(expected = Stack.Overflow.class) public void WhenCreatingStackWithZeroCapacity_AnyPushShouldOverflow() throws Exception { stack = Stack.make(0); stack.push(1); }
Green
実行してPassすることを確認する。
Blue
// Special Case PatternでRefactorする
外部から見た振る舞いは正しい。 ただ0のスタックなのにリソースを確保したりしている点がおかしい。 Refactoringする。
StackからInterfaceをExtractする。makeメソッドは除く。 元のStackクラスはBoundedStackという名前にする。 @overrideアノテーションは使わない。 IDEだとエディタ画面左端のアイコンでOverrideされていることがわかるからだろう。 実行してPassすることを確認する。
package stack; public interface Stack { boolean isEmpty(); int getSize(); void push(int element); int pop(); public static class IllegalCapacity extends RuntimeException { } }
BoudedStack#makeで中で、capacity=0のときStackから無名クラスを作成する。 コンパイラを通すためにOverflowとUnderflowをstaticにする。
public static Stack make(int capacity) { if (capacity < 0) throw new IllegalCapacity(); if (capacity == 0) return new Stack() { public boolean isEmpty() { return true; } public int getSize() { return 0; } public void push(int element) { throw new Overflow(); } public int pop() { throw new Underflow(); } }; return new BoundedStack(capacity); }
無名クラスをMoveしてprivate innter static classにする。 実行してPassすることを確認する。
public static Stack make(int capacity) { if (capacity < 0) throw new IllegalCapacity(); if (capacity == 0) return new ZeroCapacityStack(); return new BoundedStack(capacity); } private static class ZeroCapacityStack implements Stack { public boolean isEmpty() { return true; } public int getSize() { return 0; } public void push(int element) { throw new Overflow(); } public int pop() { throw new Underflow(); } }
これがSpecial Case Pattern。このためにファクトリメソッドを作成した。
1回pushしてtopできることを確認する
試験名:whenOneIsPushed_OneIsOnTop
Red
核心機能が完了した。 次は核心機能がないと試験できなかった機能を試験する。 topとfindならtopの方が周辺的なのでtopから試験する。 空ケースはエラーが発生するので後に回す。 まずは単数ケースの試験を行う。
StackTest@whenOneIsPushed_OneIsOnTopを作成する。
@Test public void whenOneIsPushed_OneIsOnTop() throws Exception { stack.push(1); assertEquals(1, stack.top()); }
Green
compile & assertion error
topをStack Interface、BoundedStack、ZeroCapacityStackに実装する。 ZeroCapacityStackでは-1を返す。最終的になにを返すかは今後、空でtopしたときのエラー試験で考える。 実行してPassすることを確認する。
public int top() { return elements[size-1]; }
ここではcompile errorとassertion erorの両方を一度に解決している。 実装がシンプルな場合はわざとassertion errorを起こすことはしないようだ。
空のStackでtopするときのエラーの確認
試験名:WhenStackIsEmpty_TopThrowsEmpty
Red
複数ケースに対応できるコードができたので複数ケースの試験はスキップしてエラーについて考慮する。 空のStackをtopしたケースを試験する。 加えて言うとZeroCapcityStack#topの挙動を決めるために空Stackをtopしたときの動作について考える必要がある。
StackTest#WhenStackIsEmpty_TopThrowsEmptyを作成する。
@Test(expected = Stack.Empty.class) public void WhenStackIsEmpty_TopThrowsEmpty() throws Exception { stack.top(); }
Green
compile error
Stack InterfaceにEmptyクラスを作成する。staticにする。 実行してFailすることを確認する。
public static class Empty extends RuntimeException { }
assertion error
BoundedStack#topで空ならEmptyをThrowさせる BoundedStack#popのif文の判定もisEmptyにRefactorしたいがPassするまで後回しにする。 実行してPassすることを確認する。
public int top() { if (isEmpty()) throw new Empty(); return elements[size-1]; }
Blue
BoundedStack#popのif文の判定もisEmptyに変える
public int pop() { if (isEmpty()) throw new Underflow(); return elements[--size]; }
ZeroCapcityStack#topでもEmptyを投げる これはRefactorではないのでRed/Gree/BlueのRuleに違反していると思う。 実装を忘れないように?ZeroCapcityStackは核心機能ではないから?
public int top() { throw new Empty(); }
ZeroCapacityStackの試験をする。
試験名:WithZeroCapcityStack_TopThrowsEmpty
Red & Green
試験が後追いになっているがStackTest#WithZeroCapcityStack_TopThrowsEmptyを作成する。 実行してPassすることを確認する。
@Test(expected = Stack.Empty.class) public void WithZeroCapcityStack_TopThrowsEmpty() throws Exception { stack = BoundedStack.make(0); stack.top(); }
Blue
// ExceptionをStack Interfaceへ移動する
BoundedStackのOverflowとUnderflowをPull members upでInterfaceへ移動する Uncle Bobはこの後、なぜか試験をしていない。
findの動作を確認する
試験名:GivenStackWithOneTwoPushed_FindOneAndTwo
Red
topが完成したのでfindメソッドを試験する。
findメソッドはtopからのindexを返す。 StackTest#GivenStackWithOneTwoPushed_FindOneAndTwoを作成する。 まずは2つ数字を入れて最初の数字のindexが1になることを確認する。 degenerateという意味では1つだけ数字を入れるのでは? 配列にした時点で1つにこだわる必要はないのか? 一緒に2つ数字を入れて2番めの数字のindexが0になることを確認する。 Logicalには1つの試験なので複数Assertを入れてる Act/Assert/Act/AssertではないのでSingle Assertion Ruleの違反ではない。
@Test public void GivenStackWithOneTwoPushed_FindOneAndTwo() throws Exception { stack.push(1); stack.push(2); assertEquals(1, stack.find(1)); assertEquals(0, stack.find(2)); }
Green
compile error
findをStack Interfaceで定義する。 実行してcompile errorでFailすることを確認する。
int find(int element);
BoundedStack#findを実装する。 compile errorを解消してassertion errorを出すことが目的なので-1を返す。 実行してcompile errorでFailすることを確認する。
public int find(int element) { return -1; }
ZeroCapacityStack#findを実装する。 実行してFailすることを確認する。 今度はAssertion Errorで失敗する。
public int find(int element) { return -1; }
assertion error
BoundedStack#findを実装する。 見つからなかったときのreturn -1は仮。最終的な挙動は今後、見つからなかったときの試験で検討、実装する。 実行してPassすることを確認する。
public int find(int element) { for (int i = size-1; i >= 0; i--) if (elements[i] == element) return (size - 1) - i; return -1; }
findで見つからなかったときの挙動を確認
試験名:GivenStackWithNo2_Find2ShouldReturnNull
Red
findで見つかったときの挙動は試験できた。 次は見つからなかったときの挙動を試験する 見つからないときはNullを返す
実行するとコンパイルエラーが発生する。
@Test public void GivenStackWithNo2_Find2ShouldReturnNull() throws Exception { assertNull(stack.find(2)); }
Green
compile error + assertion error
BoundedStack#findとZeroCapacityStack#findのSignitureをIntegerに変え、nullを返す。 Stack#findのSignitureもIntegerに変える。
public Integer find(int element) { for (int i = size-1; i >= 0; i--) if (elements[i] == element) return (size - 1) - i; return null; }
実行するとGivenStackWithOneTwoPushed_FindOneAndTwoでコンパイルエラーが発生する。 GivenStackWithOneTwoPushed_FindOneAndTwoを変更してPassすることを確認する。
@Test public void GivenStackWithOneTwoPushed_FindOneAndTwo() throws Exception { stack.push(1); stack.push(2); assertEquals(new Integer(1), stack.find(1)); assertEquals(new Integer(0), stack.find(2)); }
Blue
GivenStackWithOneTwoPushed_FindOneAndTwoがあまり見やすくない。 assertの中がごちゃごちゃしていて読みにくい。 Refactorする。
@Test public void GivenStackWithOneTwoPushed_FindOneAndTwo() throws Exception { stack.push(1); stack.push(2); assertEquals(new Integer(1), stack.find(1)); assertEquals(new Integer(0), stack.find(2)); }
Passすることを確認する。 まだ見にくい。
@Test public void GivenStackWithOneTwoPushed_FindOneAndTwo() throws Exception { stack.push(1); stack.push(2); assertEquals(1, stack.find(1).intValue()); assertEquals(0, stack.find(2).intValue()); }
Passすることを確認する。 まだ見にくい。
Inlineだと汚いのでやめる。 Refactor -> Introduce Variableで外に出す。 Shift + Cmd + ↑でStatementの順序を揃える。 Passすることを確認する。
@Test public void GivenStackWithOneTwoPushed_FindOneAndTwo() throws Exception { stack.push(1); stack.push(2); int oneIndex = stack.find(1); int twoIndex = stack.find(2); assertEquals(1, oneIndex); assertEquals(0, twoIndex); }