Determining which method triggered an aspect - java

I was wondering if there is anyway of determining what method was active when this aspect was triggered. I found the method JointPoint.getSourceLocation() which returns the source code line. I realised I could try to parse that source file and try to determine the method from that... but there seems like there ought to be a better way.
Basically, if has the following code:
class Monkey{
public void feed( Banana b ){
b.eat()
}
}
class Banana{
private static int bananaIdGen;
public final int bananaId = ++bananaIdGen;
private boolean eaten = false;
public void eat(){
if( eaten )
throw IllegalStateException( "Already eaten." );
else
eaten = true;
}
}
I'd like to have an aspect like
#After("call(void Banana.eat()) && target(bbb)")
public void whereEaten( Banana bbb ){
....
}
Where in the body I could print out something like `"Banana 47 eaten by org.example.Monkey(org.example.Banana)".
The reason is that that I would like to throw an error if a method has been called by a method without a certain annotation on it. For that I'd need to have the Method of the method.

I suppose this question with its thisEnclosingJoinPointStaticPart can help you.
But the best way to solve your problem is to construct the right join point using withincode and call pointcuts as in example here (see Contract Enforcement section). This way you prevent calls from methods with or without certain annotation.
The list of pointcuts is available here.
What about your sample code lets introduce annotation:
package com.riapriority.test;
public #interface CanEat {
}
Our Banana class:
package com.riapriority.test;
public class Banana {
private static int bananaIdGen;
private final int bananaId = ++bananaIdGen;
private boolean eaten = false;
public void eat() {
if (eaten)
throw new IllegalStateException("Already eaten.");
else
eaten = true;
}
public int getBananaId() {
return bananaId;
}
}
Our Monkey class with corresponding annotation:
package com.riapriority.test;
public class Monkey {
#CanEat
public void feed(Banana b) {
b.eat();
}
}
Our Airplane class which, of course, can't eat and so hasn't #CanEat annotation:
package com.riapriority.test;
public class Airplane {
public void feed(Banana b) {
b.eat();
}
}
Our simple main class for testing:
package com.riapriority.test;
public class WithincodeTest {
public static void main(String[] args) {
Banana monkeyBanana = new Banana();
Monkey monkey = new Monkey();
monkey.feed(monkeyBanana);
try {
monkey.feed(monkeyBanana);
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
Banana airplaneBanana = new Banana();
Airplane airplane = new Airplane();
try {
airplane.feed(airplaneBanana);
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
}
}
So we need to avoid eating bananas by Airplane. And the corresponding aspect to obtain this:
package com.riapriority.test;
import java.text.MessageFormat;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class EatingAspect {
#Pointcut("call(void Banana.eat()) && target(banana)")
public void eatCall(Banana banana) {
}
#Pointcut("#withincode(CanEat)")
public void canEat() {
}
#AfterReturning("eatCall(banana) && canEat()")
public void whereEaten(Banana banana,
JoinPoint.EnclosingStaticPart thisEnclosingStaticPart) {
System.out.println(MessageFormat.format("Banana {0} eaten by {1}", banana.getBananaId(),
thisEnclosingStaticPart.getSignature()));
}
#Before("eatCall(banana) && !canEat()")
public void forbidEating(Banana banana,
JoinPoint.EnclosingStaticPart thisEnclosingStaticPart) {
throw new IllegalStateException(MessageFormat.format("Can''t eat {0} by {1}", banana.getBananaId(),
thisEnclosingStaticPart.getSignature()));
}
}
So now our test main class produces the right output:
Banana 1 eaten by void com.riapriority.test.Monkey.feed(Banana)
Already eaten.
Can't eat 2 by void com.riapriority.test.Airplane.feed(Banana)
Hope this solves your problem.

Related

public void method throws Exception if - JUNIT test: Exception should not be thrown

I have a certain public void method which throws an Exception if a condition is fulfilled.
In my case the method looks like this:
public void toBeTestedMethod(Testobject testObject) throws CertainException {
if (testObject.getStatus().getAllowsEdit()){
throw ...}
}
getStatus() is a method which returns a certain Status and getAllowsEdit() is a method which returns a boolean value and nullable = true. For the two methods there also exist set-methods.
Edit1: The test regarding this method when it fails is already running fine:
public void testToBeTestedMethod_FailureStatus() throws Exception {
try {
TestObject testObject = _testObjectMockDAO.getNewTestObject();
_testObjectMockDAO.setTestObject(testObject);
_testObjectBusinessImpl.toBeTestedMethod(testObject);
fail("Check failed");
} catch (CertainException ex) {
assertEquals(ErrorCode.WRONG_STATUS, ex.getErrorCode());
}
}
I would now like to test the method toBeTestedMethod. The target is that the method does not throw an exception but gets executed successfully.
That means I would like to write a JUNIT-test which tests the following:
public void testToBeTestedMethod_success throws Exception{
// Enter test code here
}
Edit2 (regarding the class Status):
public class Status {
...
private String _status;
public String getStatus() {
return _status;
}
}
In my opinion, I have to modify the condition in the if-statement in order to get the expected result, correct?
Note: I did not write the method and the other code. Nevertheless, my task is to test the code via JUNIT.
I tried some code, but everytime I get the error that the Excpetion was thrown.
Even if you cannot solve this problem, I would be glad to get some hints where I should look for the problem why my test does not do what I want the test to do.
Your question is very abstract and needs more data, I am posting an answer here based on what I have understood.
Here are the classes:
public class SampleTestService {
public boolean toBeTestedMethod(TestObject testObject) throws AccessViolationException {
if (testObject.getStatus().getAllowsEdit()) {
throw new AccessViolationException("Edit is allowed for this non confirmed user");
} else {
return true;
}
}
static class TestObject {
private SomeStatus someStatus;
public SomeStatus getStatus() {
return someStatus;
}
}
static class SomeStatus {
private boolean allowsEdit;
public boolean getAllowsEdit() {
return allowsEdit;
}
}
static class AccessViolationException extends RuntimeException {
public AccessViolationException(String message) {
super(message);
}
}
}
Since the method depends on another class and that class-dependent also on another class you need to mock them in the chain. Here is how I have done it:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
#ExtendWith(SpringExtension.class)
class SampleTestServiceTest {
private final SampleTestService.TestObject mockTestObject = mock(SampleTestService.TestObject.class);
private final SampleTestService.SomeStatus mockSomeStatus = mock(SampleTestService.SomeStatus.class);
private final SampleTestService service = new SampleTestService();
#Test
void testThatMethodDoesNotThrowsException() {
when(mockTestObject.getStatus()).thenReturn(mockSomeStatus);
when(mockSomeStatus.getAllowsEdit()).thenReturn(false);
boolean result = service.toBeTestedMethod(mockTestObject);
Assertions.assertTrue(result);
}
#Test
void testThatMethodThrowsException() {
when(mockTestObject.getStatus()).thenReturn(mockSomeStatus);
when(mockSomeStatus.getAllowsEdit()).thenReturn(true);
Assertions.assertThrows(SampleTestService.AccessViolationException.class, () -> {
service.toBeTestedMethod(mockTestObject);
});
}
}

AspectJ pointcut to intercept a class

Guys I need a point cut that filter me the called function by a specific class.
public aspect intLogin {
private capture c = new capture();
pointcut login() : execution(public * login(..))
before ():login(){
c.print();
}
}
This is my aspect, I want to know which class call login function. Can you help me?
Helper class called by all the others:
package de.scrum_master.app;
public class Other {
public void doOther() {}
}
Driver application with all kinds of inner classes:
Here we have
non-static inner class,
static inner class,
local inner class,
anonymous class
and of course the normal class.
package de.scrum_master.app;
public class Application {
public String foo(int number) {
new Other().doOther();
return "foo";
}
public class MyInner {
public void doSomething() {
new Other().doOther();
}
}
public static class MyStaticInner {
public void doSomethingElse() {
new Other().doOther();
}
}
public static void main(String[] args) {
new Application().foo(11);
new Application().new MyInner().doSomething();
new Application.MyStaticInner().doSomethingElse();
class LocalInner {
public void doWhatever() {
new Other().doOther();
}
}
new LocalInner().doWhatever();
new Runnable() {
#Override public void run() {
new Other().doOther();
}
}.run();
}
}
Aspect logging caller class names:
package de.scrum_master.aspect;
public aspect CallingClassLogger {
before(Object caller) : !within(CallingClassLogger) && call(* *(..)) && this(caller) {
System.out.println(caller.getClass().getName());
}
}
Console log:
de.scrum_master.app.Application
de.scrum_master.app.Application$MyInner
de.scrum_master.app.Application$MyStaticInner
de.scrum_master.app.Application$1LocalInner
de.scrum_master.app.Application$1
Your aspect would have printed something like
Application.java:5
Application.java:11
Application.java:17
Application.java:28
Application.java:35
which is not so helpful IMO if you are interested in class names.
I solved the problem using thisJoinPoint.getSourceLocation().
The code is:
public aspect intLogin {
private capture c = new capture();
pointcut login(Object a) : call(public * login(..)) && (target(a)) && this(capture);
before (Object x):login( spring.aop.so_52992365.intLogin) {
String xString = x.toString();
System.out.println("The class that is calling the function is:" + thisJoinPoint.getSourceLocation());
c.print();
}
}

Combining multiple pointcuts AspectJ returns adviceDidNotMatch warning

I am trying to combining multiple pointcuts of getter and setter to create an advice that will be executed if both pointcuts are executed. I have tried in normal AspectJ class and annotation #Aspect class but still it gives me warning adviceDidNotMatch and eventually the advice is not executed. Strangely if I change && (AND) with || (OR) it works, but why && doesn't work at all?
Here is the advice declared in normal AspectJ class.
package testMaven;
pointcut getter() : execution(* testMaven.testing.getDd(..));
before() : getter(){
System.out.println("test get");
}
pointcut setter() : execution(* testMaven.testing.setDd(..));
before() : setter(){
System.out.println("test set");
}
pointcut combine(): getter() && setter();
before(): combine(){
System.out.println("testing combine");
}
}
Here is the advice declared in annotation #Aspect class
package testMaven;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
#Aspect
public class aspecter {
#Pointcut("call (* testMaven.testing.getDd(..))")
public void getter(){
}
#Pointcut("call (* testMaven.testing.setDd(..))")
public void setter(){}
#Pointcut("execution (* testMaven.tester.setZ(..))")
public void setterZ(){}
#Before("setterZ()")
public void settingZ(){
System.out.println("before set Z");
}
#Pointcut("getter() && setter()")
public void getterSetter(){}
#After("getterSetter()")
public void testerd(){
System.out.println("works");
}
#Pointcut("getter() && setterZ()")
public void getterSetter2(){}
#After("getterSetter2()")
public void testinger(){
System.out.println("ok");
}
}
Here is the testing class that I want to be advised:
package testMaven;
public class testing {
public int dd;
public int getDd() {
return dd;
}
public void setDd(int dd) {
this.dd = dd;
}
}
package testMaven;
public class testing {
public int dd;
public int getDd() {
return dd;
}
public void setDd(int dd) {
this.dd = dd;
}
public void aa(int a){
System.out.println(a);
}
}
And here is the main class:
package testMaven;
public class MainApp {
public static void main(String[] args) {
// TODO Auto-generated method stub
testing test = new testing();
test.aa(2);
test.setDd(3);
tester et = new tester();
et.setZ(3);
et.printNo(1000);
System.out.println(test.getDd());
}
}
Is there something wrong with my code? Any help is appreciated.
Thanks
You asked if there is something wrong with your code. The answer is yes. The two pointcuts setter() and getter() are mutually exclusive. Thus combining them with && - i.e. creating an intersection of two mutually disjunct sets of joinpoints - logically leads to an empty result set. Ergo your combined pointcut does not match. You should use || as uniknow suggested in his/her comment.
If you want to achieve something else, please explain it in a comprehensible way, if necessary by giving examples, in a comment or by updating your question. I really did not get what you really want.

Java - Method implementation dependent from parameter value

Consider a method
public void doSomething(String actionID){
switch (actionID){
case "dance":
System.out.print("I'm dancing");
break;
case "sleep":
System.out.print("I'm sleeping");
break;
default:
System.out.print("I've no idea what I'm doing");
}
The implementation of the method depends on the value of the parameter. Is there a more elegant way to do this, or a different design pattern to replicate the behaviour?
If the caller decides what logic is executed by passing different strings, then why not just have them call different methods:
public void doSomething(String actionID) {...}
...
doSomething("dance");
doSomething("sleep");
VS.:
public void dance() {...}
public void sleep() {...}
...
dance();
sleep();
It seems like you're unnecessarily funnelling all the calls into doSomething
But the strings might not always be literals. What if you're taking them from the console?
You could provide static mappings from the strings to the corresponding functions:
class MyClass {
private static final Map<String, Consumer<MyClass>> map = new HashMap<>();
static {
map.put("sleep", MyClass::sleep);
map.put("dance", MyClass::dance);
}
public void doSomething(String actionID) {
map.getOrDefault(actionID, MyClass::doNothing).accept(this);
}
public void dance() {
System.out.print("I'm dancing");
}
public void sleep() {
System.out.print("I'm sleeping");
}
private void doNothing() {
System.out.println("I've no idea what I'm doing");
}
}
This makes scenarios where you have a lot of switch cases a lot cleaner.
Introduce an interface, e.g.
public interface HumanState {
public void tellMeWhatYouAreDoing();
}
encapsulate the logic in different implementations
public class DancingState implements HumanState {
#Override
public void tellMeWhatYouAreDoing() {
System.out.println("I'm dancing");
}
}
public class SleepingState implements HumanState {
#Override
public void tellMeWhatYouAreDoing() {
System.out.println("I'm sleeping");
}
}
public class UnknownState implements HumanState {
#Override
public void tellMeWhatYouAreDoing() {
System.out.println("I've no idea what I'm doing");
}
}
and use a map. E.g.
public class HumanStateExample {
public static void main(String[] args) {
HumanStateExample humanStateExample = new HumanStateExample();
humanStateExample.doSomething("dance");
humanStateExample.doSomething("sleep");
humanStateExample.doSomething("unknown");
}
private final HashMap<String, HumanState> humanStateMap;
public HumanStateExample(){
humanStateMap = new HashMap<String, HumanState>();
humanStateMap.put("dance", new DancingState());
humanStateMap.put("sleep", new SleepingState());
}
public void doSomething(String action) {
HumanState humanState = humanStateMap.get(action);
if(humanState == null){
humanState = new UnknownState();
}
humanState.tellMeWhatYouAreDoing();
}
}
I'm not sure how the pattern is called, but it is very useful if you need to delegate the method call based on more than one parameter:
Create a lot of handlers where each one knows when it is responsible for handling a call. Then just loop through them and invoke the first one matching the parameter.
edit: I renamed the class from FancyParameterActionFactory to FancyParameterActionUtility: it is not a factory, the name was misleading
//Your method, but this time with a complex object, not with a simple string.
public void doSomething(FancyParameterObject fpo){
FancyParameterActionUtility.invokeOn(fpo);
}
//The utility which can handle the complex object and decides what to do.
public class FancyParameterActionUtility{
public Interface FPAHandler{
void invoke(FancyParameterObject fpo);
boolean handles(FancyParameterObject fpo);
}
//Omitted: Different implementations of FPAHandler
public static List<FPAHandler> handlers = new LinkedList<>();
static{
handlers.add(new DanceHandler());
handlers.add(new SleepHandler());
//Omitted: Different implementations of FPAHandler
}
public static void invokeOn(FancyParameterObject fpo){
for(FPAHandler handler:handlers){
if (handler.handles(fpo)){
handler.invoke(fpo);
return;
}
}
//Default-Behavior
}
}
Here is a simple implementation of the command pattern based your sample problem. I define a general AbstractCommand abstract class which contains two methods. The first method, createCommand(), instantiates a command class based on an input string name. This is how you can delegate your string input to create the right type of command. The second method is doAction(), and this is left undefined, to be implemented later on by specific concrete command classes.
public abstract class AbstractCommand {
public static AbstractCommand createCommand(String name) {
try {
String clsName = name + "Command";
Class<?> cls = Class.forName(clsName);
AbstractCommand command = (AbstractCommand) cls.newInstance();
return command;
}
catch (Exception e) {
System.out.println("Something went wrong.");
}
}
public abstract void doAction();
}
public class DanceCommand extends AbstractCommand {
public void doAction() {
System.out.println("I'm dancing");
}
}
public class TestCommandPattern {
public void doSomething(String actionID) {
AbstractCommand cmd = AbstractCommand.createCommand(actionID);
cmd.doAction();
}
public static void main(String[] args) {
TestCommandPattern test = new TestCommandPattern();
test.doSomething("Dance"); // should print "I'm dancing"
}
}
Now that this framework has been setup, you could easily add other commands for the various types of actions in your original problem. For example, you could create a SleepCommand class which would output I'm sleeping, or do whatever action you wish.

Parameterized test case classes in JUnit 3.x

I have a JUnit 3.x TestCase which I would like to be able to parameterize. I'd like to parametrize the entire TestCase (including the fixture). However, the TestSuite.addTestSuite() method does not allow be to pass a TestCase object, just a class:
TestSuite suite = new TestSuite("suite");
suite.addTestSuite(MyTestCase.class);
I would like to be able to pass a parameter (a string) to the MyTestCase instance which is created when the test runs. As it is now, I have to have a separate class for each parameter value.
I tried passing it an anynomous subclass:
MyTestCase testCase = new MyTestCase() {
String getOption() {
return "some value";
}
}
suite.addTestSuite(testCase.getClass());
However, this fails with the assertion:
... MyTestSuite$1 has no public constructor TestCase(String name) or TestCase()`
Any ideas? Am I attacking the problem the wrong way?
If this is Java 5 or higher, you might want to consider switching to JUnit 4, which has support for parameterized test cases built in.
Rather than create a parameterized test case for the multiple/different backends you want to test against, I would look into making my test cases abstract. Each new implementation of your API would need to supply an implementing TestCase class.
If you currently have a test method that looks something like
public void testSomething() {
API myAPI = new BlahAPI();
assertNotNull(myAPI.something());
}
just add an abstract method to the TestCase that returns the specific API object to use.
public abstract class AbstractTestCase extends TestCase {
public abstract API getAPIToTest();
public void testSomething() {
API myAPI = getAPIToTest();
assertNotNull(myAPI.something());
}
public void testSomethingElse() {
API myAPI = getAPIToTest();
assertNotNull(myAPI.somethingElse());
}
}
Then the TestCase for the new implementation you want to test only has to implement your AbstractTestCase and supply the concrete implementation of the API class:
public class ImplementationXTestCase extends AbstractTestCase{
public API getAPIToTest() {
return new ImplementationX();
}
}
Then all of the test methods that test the API in the abstract class are run automatically.
Ok, here is a quick mock-up of how JUnit 4 runs parameterized tests, but done in JUnit 3.8.2.
Basically I'm subclassing and badly hijacking the TestSuite class to populate the list of tests according to the cross-product of testMethods and parameters.
Unfortunately I've had to copy a couple of helper methods from TestSuite itself, and a few details are not perfect, such as the names of the tests in the IDE being the same across parameter sets (JUnit 4.x appends [0], [1], ...).
Nevertheless, this seems to run fine in the text and AWT TestRunners that ship with JUnit as well as in Eclipse.
Here is the ParameterizedTestSuite, and further down a (silly) example of a parameterized test using it.
(final note : I've written this with Java 5 in mind, it should be trivial to adapt to 1.4 if needed)
ParameterizedTestSuite.java:
package junit.parameterized;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class ParameterizedTestSuite extends TestSuite {
public ParameterizedTestSuite(
final Class<? extends TestCase> testCaseClass,
final Collection<Object[]> parameters) {
setName(testCaseClass.getName());
final Constructor<?>[] constructors = testCaseClass.getConstructors();
if (constructors.length != 1) {
addTest(warning(testCaseClass.getName()
+ " must have a single public constructor."));
return;
}
final Collection<String> names = getTestMethods(testCaseClass);
final Constructor<?> constructor = constructors[0];
final Collection<TestCase> testCaseInstances = new ArrayList<TestCase>();
try {
for (final Object[] objects : parameters) {
for (final String name : names) {
TestCase testCase = (TestCase) constructor.newInstance(objects);
testCase.setName(name);
testCaseInstances.add(testCase);
}
}
} catch (IllegalArgumentException e) {
addConstructionException(e);
return;
} catch (InstantiationException e) {
addConstructionException(e);
return;
} catch (IllegalAccessException e) {
addConstructionException(e);
return;
} catch (InvocationTargetException e) {
addConstructionException(e);
return;
}
for (final TestCase testCase : testCaseInstances) {
addTest(testCase);
}
}
private Collection<String> getTestMethods(
final Class<? extends TestCase> testCaseClass) {
Class<?> superClass= testCaseClass;
final Collection<String> names= new ArrayList<String>();
while (Test.class.isAssignableFrom(superClass)) {
Method[] methods= superClass.getDeclaredMethods();
for (int i= 0; i < methods.length; i++) {
addTestMethod(methods[i], names, testCaseClass);
}
superClass = superClass.getSuperclass();
}
return names;
}
private void addTestMethod(Method m, Collection<String> names, Class<?> theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning("Test method isn't public: "+m.getName()));
return;
}
names.add(name);
}
private boolean isPublicTestMethod(Method m) {
return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
}
private boolean isTestMethod(Method m) {
String name= m.getName();
Class<?>[] parameters= m.getParameterTypes();
Class<?> returnType= m.getReturnType();
return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
}
private void addConstructionException(Exception e) {
addTest(warning("Instantiation of a testCase failed "
+ e.getClass().getName() + " " + e.getMessage()));
}
}
ParameterizedTest.java:
package junit.parameterized;
import java.util.Arrays;
import java.util.Collection;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.parameterized.ParameterizedTestSuite;
public class ParameterizedTest extends TestCase {
private final int value;
private int evilState;
public static Collection<Object[]> parameters() {
return Arrays.asList(
new Object[] { 1 },
new Object[] { 2 },
new Object[] { -2 }
);
}
public ParameterizedTest(final int value) {
this.value = value;
}
public void testMathPow() {
final int square = value * value;
final int powSquare = (int) Math.pow(value, 2) + evilState;
assertEquals(square, powSquare);
evilState++;
}
public void testIntDiv() {
final int div = value / value;
assertEquals(1, div);
}
public static Test suite() {
return new ParameterizedTestSuite(ParameterizedTest.class, parameters());
}
}
Note: the evilState variable is just here to show that all test instances are different as they should be, and that there is no shared state between them.
a few details are not perfect, such as the names of the tests in the IDE being the same across parameter sets (JUnit 4.x appends [0], [1], ...).
To solve this you just need to overwrite getName() and change the constructor in your test case class:
private String displayName;
public ParameterizedTest(final int value) {
this.value = value;
this.displayName = Integer.toString(value);
}
#Override
public String getName() {
return super.getName() + "[" + displayName + "]";
}
For Android projects, we wrote a library called Burst for test parameterization. For example
public class ParameterizedTest extends TestCase {
enum Drink { COKE, PEPSI, RC_COLA }
private final Drink drink;
// Nullary constructor required by Android test framework
public ConstructorTest() {
this(null);
}
public ConstructorTest(Drink drink) {
this.drink = drink;
}
public void testSomething() {
assertNotNull(drink);
}
}
Not really an answer to your question since you're not using Android, but a lot of projects which still use JUnit 3 do so because Android's test framework requires it, so I hope some other readers will find this helpful.

Categories