I have an activity Let's say A
it is defined in a given application and it's corresponding manifest. This activity loads a contentView which it just loads via a static R index. Let's say R.layout.foo. That layout happens to have a component in there that looks on something that isn't the base android attrs. I am seeing that when a Test app runs this activity the theme and the styles within the theme are not filled with anything.
Sample Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.foo.bar"
android:versionCode="1"
android:versionName="Test"
android:installLocation="auto">
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:description="@string/description"
android:theme="@style/Theme.Custom"
android:name=".MyApplicationObject">
<activity android:name=".activity.A"/>
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
</manifest>
Activity A
public class A extends Activity {
public void onCreate(Bundle a) {
setContentView(R.layout.foo);
}
}
A Layout, foo.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<com.foo.bar.CustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
CustomView
public class CustomView extends RelativeLayout {
public CustomView(Context context, AttributeSet set) {
this(context, set, R.attr.CustomViewStyle);
}
public CustomView(Context c, AttributeSet set, int defStyle) {
super(c, set, defStyle);
TypedArray array = c.obtainStyledAttributes(set, R.styleable.CustomViewAttrs, defStyle, defStyle);
final int layout = array.getResourceId(R.styleable.CustomViewAttrs_layout, 0);
final Drawable icon = array.getDrawable(R.styleable.CustomViewAttrs_icon);
array.recycle();
if (layout == null) {
throw new IllegalStateException("WTF");
}
if (icon == null) {
throw new IllegalStateException("For real, WTF");
}
}
}
Some Resources in a Values file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Used to define a namespace for our special types -->
<declare-styleable name="CustomTypes">
<attr name="CustomViewStyle" format="reference"/>
</declare-styleable>
<!-- Used to define Attributes specific to our new View, "CustomView" -->
<declare-styleable name="CustomViewAttrs">
<attr name="layout" format="reference"/>
<attr name="anotherOne" format="reference"/>
</declare-styleable>
<!-- A usable style that we can reference later to pass to an instance of CustumView -->
<style name="CustomView">
<item name="layout">@layout/foo</item>
<item name="AnotherOne">@drawable/icon</item>
</style>
<!-- A Style to act as o开发者_如何学JAVAur Theme, referenced in our Manifest as the Theme for all activities -->
<style name="Theme.Custom" parent="android:Theme">
<item name="CustomViewStyle">@style/CustomView</item>
</style>
<resources>
This works fine, but when I use an ActivityUnitTest to load up an instance of A the values inside of the TypedArray are empty.
Some Test Class
public class ActivityTester extends ActivityUnitTestCase<A> {
public ActivityTester() {
super(A.class);
}
public void testOne() {
Intent intent = new Intent(getInstrumentation().getTargetContext(), A.class);
// This fails with my IllegalStateException
startActivity(intent, null, null);
}
}
Any idea how/if the target Application get's it's manifest parsed? It seems like the theme isn't even getting loaded. The documentation for startActivity() states that it will start the activity the same way that context.startActivity() would. I don't see that happening as it doesn't seem to respect the manifest data of the activity.
So after a lot of time spent trying to do this, I found that personally the best way to do this is to mock out the Theme itself.
public void setUp() {
ContextThemeWrapper context = new ContextThemeWrapper(getInstrumentation().getTargetContext(), R.style.Theme_MyTheme);
setActivityContext(context);
}
public void testOne() {
Intent intent = new Intent(getInstrumentation().getTargetContext(), A.class);
startActivity(intent, null, null);
}
Personally I don't like this, because it couples the unit test implementation to the stylistic nuances that I would like to offset to another role, (like a visual designer, or a graphic artist) and adds the possibility of the test being completely out of sync with the implementation that would be shipped. i.e. someone changes the Manifest files so that the the Theme
resource that is associated with an Activity
is different than what is being tested. This TestCase type just seems really short sighted since it could lead to a bunch of bad things like this. I mean you would end up with a bunch of ActivityUnitTestCase
instances that test the base functionality and then some Instrumentation Test Cases for the same Activity
, that just start it up or go through the transitions that might invoke a layout inflation. Not a real app being tested at that point.
You can use ActivityInstrumentationTestCase2 instead which is intended to tests Activities created using the system infrastructure:
import android.test.ActivityInstrumentationTestCase2;
public class ActivityTester extends ActivityInstrumentationTestCase2<ActivityTester> {
public ActivityTester() {
super(ActivityTester.class);
}
public void testOne() {
//Intent intent = new Intent(getInstrumentation().getTargetContext(), ActivityTester.class);
// This fails with my IllegalStateException
//startActivity(intent, null, null);
getActivity();
}
}
ActivityUnitTestCase is for unit test Activities in isolation.
There are some other errors in your code but I think they are the result of copying here eliminating some parts.
精彩评论