我正在测试一个Android应用程序,并且正在使用我大学提供给我的图书馆,1-4班来自我的讲师供我们使用。
我的课程结构如下:
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);
}
ClassFour的结构如下:
public ClassFour {
...
public ClassFour(Context context);
...
}
我正在测试的Class是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?
答案 0 :(得分: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旨在轻松制作课程的替换实例,以便您可以检查您与被测试课程的互动情况:在这里,您正在创建假(&#34;测试双&#34;) ClassOne,ClassTwo和ClassThree的实现,用于测试ClassFive。 (您可能还会选择使用真实实现或手动编写的虚假实现,如果其中任何一个对于您的特定情况比Mockito生成的实现更有意义。)除非您以其他方式存根,否则Mockito实现返回虚拟值,如零或{{ 1}}对于所有已实现的方法,因此尝试在null
返回的getClsThree
上调用null
会导致NPE,直到您以getClsTwo
为其他方式。
如果mockThree的存根在测试之间发生变化,您可以在初始化ClassFive之前将它们移动到测试中。我还坚持上面的JUnit3语法和显式getClsTwo
,因为如果您不使用Android Testing Support Library,Android检测测试会停留在JUnit3语法上。对于JUnit4或该库的测试,您可以使用a cleaner alternative to initMocks
。一旦你对Mockito感到满意,你也可以考虑RETURNS_DEEP_STUBS,但我喜欢自己保持我的存根;文档也正确警告&#34;每当模拟返回模拟时,仙女就死了#34;
这种漫长而复杂,并不觉得没必要吗? 是。您正在解决违反Law of Demeter的问题,维基百科总结(强调我的):
- 每个单位应该对其他单位的知识有限:只有单位&#34;密切关注&#34;与当前单位有关。
- 每个单位只应与其朋友交谈;不要和陌生人说话。
- 只与您的直接朋友交谈。
您的问题和详细解决方案都源于ClassFive,具体取决于ClassThree,但仅限于ClassOne和ClassTwo实现细节。这不是一个严格的法律,但在大学以外的自己的代码中,您可能会将此视为重新审视ClassOne,ClassTwo和ClassFive的设计以及它们如何交互的标志。如果ClassFive直接依赖于ClassThree,那么在生产和测试中使用代码可能会更容易,并且您可能发现ClassOne根本不是必需的。
initMocks
答案 1 :(得分:1)
我想通过展示代码的外观来支持@ JeffBowman的答案。
建议的解决方案意味着您将另一个参数添加到构造函数参数列表中,并且已经很长。通过遵循支持合成而不是继承原则
,可以简化您的代码 ClassFive
中构造函数的大多数参数只能传递给父类构造函数。
在这种情况下,最好不要从超类继承,而是创建超类的接口(例如:支持IDE的提取)(允许调用SuperInterface
由两者实现,超级班和CLassFive
。
您将一个SuperInterface
类型的参数替换为传递给超类的所有参数。
然后,您只需将SuperInterface
未实现的CLassFive
的所有方法直接委托给SuperInterface
实例。
这就是它的样子:
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
的所有类的重复方法委派,则此模式也可以反过来。
如果您的专业化只覆盖界面的几个方法并且几乎完全相同,那么这种替代方法很有用。
在这种情况下,您创建的接口可能无法由超类实现。在接口中声明的方法甚至不需要是超类公共方法的一部分。接口只声明超类(现在应该更好地称为“泛型类”)需要使用派生行为的方法。
这看起来像这样:
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());
}