2016-12-31 286 views
0

我正在測試一個android應用程序,並且正在使用我的大學爲我提供的一個庫,1-4級來自我的講師供我們使用。Android單元測試:我如何測試?

我有一個類結構如下所示:

ClassOne

public ClassOne { 
    private ClassTwo clsTwo; 
    ... 
    public ClassOne(ClassTwo p1) 
    public ClassTwo getClsTwo(); 
} 

ClassTwo的結構爲這樣:

public ClassTwo { 
    private ClassThree clsThree; 
    ... 
    public ClassTwo() 
    public ClassThree getClsThree(); 
} 

ClassThree的結構爲這樣:

public ClassThree { 
    private HashMap<Bitmap> mBitmaps; 
    ... 
    private ClassFour clsFour; 
    ... 
    public ClassThree(ClassFour p1); 
    ... 
    public loadFile(String path, String name); 
    public loadFileFromAssetStore(String name); 
} 

CLA ssFour的結構爲這樣:

public ClassFour { 
    ... 
    public ClassFour(Context context); 
    ... 
} 

我測試是ClassFive,具體有方法的類強調了正在造成的問題:

public ClassFive { 
    private Bitmap myBitmap 
    ... 
    public ClassFive(...,...,...,ClassOne p,...){ 
     super(..., p, 
      p.getClsTwo().getClsThree().loadFileFromAssetStore("Default value")); 
     this.myBitmap = loadCorrectFile(...,p); 
    } 
    private Bitmap loadCorrectFile(..., ClassOne p){ 
     String strCorrectFileName; 
     switch(...){ 
      ... 
      // set value of strCorrectFileName 
      ... 
     } 
     this.myBitmap = p.getClsTwo().getClsThree().loadFileFromAssetStore(strCorrectFileName); 
    } 
} 

我的問題是我需要測試使用的構造方法ClassFive,但是當用NPE調用構造函數時,所有測試都會'翻倒'。

public class ClassFiveTest { 

@Mock 
private ClassOne mockClassOne = Mockito.Mock(ClassOne.class); 

@Test 
public void testConstructorGetName() throws Exception { 
    ClassFive instance = new ClassFive(..., mockClassOne); 
    ... 
    // Assertions here 
    ... 
} 

我的問題是,被返回一個空指針異常之前我的測試可以讓我的斷言。我需要使用mockito嗎?因爲我嘗試過 - 也許我只是在這個實例中使用了錯誤。或者我需要使用儀器測試?當我嘗試進行儀器測試時,發現無法訪問ClassOne和ClassTwo?

回答

3

這很容易通過一些stubbing補救。

@Mock private ClassOne mockClassOne; // Don't call `mock`; let @Mock handle it. 
@Mock private ClassTwo mockClassTwo; 
@Mock private ClassThree mockClassThree; 

@Override public void setUp() { 
    MockitoAnnotations.initMocks(this); // Inits fields having @Mock, @Spy, and @Captor. 
    when(mockClassOne.getClsTwo()).thenReturn(mockClassTwo); 
    when(mockClassTwo.getClsThree()).thenReturn(mockClassThree); 

    // Now that you can get to mockClassThree, you can stub that too. 
    when(mockClassThree.loadFileFromAssetStore("Default value")).thenReturn(...); 
    when(mockClassThree.loadFileFromAssetStore("Your expected filename")).thenReturn(...); 
} 

總之,是的Mockito設計製作容易的更換類的實例,所以你可以用你的類被測檢查您互動:在這裏,你正在創建假的(「雙考」)ClassOne的實現,ClassTwo和ClassThree,用於測試ClassFive。 (您也可以選擇使用真實的實現或手動編寫的假實現,如果其中任何一個對於您的特定情況比Mockito生成的實現更有意義)。除非您以其他方式存儲它們,否則Mockito實現會返回零值或null所有實施的方法,所以試圖撥打getClsThree,nullgetClsTwo返回導致NPE,直到你存根getClsTwo否則。

如果mockThree的存根在測試之間改變,您可以在初始化ClassFive之前將它們移動到您的測試中。我還堅持使用JUnit3語法和上面顯式的initMocks,因爲如果不使用Android Testing Support Library,Android Instrumentation測試會停留在JUnit3語法上;對於JUnit4或與該庫的測試,您可以使用a cleaner alternative to initMocks。一旦你對Mockito感到滿意,你也可以考慮RETURNS_DEEP_STUBS,但我喜歡保持我的存根清晰自己;該文件也正確地警告「每次模擬迴歸模擬,仙女死亡」。


這是不是很漫長而複雜,它不覺得沒有必要嗎? 是的。你周圍侵犯Law of Demeter,它總結了維基百科(重點煤礦)工作作爲:

