BoundBox
Breaking encapsulation to ease testing.
With Android in mind.
public class MainActivity { private Button buttonMain; ... }
bbOfMainActivity._getButtonMain().performClick();
BoundBox your code
Simply type this annotation inside your IDE (in a test).
The annotation processor will create, in real time, a BoundBox class. Directly use the BoundBox class to access fields and methods of a bound object.
boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
@BoundBox(boundClass = MainActivity.class)
boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity()); boundBoxOfMainActivity.boundBox_getButtonMain().performClick(); assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
@BoundBox(boundClass = MainActivity.class)
BoundBox customization
--> Creates a simple BoundBox. Access all fields/methods of MainActivity and its super classes.
@BoundBox(boundClass=MyActivity.class, maxSuperClass=Activity.class)
--> Creates a BoundBox that exposes all inner fields/methods of MyActivity and its super classes up to Activity (excluded) .
@BoundBox( boundClass = A.class, extraFields= {@BoundBoxField(fieldName="foo", fieldClass=String.class)})
--> Creates a BoundBox with additionnal data members (if you use stub jars, like on Android).
@BoundBox(boundClass=A.class,boundBoxPackage="foo",prefixes={"BB","bb"})
--> Creates a BoundBox with custom BoundBox class name, package name and prefix methods.
@BoundBox(boundClass = MainActivity.class)
Benefits of BoundBox
Don't change a single line of code
in your app under tests.
Objects under tests will be accessed using reflection,
and this access will be checked at compile time
(unlike using pure reflection or WhiteBox from PowerMock).
Fields, constructors and methods, even those defined
in super classes are accessible.
e.g : access
foo.super.super.a
(not possible in Java).
BoundBox quality
-
Open Source, on github
-
Heavily tested (over 120 tests)
-
95 % code coverage
-
continuous integration on travis CI
-
generation of javadoc for BoundBox classes
-
Pretty well documented
BoundBox is production-ready.
BoundBox Disclaimer
Do not do this at home !
BoundBox is designed to solve corner/edge cases, exactly like WhiteBox.
BoundBox is designed for testing.
Do not build your app without proper encapsulation,
that would be a terrible failure.
But, some situations make BoundBox interesting...
Android
UI is hard to test :
- Robotium : find views by labels.
- Robolectric/Test kit : find views by ids or labels.
-
UIAutomator : find views by content-desc.
- BoundBox : find views by fields, invoke methods or inner classes directly. UI becomes accessible programmatically.
- BoundBox is as fast as espresso (i.e. super fast).
Algorithmically complex code
is hard to test.
Don't expose sub methods just for testing them.
Do not pollute your API.
Do not pollute your API.
Fine grain unit test all parts of your algorithms.
Apply good OO design and hide your inner structures, but test them as much as you want.
Legacy code
is horrible to test
Don't change that code that is supposed to work.
Test it as much as you want.
If you want to refactor, you now got tests you can rely on.
A simple Android Example
public class MainActivity extends Activity {
private Button buttonMain; private TextView textViewMain;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); buttonMain = (Button) findViewById(R.id.button_main); textViewMain = (TextView) findViewById(R.id.textview_main);
buttonMain.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { final int result = 42; textViewMain.setText(String.valueOf(result)); } });
} }
Access it via BoundBox
@BoundBox(boundClass = MainActivity.class, maxSuperClass = Activity.class) public class MainTest extends ActivityInstrumentationTestCase2<Mainactivity> { BoundBoxOfMainActivity bBoxOfMainActivity; public MainTest() { super(MainActivity.class); } @UiThreadTest public void testCompute() { // given bBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity()); // when bBoxOfMainActivity.boundBox_getButtonMain().performClick();
// then assertEquals("42", bBoxOfMainActivity.boundBox_getTextViewMain().getText()); } }