Refactor Tasks for better testability

pull/77/merge
Alinson S. Xavier 10 years ago
parent 67b88c5012
commit 2ba7246e5f

@ -0,0 +1,53 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits;
import android.content.Context;
import android.os.Looper;
import android.support.test.InstrumentationRegistry;
import org.isoron.uhabits.tasks.BaseTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class BaseTest
{
protected Context testContext;
protected Context targetContext;
private static boolean isLooperPrepared;
protected void setup()
{
if(!isLooperPrepared)
{
Looper.prepare();
isLooperPrepared = true;
}
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
}
protected void waitForAsyncTasks() throws InterruptedException, TimeoutException
{
BaseTask.waitForTasks(30, TimeUnit.SECONDS);
}
}

@ -17,7 +17,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.isoron.uhabits.unit.models; package org.isoron.uhabits.unit;
import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.helpers.ColorHelper;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
@ -60,9 +60,39 @@ public class HabitFixtures
return habit; return habit;
} }
public static Habit createLongHabit()
{
Habit habit = createEmptyHabit();
habit.freqNum = 3;
habit.freqDen = 7;
habit.color = ColorHelper.palette[4];
habit.save();
long day = DateHelper.millisecondsInOneDay;
long today = DateHelper.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, 28, 50, 51, 52,
53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 81, 83, 89, 90, 91, 95,
102, 103, 108, 109, 120};
for(int mark : marks)
habit.repetitions.toggle(today - mark * day);
return habit;
}
public static void purgeHabits() public static void purgeHabits()
{ {
for(Habit h : Habit.getAll(true)) for(Habit h : Habit.getAll(true))
h.cascadeDelete(); h.cascadeDelete();
} }
public static void fixTime()
{
DateHelper.setFixedLocalTime(FIXED_LOCAL_TIME);
}
public static void releaseTime()
{
DateHelper.setFixedLocalTime(null);
}
} }

@ -27,7 +27,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.io.HabitsCSVExporter; import org.isoron.uhabits.io.HabitsCSVExporter;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.models.HabitFixtures; import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;

@ -28,7 +28,7 @@ import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.io.GenericImporter; import org.isoron.uhabits.io.GenericImporter;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.models.HabitFixtures; import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;

@ -24,6 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -26,6 +26,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.hamcrest.MatcherAssert; import org.hamcrest.MatcherAssert;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;

@ -24,6 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -26,6 +26,7 @@ import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.models.Score; import org.isoron.uhabits.models.Score;
import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -19,22 +19,20 @@
package org.isoron.uhabits.unit.tasks; package org.isoron.uhabits.unit.tasks;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.tasks.ExportCSVTask; import org.isoron.uhabits.tasks.ExportCSVTask;
import org.isoron.uhabits.unit.models.HabitFixtures; import org.isoron.uhabits.unit.HabitFixtures;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -44,17 +42,20 @@ import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class ExportCSVTaskTest public class ExportCSVTaskTest extends BaseTest
{ {
@Test @Before
public void exportCSV() throws InterruptedException public void setup()
{ {
Context context = InstrumentationRegistry.getContext(); super.setup();
final CountDownLatch latch = new CountDownLatch(1); }
@Test
public void exportCSV() throws Throwable
{
HabitFixtures.createNonDailyHabit(); HabitFixtures.createNonDailyHabit();
List<Habit> habits = Habit.getAll(true); List<Habit> habits = Habit.getAll(true);
ProgressBar bar = new ProgressBar(context); ProgressBar bar = new ProgressBar(targetContext);
ExportCSVTask task = new ExportCSVTask(habits, bar); ExportCSVTask task = new ExportCSVTask(habits, bar);
task.setListener(new ExportCSVTask.Listener() task.setListener(new ExportCSVTask.Listener()
@ -67,11 +68,10 @@ public class ExportCSVTaskTest
File f = new File(archiveFilename); File f = new File(archiveFilename);
assertTrue(f.exists()); assertTrue(f.exists());
assertTrue(f.canRead()); assertTrue(f.canRead());
latch.countDown();
} }
}); });
task.execute(); task.execute();
latch.await(30, TimeUnit.SECONDS); waitForAsyncTasks();
} }
} }