  • 每個單元有關於其他單位只有有限的知識:只有單位「密切」關係到當前的單元。
  • 每個單位只能跟朋友交談;不要與陌生人交談。
  • 只與您的直接朋友交談。

你的問題,並根據ClassThree,但只能通過ClassOne和ClassTwo實施細則從ClassFive您詳細的解決方案都幹。這不是一個嚴格的法律,但是在大學之外的自己的代碼中,您可以將此視爲一種標誌,重新審視ClassOne,ClassTwo和ClassFive的設計以及它們之間的交互方式。如果ClassFive直接依賴於ClassThree,那麼在生產和測試中使用代碼可能會更容易,也許您會發現ClassOne根本就沒有必要。

// ClassFive doesn't just work with its dependency ClassOne, it works directly with its 
// dependency's dependency's dependency ClassThree. 
super(..., p, 
    p.getClsTwo().getClsThree().loadFileFromAssetStore("Default value")); 
+0

表現的很出色,太感謝你了!你的解釋絕對精彩。 –

+1

@PeterReid不客氣!我很高興它有幫助。祝你學業好,新年快樂! –

1

我想通過展示代碼的樣子來支持@JeffBowman的答案。

建議的解決方案意味着您將另一個參數添加到構造函數參數列表中,而且已經很長了。您的代碼可以由青睞組成如下超過繼承原則ClassFive構造的

大多數參數都是隻有在那裏被傳遞給父類的構造函數被簡化。

在這種情況下,最好不要從那個超類繼承,而是創建一個超級類的接口(例如:提取支持你的IDE)(讓我們的調用是由SuperInterface實現的,超級類和CLassFive

的更換由SuperInterface類型的一個參數傳遞給超類的所有參數。

然後你直接委託不受CLassFive實施直接到的SuperInterface所有方法SuperInterface instance。

這是它會是什麼樣子:

public interface SuperInterface { 
    // all public methods of the super class. 
    } 

public class ClassFive implements SuperInterface{ 
    private final SuperInterface superClass; 
    private final Bitmap myBitmap 
    public ClassFive(SuperInterface superClass ,ClassTree p){ 
     this.superClass = superClass; 
     p.loadFileFromAssetStore("Default value")); 
     this.myBitmap = loadCorrectFile(...,p); 
    } 
    @Override 
    public void someMethodDeclaredInInterface(){ 
     this.superClass.someMethodDeclaredInInterface(); 
    } 
} 

這種模式也適用反之亦然,如果你不喜歡重複的方法代表團遍佈延伸SuperInterface你的類。

如果您的專業化覆蓋接口的幾個方法並幾乎完全相同,則此替代方法非常有用。

在這種情況下,您創建的接口可能不會由超類實現。在接口中聲明的方法甚至不需要成爲超類public方法的一部分。該接口只聲明超類(現在應該更好地稱爲「泛型類」)需要使用派生行爲的方法。

這應該是這樣的:

interface AnimalSound{ 
    String get(); 
} 

class DogSound implements AnimalSound{ 
    @Override 
    public String get(){ 
    return "wouff"; 
    } 
} 

class CatSound implements AnimalSound{ 
    @Override 
    public String get(){ 
    return "meaw"; 
    } 
} 

class Animal { 
    private final AnimalSound sound; 
    public Animal(AnimalSound sound){ 
    this.sound = sound; 
    } 

    public String giveSound(){ 
    return sound.get(); 
    } 
} 

這是我們如何使用它:

List<Animal> animals = new ArrayList<>(); 
animals.add(new Animal(new DogSound())); 
animals.add(new Animal(new CatSound())); 
for(Animal animal : animals){ 
    System.out.println(animal.giveSound()); 
}