- Glossary
- Source Code
- UML Class Diagram
- Adapter Pattern
- TDD Algorithm
- Characterization Test Algorithm
- Java Stack Iterator Question
- Git Bisect
- Parameterize Constructor
- Flaky Test
- Construction Blob
- Supersede Instance Variable
- Irritating Global Dependency
- Singleton pattern
- Reset singleton instance for testing
- Dependency Injection
- Feature Toggles
- Java Reflection
- Proxy
- Guice Framework
- characterization test is a test written to document the current behavior of a piece of code. The tests document the actual behavior of the system.
- fake object is an object that impersonates a collaborator of a class during testing.
- flaky test is a non-deterministic test.
- hidden dependency: is a testing smell where the constructor in the class under test uses some resources that we can't access in our test harness.
- inversion of control: use inversion of control to allow the framework to specify the dependencies
- legacy code is simply code without tests.
- mock object is a fake object that asserts conditions internally.
- mockito mocks objects using: reflection and a proxy object.
- reflection is a language's ability to inspect and dynamically call classes, methods, attributes, etc. at runtime.
- sensing: we break dependencies to sense when we can't access values our code computes.
- separation: we break dependencies to separate when we can't even get a piece of code into a test harness to run.
- test harness is a piece of software that enables unit testing.
- test-driven development is a development process that consists of writing failing test cases and satisfying them one at a time. As you do this, you refactor to keep the code as simple as possible. Code developed using TDD has test coverage, by default.
- unit test is a test that runs in less than 1/10th of a second and is small enough to help you localize problems when it fails.
src | Main | Test |
---|---|---|
TDD | link | link |
Characterization test | link | link |
Dependency breaking | link | link |
Mockito | --- | link |
Proxy | link | link |
Singleton | link | --- |
Data Migration | link | link |
- Write a failing test case.
- Get it to compile.
- Make it pass.
- Remove duplication.
- Repeat.
You want to see the code behavior, so you make a failing test which tells you the correct behavior.
- Use a piece of code in a test harness.
- Write an assertion that you know will fail.
- Let the failure tell you what the behavior is.
- Change the test so that it expects the behavior that the code produces.
- Repeat.
Step by step sample
- Get the code in a test harness.
java.util.Stack;
- Write an assertion that you know will fail.
push(1);
push(2);
assertEquals(-1, pop());
- Let the failure tell you what the behavior is.
prints 2
- Change the test so that it expects the behavior that the code produces.
assertEquals(2, pop());
- Repeat
Recall that the iterator for the Stack class for Java is incorrect. Write a unit test that would characterize the actual behavior of the Java Stack's iterator. See test for full details.
@Test
public void testStack() {
Stack<Integer> stack = new Stack<Integer>();
stack.push(1);
stack.push(2);
int expectedVal = 1;
for (Integer stackElem : stack) {
// prints like a queue: 1, 2 (iterator of the stack behaves like a queue)
log.info("stack elem {}", stackElem);
assertEquals(expectedVal, stackElem.intValue());
expectedVal++;
}
// stack is not empty because we didn't pop
assertFalse(stack.isEmpty());
}
// [main] INFO characterization.test.TestStack - stack [1, 2]
// [main] INFO characterization.test.TestStack - peek stack 2
// [main] INFO characterization.test.TestStack - stack elem 1
// [main] INFO characterization.test.TestStack - stack elem 2
Stack<Integer> mockStack = mock(Stack.class);
when(mockStack.pop()).thenReturn(3, 2, 1);
mockStack.pop();
mockStack.pop();
mockStack.pop();
mockStack.pop();
The idea behind git bisect
is to perform a binary search in the history to find a particular regression.
You could try to check out each commit, build it, check if the regression is present or not. If there is a large number of commits, this can take a long time. This is a linear search. We can do better by doing a binary search. This is what the git bisect
command does. At each step it tries to reduce the number of revisions that are potentially bad by half.
The goal is find the first commit that fails a test and this is the procedure for finding that commit:
- split the suspect commits in half
- if test fails, then bug is in the left half
- if test passes, then bug is in the right half
- repeat
Example 1
Git bisect on the following sequence of commits. You get fail/bad
,
pass/good
, fail/bad
, which is the culprit of commit?
- 1----2----3----4----[5]----6----7----8----9
- fail: 1----2----[3]----4----5
- pass: 4----5
- fail: 4
Answer: 4
Solves the problem of hidden dependencies in constructor, so we can inject dependencies and be able to test. If you are creating an object in a constructor, often the easiest way to replace it is to externalize its creation, create the object outside the class, and make clients pass it into the constructor as a parameter. Here are some example.
We start with this:
public class MailChecker {
public MailChecker(int checkPeriodSeconds) {
this.receiver = new MailReceiver();
this.checkPeriodSeconds = checkPeriodSecond;
}
...
}
Then we make a copy of the constructor, add parameter MailReceiver receiver
, and assign that parameter to the instance variable getting rid of the new
expression:
public class MailChecker {
public MailChecker(int checkPeriodSeconds) {
this.receiver = new MailReceiver();
this.checkPeriodSeconds = checkPeriodSecond;
}
public MailChecker(MailReceiver receiver, int checkPeriodSeconds) {
this.receiver = receiver;
this.checkPeriodSeconds = checkPeriodSeconds;
}
...
}
Lastly, we go back to the original constructor and remove its body, replacing it with a call to new constructor. The original constructor uses new
to create the parameter it needs to pass.
public class MailChecker {
public MailChecker(int checkPeriodSeconds) {
this(new MailReceiver(), checkPeriodSeconds);
}
public MailChecker(MailReceiver receiver, int checkPeriodSeconds) {
this.receiver = receiver;
this.checkPeriodSeconds = checkPeriodSeconds;
}
...
}
Steps To Parameterize Constructor, follow these steps:
- Identify the constructor that you want to parameterize and make a copy of it.
- Add a parameter to the constructor for the object whose creation you are going to replace. Remove the object creation and add an assignment from the parameter instance variable for the object.
- If you can call a constructor from a constructor in your language, remove the body of the old constructor and replace it with a call to the old constructor. Add a new expression to the call of the new constructor in the old constructor. If you can't call a constructor from another constructor in your language, you may have to extract any duplication among the constructors to a new method.
Consider the following constructor:
public Pixel {
public Pixel(Color color) {
this.color = color;
this.position = new Position(23, 4, 52);
}
...
}
- Parameterize the constructor
- Make the change behavior preserving
- Mock out the position and color
public Pixel {
public Pixel(Color color) {
this(new Position(23, 4, 52), color);
}
public Pixel(Position position, Color color) {
this.color = color;
this.position = position;
}
...
}
For number three:
@Test
public void test() {
Position position = mock(Position.class);
Color color = mock(Color.class);
Pixel pixel = new Pixel(position, color);
}
A flaky test is a non-deterministic test.
- Test should fail when there is a problem and pass when there is no problem
- Flaky test can pass and fail on the same build!
- Flaky tests fail even when there is no problem. This destroys developer confidence in tests.
Sources of non-determinism
- Inherent non-determinism
- noisy or complex test environment
- asynchronous calls
- Accidental non-determinism: old and out-of-date tests introduce flakiness
The case of construction blob
- The constructor creates many objects.
- We could parameterize constructors but the parameter list would become massive.
- We can supersede instance variable.
Is a solution for construction blob. To Supersede Instance Variable, follow these steps:
- Identify the instance variable that you want to supersede.
- Create a method named
supersedeXXX
, whereXXX
is the named of the variable you want to supersede. - In the method, write whatever code you need to, so that you destroy the previous instance of the variable and set it to the new value. If the variable is a reference, verify that there are not any other references in the class to the object it points to. If there are you might have an additional work to do in superseding method to make sure that replacing the object is safe and has the right effect.
public class Sale {
private Display display;
private Storage storage;
private Interact interac;
public Sale(Display display, Storage storage) {
this(display, storage, new Interac(12));
}
public Sale(Display display, Storage storage, Interac interac) {
this.display = display;
this.storage = storage;
this.interac = interac;
}
public void supersedeInteract(Interac interac) {
this.interac = interac;
}
}
Now the test:
@Test
public void testSupersedeInterac() {
Display mockDisplay = mock(Display.class);
Storage mockStorage = mock(Storage.class);
Interac mockInterac = mock(Interac.class);
// call to the original constructor (the one without the superseded variable)
// and pass in the mocked classes
Sale sale = new Sale(mockDisplay, mockStorage);
// supersede interac
sale.supersedeInterac(mockInterac);
}
class Pane {
private FocusWidget cursor;
public Pane(WashBrush brush, Pattern backdrop) {
...
this.cursor = new FocusWidget(brush, backdrop);
...
}
// superseding the instance variable cursor
public void supersedeCursor(FocusWidget newCursor) {
cursor = newCursor;
}
}
Now the test:
@Test
public void testSupersedeCursor() {
WashBrush mockBrush = mock(WashBrush.class);
Pattern mockPattern = mock(Pattern.class);
Pane pane = new Pane(mockBrush, mockPattern);
FocusWidget cursor = mock(FocusWidget.class);
pane.supersedeCursor(cursor);
// or with fakes
FakeFocusWidget cursor2 = new FakeFocusWidget();
pane.supersedeCursor(cursor2);
}
- In Java, the singleton pattern is one of the mechanisms people use to make global variables.
- The whole idea of the singleton pattern is to make it impossible to create more than one instance of a singleton in an application.
- That might be fine in production code, but, it is particularly hard to fake and when testing, each test in a suite of tests should be a mini-application, in a way: It should be totally isolated from the other tests.
So, to run code containing singletons in a test harness, we have to relax the singleton property. Here's how we can do it. The first step is to add a new static method to the singleton class. This method allows us to replace the static instance in the singleton. We'll call it setTestingInstance
.
Applying the first step, the singleton class PermitRepository
becomes:
public class PermitRepository {
private static PermitRepository instance = null;
private PermitRepository() {}
public static PermitRepository getInstance() {
if (instance == null) {
instance = new PermitRepository();
}
return instance;
}
// irritating global dependency solution 1: static instance setter
public static void setTestingInstance(PermitRepository newInstance) {
instance = newInstance;
}
// ...
}
Now, we'd like to write code like this in our test setup.
@Before
public void setUp() {
PermitRepository repository = null;
PermitRepository.setTestingInstance(repository);
...
}
Introducing static setter is not the only way of handling this situation. Another approach is to add a resetForTesting()
method to the singleton that looks like this:
public class PermitRepository {
private static PermitRepository instance = null;
// irritating global dependency solution 2: reset the singleton
public static void resetForTesting() {
instance = null;
}
// ...
}
Dependency injection is basically providing the objects that an object needs (its dependencies) instead of having it construct them itself. It's a very useful technique for testing, since it allows dependencies to be mocked or stubbed out.
- means giving an object its instance variables
- all parameters to be passed in through the constructor
- refactor using parameterize constructor
- stackoverflow
Without dependency injection
class Car {
private Wheel wh;
private Battery bt;
Car() {
// dependency is hidden in this constructor
// can't test
// can't inject dependencies or mocks
// violates the law of demeter by creating an instance of objects in the constructor
// solutions: parameterize the constructor or use Guice framework
wh = new NepaliRubberWheel();
bt = new ExcideBattery();
}
// ... more code
}
After using dependency injection
Here, we are injecting the dependencies (Wheel and Battery) at runtime. Hence the term : Dependency Injection.
class Car {
private Wheel wh; // [Inject an Instance of Wheel (dependency of car) at runtime]
private Battery bt; // [Inject an Instance of Battery (dependency of car) at runtime]
Car(Wheel wh, Battery bt) {
this.wh = wh;
this.bt = bt;
}
// Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
Sale example
class Sale {
@Inject
HashStorage lookup;
@Inject
Display display;
@Inject
Interac interac;
@Inject
Sale(Display d, HashStorage h, Interac i) {
this.display = d;
this.lookup = h;
this.interac = i;
}
//...
}
Sale injector module
public class SaleInjectorModule extends AbstractModule {
@Override
protected void configure() {
bind(Display.class).to(Display.class);
// you can also use toInstance(instance);
bind(Interac.clss).toInstance(new Interact(12));
HashStorage hashStorage = new HashStorage();
hashStorage.put("1B", "Chocolate");
bind(HashStorage.class).toInstance(hashStorage);
}
}
In a main method or a test method.
class TestSaleInjector {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new SaleInjectorModule());
Sale sale = injector.getInstance(Sale.class);
sale.scan("1B");
// notice that there's no need for a constructor and mocks anymore
// usually you would have Display, HashStorage, and Interac instances
// but now SaleInjectorModule handles that
}
}
Class under test
class BillingService {
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
@Inject
BillingService(CreditCardProcessor processor, TransactionLog log) {
this.processor = processor;
this.transactionLog = log;
}
// ... more code
}
Guice uses bindings to map types to their implementations. A module is a collection of bindings specified using fluent, English-like method calls:
class BillingModule extends AbstractModule {
@Override
protected void configure() {
/*
* This tells Guice that whenever it sees a dependency on a TransactionLog,
* it should satisfy the dependency using a DatabaseTransactionLog.
*/
bind(TransactionLog.class).to(DatabaseTransactionLog.class);
/*
* Similarly, this binding tells Guice that when CreditCardProcessor is used in
* a dependency, that should be satisfied with a PaypalCreditCardProcessor.
*/
bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
}
}
Testing
public class TestBillingService {
@Test
public void testBilling() {
/*
* 3. Create an injector instance and initialize it to Guice.createInjector()
* which takes your Modules, and returns a new Injector instance.
* Most applications will call this method exactly once, in their main() method.
*/
Injector injector = Guice.createInjector(new BillingModule());
// 4. Calling the class under test
BillingService billingService = injector.getInstance(BillingService.class);
}
}
Review
- Use inject annotation
@Inject
in class under test - Create a module class and override the configure method
- Inside the configure method, use bindings to bind the parameters of constructors
- Create an
Injector
instance and initialize it withGuice.createInjector(moduleName)
- You can now create a new instance of the class under test by initializing it as
injector.getInstance(ClassUnderTest.class)
Is a language's ability to inspect and dynamically call classes, methods, attributes, etc. at runtime.
// Show the methods of BillingService class
Method[] methods = BillingService.class.getMethods();
for (Method method: methods) {
System.out.println('billing service methods :' + method.getName());
}
You create a dynamic proxies using Proxy.newProxyInstance(...)
method. The newProxyInstance() methods takes 3 parameters:
- The ClassLoader that is to "load" the dynamic proxy class.
MyInterface.class.getClassLoader()
- An array of interfaces to implement.
new Class[] {MyInterface.class}
- An InvocationHandler to forward all methods calls on the proxy to.
myHandler
For example,
public class TestProxy {
@Test
void testProxy() {
MockInvocationHandler mockHandler = new MockInvocationHandler();
IDisplay display = (IDisplay) Proxy.newProxyInstance(
IDisplay.class.getClassLoader(),
new Class[] {IDisplay.class},
mockHandler
);
}
}
See the test here
There are three types of toggles: development, long-term business, and release toggles. Although release toggles are shorter lived than the other types of toggles, 53% still exist after 10 releases indicating that many linger as technical debt.
Therefore, many major companies doing rapid releases prefer to work from a single master branch (trunk) in their version control system and use feature toggles instead of feature branches to isolate feature development.
In particular, feature toggles provide the flexibility to gradually roll out features and do user experiments (A/B testing) on new features. For example, “Every day, we [Facebook] run hundreds of tests on Facebook, most of which are rolled out to a random sample of people to test their impact. If a new feature does not work well, it is toggled off. The ability to flexibly enable and disable feature sets to specific groups of users to determine their effectiveness early on, reduces the investment in features that are not profitable.
For example, if a developer working on a given feature in a dedicated branch gets a request to fix an urgent bug, she needs to switch to another branch, fix the bug and test it, then switch back to the original branch to continue feature development. Developers often mistake the branch they are in, leading to commits to the wrong branch. According to Ho, toggling requires less effort than switching branches, hence it reduces the potential of unwanted check-ins and accompanying broken builds.
For example, the on-call developers at Facebook responsible for monitoring how new features behave in production (DevOps) need to be able to disable malfunctioning features within seconds to avoid affecting millions of users.