Sto cercando di usare PowerMock per deridere una classe con un metodo statico, ma in particolare desidero farlo all'interno di un Test di strumentazione Android. Per essere chiari vorrei eseguire il test su un vero dispositivo Android o su un emulatore. Utilizzo Android Studio (1.5.1) e Gradle (1.5.0). Per evitare aringhe rosse ho creato un'applicazione per l'hello world davvero semplice e piuttosto rozza. Questa applicazione mostra semplicemente 2 pezzi di testo, uno recuperato da un metodo statico e uno da un metodo non statico. Ho scritto test di strumentazione per entrambe queste classi di 'provider di testi'. Si può vedere questa applicazione qui:Utilizzo di PowerMock e Mockito in un test di Strumentazione Android - Errore - File duplicati - org.mockito.plugins.MockMaker
https://github.com/Kai2k/PowerMockAndroidTest.git
Quando si tenta di eseguire i test di strumentazione ottengo l'errore che compare molte persone che cercano di raggiungere questo get:
com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK mockito-extensions/org.mockito.plugins.MockMaker
File1: /Users/kaiarmer/.gradle/caches/modules-2/files-2.1/com.crittercism.dexmaker/dexmaker-mockito/1.4/70892a94894462c1b35df3c8a77d21b7e843550b/dexmaker-mockito-1.4.jar
File2: /Users/kaiarmer/.gradle/caches/modules-2/files-2.1/org.powermock/powermock-api-mockito/1.6.4/fe12509b7e9e49d25131f4155145748a31e42e40/powermock-api-mockito-1.6.4.jar
mie dipendenze sulle mie app di aspetto di file build.gradle come questo:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
testCompile 'org.powermock:powermock-api-mockito:1.6.4'
testCompile 'org.powermock:powermock-module-junit4-rule-agent:1.6.4'
testCompile 'org.powermock:powermock-module-junit4-rule:1.6.4'
testCompile 'org.powermock:powermock-module-junit4:1.6.4'
androidTestCompile 'org.mockito:mockito-core:1.10.19'
androidTestCompile 'org.powermock:powermock-api-mockito:1.6.4'
androidTestCompile 'com.android.support:support-annotations:23.1.1'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
androidTestCompile 'com.crittercism.dexmaker:dexmaker:1.4'
androidTestCompile 'com.crittercism.dexmaker:dexmaker-mockito:1.4'
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
}
Si noterà alcune dipendenze 'testCompile' su finto potere e si riferiscono d librerie (oltre a AndroidTestCompile). Questo perché per la cintura e le parentesi graffe volevo vedere se potevo ottenere un test di junit (non strumentazione) (che usa power mock) funzionante anche. Sono stato in grado di ottenere questo risultato, ma non il test della strumentazione. Qui è il mio codice (orribile):
attività principale:
package com.example.android.powermocktest;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
TextView textView1 = (TextView)findViewById(R.id.textView1);
textView1.setText(GetStaticValue.getTheStaticValue());
TextView textView2 = (TextView)findViewById(R.id.textView2);
textView2.setText(new GetNonStaticValue().getNonStaticString());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
file di layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.example.android.powermocktest.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.example.android.powermocktest.MainActivity"
tools:showIn="@layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView1"
android:text="Hello World!" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:id="@+id/textView2"
android:text="Hello World again!" />
</RelativeLayout>
e le classi per ottenere i valori statici e non statici:
package com.example.android.powermocktest;
public class GetStaticValue {
private final static String THE_VALUE = "Hi, I'm static";
public static String getTheStaticValue(){
return THE_VALUE;
}
}
package com.example.android.powermocktest;
public class GetNonStaticValue {
public String getNonStaticString(){
return "Hi, I'm non-static";
}
}
E infine le classi di test. I primi test di strumentazione:
package com.example.android.powermocktest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(AndroidJUnit4.class)
@PrepareForTest(GetStaticValue.class)
public class GetStaticValueTest {
@Test
public void getStaticValueReturnsValue(){
PowerMockito.mockStatic(GetStaticValue.class);
when(GetStaticValue.getTheStaticValue()).thenReturn("Hi, I'm static");
assertThat(GetStaticValue.getTheStaticValue(), equalTo("Hi, I'm static"));
}
}
package com.example.android.powermocktest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.when;
@RunWith(AndroidJUnit4.class)
public class GetNonStaticValueTest {
@Test
public void getNonStaticValueReturnsNonStaticValue(){
GetNonStaticValue getNonStaticValue = Mockito.mock(GetNonStaticValue.class);
when(getNonStaticValue.getNonStaticString()).thenReturn("Hi, I'm non-static");
assertThat(getNonStaticValue.getNonStaticString(), equalTo("Hi, I'm non-static"));
}
}
e ora il test JUnit (che di lavoro):
package com.example.android.powermocktest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.powermock.api.mockito.PowerMockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(GetStaticValue.class)
public class GetStaticValueTest {
@Test
public void getStaticValueReturnsValue() {
PowerMockito.mockStatic(GetStaticValue.class);
when(GetStaticValue.getTheStaticValue()).thenReturn("Hi, I'm static");
assertThat(GetStaticValue.getTheStaticValue(), equalTo("Hi, I'm static"));
}
}
quello che ho provato finora:
Ho fatto qualche lettura a questo problema e una soluzione che ho trovato, che non ho provato è quella di aprire manualmente i contenitori della libreria power mock e rimuovere la classe duplicata offendente. Come (al lavoro) usiamo gradle e non lavoriamo con i jar locali preferirei non farlo.
How to get Powermock to work with Dexmaker
persone che ho sentito suggeriscono usando ‘escludere’ all'interno della sezione Android del file di build Gradle. Questo non è adatto in quanto il test di strumentazione per eseguirlo è costruito (da Android Studio/Gradle) come apk e lanciato sul dispositivo. Di conseguenza sono necessari i power mock jars per eseguire i test.
android studio: gradle dependency error
Ho provato a rimuovere alcune delle dipendenze che vedete elencati nel mio file di generazione sopra. Ad esempio, ho provato a rimuovere la dipendenza "org.powermock: powermock-api-mockito", ma è necessario usare "mockStatic" per esempio.
sembra che qualcuno ha avuto successo quando si utilizza Maven, dicendo Maven per escludere i duplicati:
https://groups.google.com/forum/#!searchin/powermock/android/powermock/ndZ2ZliYGCY/Eh226605u2cJ
PowerMock + Mockito + Maven on Android app showing Dex loader error
ho cercato di vedere se c'è un modo di raccontare a Gradle ignora le classi duplicate all'interno dei barattoli delle dipendenze, ma finora non ho avuto successo.
Credo che valga la pena perseguire come prima cosa, il power mock può essere molto utile se usato con parsimonia e in secondo luogo, non posso credere che questo problema non sia stato risolto (è stato sicuramente riscontrato prima!).
Come ultimo punto, ho notato che Google se stessi sembrano suggerire che intrinsecamente beffardo è solo per unità, non i test di strumentazione:
http://developer.android.com/training/testing/start/index.html
Tutto l'aiuto che chiunque può offrire sarebbe molto apprezzato. Grazie.
Sei stato capace di risolvere questo problema? – thedarkpassenger
No, non ancora, questo problema è ancora in sospeso. – Kai