这是我在某些服务类中的方法。它是公开的,所以应该进行测试。我根本不知道应该测试什么。我嘲笑public class MainActivity extends Activity {
private interface GooglePlacesClient {
@GET("/maps/api/place/autocomplete/json")
Observable<PlacesResult> autocomplete(
@Query("key") String key,
@Query("input") String input);
}
private class PlacesResult {
@Expose
List<MainActivity.Prediction> predictions;
@Expose
String status;
}
private class Prediction {
@Expose
String description;
}
private static final String LOG_TAG = "RxRetrofitAutoComplete";
private static final String GOOGLE_API_BASE_URL = "https://maps.googleapis.com";
private static final String API_KEY = "XXX";
private static final int DELAY = 500;
GooglePlacesClient mGooglePlacesClient;
@InjectView(R.id.editText1)
EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
if (API_KEY.length()<10) {
Toast.makeText(this, "API KEY is unset!", Toast.LENGTH_LONG).show();
return;
}
if (mGooglePlacesClient == null) {
mGooglePlacesClient = new RestAdapter.Builder()
.setConverter(new GsonConverter(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()))
.setEndpoint(GOOGLE_API_BASE_URL)
.setLogLevel(RestAdapter.LogLevel.FULL).build()
.create(GooglePlacesClient.class);
}
Observable<EditText> searchTextObservable = ViewObservable.text(editText);
searchTextObservable.debounce(DELAY, TimeUnit.MILLISECONDS)
.map(new Func1<EditText, String>() {
@Override
public String call(EditText editText) {
return editText.getText().toString();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String s) {
Log.d(LOG_TAG, s);
try {
mGooglePlacesClient
.autocomplete(API_KEY, URLEncoder.encode(s, "utf8"))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<PlacesResult>() {
@Override
public void call(PlacesResult placesResult) {
List<String> strings = new ArrayList<String>();
for (MainActivity.Prediction p : placesResult.predictions) {
strings.add(p.description);
}
ListView listView = (ListView) findViewById(R.id.listView1);
if (listView != null) {
listView.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, strings));
}
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
});
}
@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);
}
@Override
protected void onStop() {
super.onStop();
}
}
和spyOn函数调用,但是这个实现它是不可能的(不是吗?)
我正在使用Writer
和Mockito
现在,我只能使函数抛出并断言该异常
任何帮助?
JUnit
答案 0 :(得分:4)
如果您觉得添加特殊内容是业务逻辑,因此是您的类的职责,那么创建FileWriter不是(根据单一责任模式。
因此,您应该使用已在测试下的类中调用的FileWriterFactory
。然后你可以模拟FileWriterFactory
来重新调用Writer
接口的模拟实现,然后你可以检查它是否有预期的字符串。
你的CuT会改为:
private final WriterFactory writerFactory;
public ClassUnderTest(@Inject WriterFactory writerFactory){
this.writerFactory = writerFactory;
}
@Override
public void initIndexFile(File emptyIndexFile) {
try {
Writer writer = writerFactory.create(emptyIndexFile);
writer.write("[]");
writer.close();
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}
和你的测试:
class Test{
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@Mock
private FileWriterFactory fileWriterFactory;
private Writer fileWriter = spy(new StringWriter());
File anyValidFile = new File(".");
@Test
public void initIndexFile_validFile_addsEmptyraces(){
//arrange
doReturn(fileWriter).whenn(fileWriterFactory).create(any(File.class));
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
//assert
verify(fileWriterFactory)create(anyValidFile);
assertEquals("text written to File", "[]", fileWriter.toString());
verify(fileWriter).close();
}
}
此外,您可以轻松检查切割是否截获IOException:
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void initIndexFile_missingFile_IndexFileInitializationException(){
//arrange
doReturnThrow(new IOException("UnitTest")).whenn(fileWriterFactory).create(any(File.class));
//assert
exception.expect(IndexFileInitializationException.class);
exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
}
尼斯!一家工厂只是为了测试3行代码! - 尼古拉斯·菲洛托
这是一个好点。
问题是:该类是否有任何方法直接与File
对象进行交互,需要在之后创建FileWriter?
如果答案是“否”(因为它很可能)遵循KISS原则,您应该直接注入Writer
对象而不是工厂,并使用没有File参数的方法。
private final Writer writer;
public ClassUnderTest(@Inject Writer writer){
this.writer = writer;
}
@Override
public void initIndexFile() {
try {
writer.write("[]");
writer.close();
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}
修改测试:
class Test{
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@Rule public ExpectedException exception = ExpectedException.none();
@Mock
private FileWriterFactory fileWriterFactory;
@Mock
private Writer failingFileWriter;
private Writer validFileWriter = spy(new StringWriter());
File anyValidFile = new File(".");
@Test
public void initIndexFile_validFile_addsEmptyraces(){
//arrange
// act
new ClassUnderTest(validFileWriter).initIndexFile();
//assert
verify(fileWriterFactory)create(anyValidFile);
assertEquals("text written to File", "[]", fileWriter.toString());
verify(fileWriter).close();
}
@Test
public void initIndexFile_missingFile_IndexFileInitializationException(){
//arrange
doReturnThrow(new IOException("UnitTest")).whenn(failingFileWriter).whire(anyString());
//assert
exception.expect(IndexFileInitializationException.class);
exception.expectMessage("Error initialization index file "+anyValidFile.getPath());
// act
new ClassUnderTest(fileWriterFactory).initIndexFile(anyValidFile);
}
}
答案 1 :(得分:0)
要测试您的方法是否可以正确地与作者交互,通过发送正确的命令,您的Pogram必须暴露某种&#34; seam&#34;以便您的测试可以配置模拟FileWriter
。我不熟悉mockito
,但有一种方法是将FileWriter
实例封装在方法后面,然后您的测试可以覆盖该方法以返回模拟FileWriter
。
假设File
是一个接口:
public Writer getFileWriter(File emptyIndexFile) {
return new FileWriter(emptyIndexFile);
}
这可以允许您覆盖测试的上述方法并返回假的Writer
@Override
public Writer getFileWriter(File emptyIndexFile) {
return mockFileWriterInstance;
}
然后你的测试可以进行练习initIndexFile
并对操作进行断言。使用模拟文件编写器可以轻而易举地抛出IOException
,以便您可以使用错误处理逻辑。
答案 2 :(得分:0)
您只需在测试中为方法提供一个临时文件,只需按预期检查它是否包含[]
,然后再删除该文件。
类似的东西:
public class FileWritingTest {
// File to provide to the method initIndexFile
private File file;
/* This is executed before the test */
@Before
public void init() throws IOException {
// Create a temporary file
this.file = File.createTempFile("FileWritingTest", "tmp");
// Indicates that it should be removed on exit
file.deleteOnExit();
}
/* This is executed after the test */
@After
public void clean() throws IOException {
// Delete the file once test over
file.delete();
}
@Test
public void testInitIndexFile() throws IOException {
FileWriting fw = new FileWriting();
// Call the method
fw.initIndexFile(this.file);
// Check that the content is [] as expected
Assert.assertEquals("[]", new String(Files.readAllBytes(file.toPath())));
}
}
NB 1:我依赖于new String(byte[])
,这意味着我依赖于您在当前代码中执行的默认字符编码,但这不是一个好习惯,我们应该设置一个显式字符编码以避免平台相关。
注意2:假设您使用的是java 7或更高版本,您应该考虑使用 try-with-resources 语句来正确关闭您的编写器,然后您的代码就会是:
public void initIndexFile(File emptyIndexFile) {
try (Writer writer = new FileWriter(emptyIndexFile)) {
writer.write("[]");
} catch (IOException e) {
throw new IndexFileInitializationException(
"Error initialization index file " + emptyIndexFile.getPath()
);
}
}
答案 3 :(得分:0)
模拟一个依赖是可能和自然的,但是模拟在方法体中声明的对象并不自然而且很棘手。
我想象3个解决方案:
1)为什么不是简单地断言文件是用预期的字符写的,而不是嘲笑?
它可以避免欺骗,但如果您经常执行此任务并且想要对它们进行单元测试,则可能会多余且缓慢。
2)使局部变量成为实例字段来模拟它。这似乎真的不是一个干净的解决方案。如果在同一个类中有多个方法执行此类处理,则可能会重复使用同一个编写器或具有多个编写器字段。在这两种情况下,您都可能有副作用。
3)如果你执行了许多写操作并且想要真正隔离对编写器的调用,那么你就有了一个解决方案:重新设计你的代码以获得一个可测试的类。
您可以提取依赖项以执行编写器处理。该类可以提供具有执行指令所需参数的方法。我们可以称之为:WriteService
。
public class WriteService {
...
public void writeAndClose(Writer writer, String message){
try {
writer.write(message);
writer.close();
}
catch (IOException e) {
throw new IndexFileInitializationException("Error initialization index file " + emptyIndexFile.getPath());
}
}
}
此类是可测试的,因为writer依赖项是一个参数。
你打电话给新服务:
public class YourAppClass{
private WriteService writeService;
public YourAppClass(WriteService writeService){
this.writeService=writeService;
}
@Override
public void initIndexFile(File emptyIndexFile) {
Writer writer = new FileWriter(emptyIndexFile);
writeService.writeAndClose(writer,"[]");
}
}
现在initIndexFile()
也可以通过模拟WriteService
来测试。
您可以检查在writeService上使用good参数调用writeAndClose()。
就个人而言,我会使用第一种解决方案或第三种解决方案。