@ -25,13 +25,13 @@ import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.tasks.ExportDBTask; import org.isoron.uhabits.tasks.ExportDBTask;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.io.File; import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -41,13 +41,18 @@ import static org.hamcrest.core.IsNot.not;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class ExportDBTaskTest public class ExportDBTaskTest extends BaseTest
{ {
@Before
public void setup()
{
super.setup();
}
@Test @Test
public void exportCSV() throws InterruptedException public void exportCSV() throws Throwable
{ {
Context context = InstrumentationRegistry.getContext(); Context context = InstrumentationRegistry.getContext();
final CountDownLatch latch = new CountDownLatch(1);
ProgressBar bar = new ProgressBar(context); ProgressBar bar = new ProgressBar(context);
ExportDBTask task = new ExportDBTask(bar); ExportDBTask task = new ExportDBTask(bar);
@ -61,11 +66,10 @@ public class ExportDBTaskTest
File f = new File(filename); File f = new File(filename);
assertTrue(f.exists()); assertTrue(f.exists());
assertTrue(f.canRead()); assertTrue(f.canRead());
latch.countDown();
} }
}); });
task.execute(); task.execute();
latch.await(30, TimeUnit.SECONDS); waitForAsyncTasks();
} }
} }

@ -19,13 +19,12 @@
package org.isoron.uhabits.unit.tasks; package org.isoron.uhabits.unit.tasks;
import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.SmallTest;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DatabaseHelper;
import org.isoron.uhabits.tasks.ImportDataTask; import org.isoron.uhabits.tasks.ImportDataTask;
import org.junit.Before; import org.junit.Before;
@ -44,15 +43,14 @@ import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@SmallTest @SmallTest
public class ImportDataTaskTest public class ImportDataTaskTest extends BaseTest
{ {
private Context context;
private File baseDir; private File baseDir;
@Before @Before
public void setup() public void setup()
{ {
context = InstrumentationRegistry.getContext(); super.setup();
baseDir = DatabaseHelper.getFilesDir("Backups"); baseDir = DatabaseHelper.getFilesDir("Backups");
if(baseDir == null) fail("baseDir should not be null"); if(baseDir == null) fail("baseDir should not be null");
@ -60,14 +58,12 @@ public class ImportDataTaskTest
private void copyAssetToFile(String assetPath, File dst) throws IOException private void copyAssetToFile(String assetPath, File dst) throws IOException
{ {
InputStream in = context.getAssets().open(assetPath); InputStream in = testContext.getAssets().open(assetPath);
DatabaseHelper.copy(in, dst); DatabaseHelper.copy(in, dst);
} }
private void assertTaskResult(final int expectedResult, String assetFilename) private void assertTaskResult(final int expectedResult, String assetFilename) throws Throwable
throws IOException, InterruptedException
{ {
final CountDownLatch latch = new CountDownLatch(1);
ImportDataTask task = createTask(assetFilename); ImportDataTask task = createTask(assetFilename);
task.setListener(new ImportDataTask.Listener() task.setListener(new ImportDataTask.Listener()
@ -76,18 +72,17 @@ public class ImportDataTaskTest
public void onImportFinished(int result) public void onImportFinished(int result)
{ {
assertThat(result, equalTo(expectedResult)); assertThat(result, equalTo(expectedResult));
latch.countDown();
} }
}); });
task.execute(); task.execute();
latch.await(30, TimeUnit.SECONDS); waitForAsyncTasks();
} }
@NonNull @NonNull
private ImportDataTask createTask(String assetFilename) throws IOException private ImportDataTask createTask(String assetFilename) throws IOException
{ {
ProgressBar bar = new ProgressBar(context); ProgressBar bar = new ProgressBar(targetContext);
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename)); File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
copyAssetToFile(assetFilename, file); copyAssetToFile(assetFilename, file);

@ -24,7 +24,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.DateHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import org.isoron.uhabits.unit.models.HabitFixtures; import org.isoron.uhabits.unit.HabitFixtures;
import org.isoron.uhabits.views.CheckmarkView; import org.isoron.uhabits.views.CheckmarkView;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;

@ -19,14 +19,15 @@
package org.isoron.uhabits.unit.views; package org.isoron.uhabits.unit.views;
import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.support.test.InstrumentationRegistry; import android.os.SystemClock;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import org.isoron.uhabits.BaseTest;
import org.isoron.uhabits.helpers.DialogHelper; import org.isoron.uhabits.helpers.DialogHelper;
import org.junit.Before;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -35,21 +36,11 @@ import java.io.InputStream;
import static junit.framework.Assert.fail; import static junit.framework.Assert.fail;
public class ViewTest public class ViewTest extends BaseTest
{ {
protected static final double SIMILARITY_CUTOFF = 0.02; protected static final double SIMILARITY_CUTOFF = 0.02;
public static final int HISTOGRAM_BIN_SIZE = 8; public static final int HISTOGRAM_BIN_SIZE = 8;
protected Context testContext;
protected Context targetContext;
@Before
public void setup()
{
targetContext = InstrumentationRegistry.getTargetContext();
testContext = InstrumentationRegistry.getContext();
}
protected void measureView(int width, int height, View view) protected void measureView(int width, int height, View view)
{ {
int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); int specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
@ -190,4 +181,13 @@ public class ViewTest
{ {
return (int) DialogHelper.dpToPixels(targetContext, dp); return (int) DialogHelper.dpToPixels(targetContext, dp);
} }
protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException
{
long now = SystemClock.uptimeMillis();
MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
dpToPixels(y), 0);
view.onSingleTapUp(e);
e.recycle();
}
} }

@ -0,0 +1,61 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.tasks;
import android.os.AsyncTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class BaseTask extends AsyncTask<Void, Void, Void>
{
private static CountDownLatch latch;
private static int activeTaskCount;
@Override
protected Void doInBackground(Void... params)
{
return null;
}
@Override
protected void onPreExecute()
{
activeTaskCount++;
latch = new CountDownLatch(activeTaskCount);
}
@Override
protected void onPostExecute(Void aVoid)
{
activeTaskCount--;
latch.countDown();
}
public static void waitForTasks(long timeout, TimeUnit unit)
throws TimeoutException, InterruptedException
{
if(activeTaskCount == 0) return;
boolean successful = latch.await(timeout, unit);
if(!successful) throw new TimeoutException();
}
}

@ -32,7 +32,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
public class ExportCSVTask extends AsyncTask<Void, Void, Void> public class ExportCSVTask extends BaseTask
{ {
public interface Listener public interface Listener
{ {
@ -58,6 +58,8 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
@Override @Override
protected void onPreExecute() protected void onPreExecute()
{ {
super.onPreExecute();
if(progressBar != null) if(progressBar != null)
{ {
progressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
@ -73,6 +75,8 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
if(progressBar != null) if(progressBar != null)
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
super.onPostExecute(null);
} }
@Override @Override

@ -29,7 +29,7 @@ import org.isoron.uhabits.helpers.DatabaseHelper;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
public class ExportDBTask extends AsyncTask<Void, Void, Void> public class ExportDBTask extends BaseTask
{ {
public interface Listener public interface Listener
{ {
@ -53,6 +53,8 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
@Override @Override
protected void onPreExecute() protected void onPreExecute()
{ {
super.onPreExecute();
if(progressBar != null) if(progressBar != null)
{ {
progressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
@ -68,6 +70,8 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
if(progressBar != null) if(progressBar != null)
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
super.onPostExecute(null);
} }
@Override @Override

@ -29,7 +29,7 @@ import org.isoron.uhabits.io.GenericImporter;
import java.io.File; import java.io.File;
public class ImportDataTask extends AsyncTask<Void, Void, Void> public class ImportDataTask extends BaseTask
{ {
public static final int SUCCESS = 1; public static final int SUCCESS = 1;
public static final int NOT_RECOGNIZED = 2; public static final int NOT_RECOGNIZED = 2;
@ -65,6 +65,8 @@ public class ImportDataTask extends AsyncTask<Void, Void, Void>
@Override @Override
protected void onPreExecute() protected void onPreExecute()
{ {
super.onPreExecute();
if(progressBar != null) if(progressBar != null)
{ {
progressBar.setIndeterminate(true); progressBar.setIndeterminate(true);
@ -79,6 +81,8 @@ public class ImportDataTask extends AsyncTask<Void, Void, Void>
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
if(listener != null) listener.onImportFinished(result); if(listener != null) listener.onImportFinished(result);
super.onPostExecute(null);
} }
@Override @Override

@ -25,6 +25,7 @@ import android.util.AttributeSet;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewParent;
import android.widget.Scroller; import android.widget.Scroller;
public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener, public abstract class ScrollableDataView extends View implements GestureDetector.OnGestureListener,
@ -89,7 +90,10 @@ public abstract class ScrollableDataView extends View implements GestureDetector
return false; return false;
if(Math.abs(dx) > Math.abs(dy)) if(Math.abs(dx) > Math.abs(dy))
getParent().requestDisallowInterceptTouchEvent(true); {
ViewParent parent = getParent();
if(parent != null) parent.requestDisallowInterceptTouchEvent(true);
}
scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0); scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) -dx, (int) dy, 0);
scroller.computeScrollOffset(); scroller.computeScrollOffset();

Loading…
Cancel
Save