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)             

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.

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());   } }

BoundBox Design

Made with Slides.com