diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 25b9711b5a4b355528933d60be8c1e372d445a7c..ee8b52254466712d04dac4eef2fcc9cb3929510c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,4 +10,9 @@ stages: unitTests: stage: test script: - - ./gradlew test + - ./gradlew testDebugUnitTestCoverage + artifacts: + paths: + - app/build/reports/jacoco/ + + diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b25f67a096337303396435c57766a78f0a7462c9 --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +Codebase MyUI +===== + +### Specification + + +- Target SDK : Marshmallow (25) +- Minimum SDK : Ice Cream Sandwich (15) +- Installed Library : + 1. [Roughike Bottombar](https://github.com/roughike/BottomBar) + Defining footer tab with much easier way. + 2. [Retrofit](square.github.io/retrofit/) + Tool for getting Http Request. + 3. [GSON](https://github.com/google/gson) + Convert a Response from Retrofit into Gson, and can be converted into JSON Format. + 4. [JUnit](http://junit.org/junit4/) + Simple framework to write repeatable tests. + 5. [Espresso](https://google.github.io/android-testing-support-library/docs/espresso/) + Tool for creating Instrumental tests. + 6. [JaCoCo](https://github.com/jacoco/jacoco) + Tool for creating Code Coverage Report. + 7. [Gradle Console Reporter](https://github.com/ksoichiro/gradle-console-reporter) + Print all Gradle Report into Console. + +### Development Guideline + +1. Test Driven Development +2. To maintain high **Code Coverage** (which is important), make sure to: + - Write **Unit Test** that cover all lines in function + - Separate code that doesn't involved in **Unit Testing** (Read more about Unit Testing to understand it) + into another package, and then exclude those package from **Code Coverage Report** + - To exclude the package that doesn't need to be reported, please see file **jacoco.gradle** and see **classDirectories**, part **excludes** +3. Clean Code (I'll only write some main points that I think it's important to remember, for further information, please read Books about Clean Code) + + 1. Create a **Meaningfull Names** when you create a function, class, or variable + 2. Create a **Function that handle 1 specific task** instead of 1 function for many task + 3. Create a **Comments** for every important parts of code, to make an easier understanding of code + 4. Create a **Code that Self Explained** to make the code easier to read + 5. Create many **Small Function** when solving some problem is easier to do rather than creating + 1 large function. It's also easier to maintain if you have this kind of function + +4. Coding Guideline (For further information please visit this [link](https://source.android.com/source/code-style)) + + - Never write a code that doesn't handle an exception, always make sure that you catch every exception and give some feedback to user when that happen (Exception handling) + + ```java + /** Set port. If value is not a valid number, 80 is substituted. */ + + void setServerPort(String value) { + try { + serverPort = Integer.parseInt(value); + } catch (NumberFormatException e) { + serverPort = 80; // default port for server + } + } + ``` + + - When handling exception, make sure that you doesn't use Generic Exception as a way to catch those exception, + unless you doesn't sure what kind of error will be raised on the code + + ```java + try { + gettingDatafromAPI(); // may throw HttpException + convertDataToJSON(); // may throw ParsingException + calculateItAndReturn(); // may throw NumberFormatException + // phew, made it all the way + } catch (Exception e) { // I'll just catch all exceptions + handleError(); // with one generic handler! + } + ``` + + - When you importing some module or package, use full path import so we can make sure what package are imported, + unless it's come from Java Standard Library + + ```java + import android.support.v4.app.*; //Doesn't know which pacakage is needed, so it's bad + import android.support.v4.app.Fragment //Fragment with compat support is imported + ``` + + - Use proper Naming Convention + + ```java + public static final int SOME_CONSTANT = 42; //For Final variable, use all Uppercase Letter + private static MyClass sSingleton; //When using staticm use s at the beginning of name + private int mPrivate; // When using non-public/non-static, use m at the beginning of name + public int publicField; //Others, use lowercase at first word + ``` + - Use TODO Comments when you're creating a temporary solution (line of code, or function) to make + an understanding to next developer that this function needs some fixing + +### API Access + +API that will be used for this project is : [API CS](https://api.cs.ui.ac.id/) + +You can see this [link](https://api-dev.cs.ui.ac.id/how-to-use/) if you want to see how to access API CS + +### Virtual Machine (Server) + +We Prepare a VM for you, if you think that you need it for solving some task (such as creating additional +backend, or creating a proxy API) + +... to be continue + +### Build Status and Code Coverage + +Make sure that Build Status are Success and Code Coverage is High + +Here's the summary of build status and code coverage from important branch : + +1. **Master** + + [](https://gitlab.com/hafiyyan94/MyUI/commits/master) + [](https://gitlab.com/hafiyyan94/MyUI/commits/master) + +2. **Development** + + [](https://gitlab.com/hafiyyan94/MyUI/commits/development) + [](https://gitlab.com/hafiyyan94/MyUI/commits/development) + +All Right Reserved to Team Fasilkom UI \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d2ae2630f6f5611bea0bc01e651a401498e5d104..2875854bfa9f488e3b66ba5b3c55bd2c544c7f84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.application' +apply from: '../jacoco.gradle' +apply plugin: 'com.github.ksoichiro.console.reporter' android { compileSdkVersion 25 @@ -16,6 +18,9 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } + debug { + testCoverageEnabled true + } } } @@ -30,12 +35,13 @@ dependencies { compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' - - - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - + compile 'com.android.support:support-annotations:' + rootProject.supportLibVersion; + // Force usage of support annotations in the test app, since it is internally used by the runner module. + androidTestCompile 'com.android.support:support-annotations:' + rootProject.supportLibVersion; + androidTestCompile 'com.android.support.test:runner:' + rootProject.runnerVersion; + androidTestCompile 'com.android.support.test:rules:' + rootProject.rulesVersion; + androidTestCompile 'com.android.support.test.espresso:espresso-core:' + rootProject.espressoVersion; + androidTestCompile 'com.android.support.test.espresso:espresso-intents:' + rootProject.espressoVersion; compile 'com.android.support:appcompat-v7:25.0.3' compile 'com.android.support.constraint:constraint-layout:1.0.1' testCompile 'junit:junit:4.12' diff --git a/app/src/androidTest/java/id/ac/ui/cs/myui/LoginInstrumentedTest.java b/app/src/androidTest/java/id/ac/ui/cs/myui/LoginInstrumentedTest.java new file mode 100644 index 0000000000000000000000000000000000000000..34a62115952619e1f4b6964cfc9cc31e8f855094 --- /dev/null +++ b/app/src/androidTest/java/id/ac/ui/cs/myui/LoginInstrumentedTest.java @@ -0,0 +1,51 @@ +package id.ac.ui.cs.myui; + +import android.support.test.filters.LargeTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import id.ac.ui.cs.myui.activity.LoginActivity; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +/** + * Created by DELL on 7/13/2017. + */ +@RunWith(AndroidJUnit4.class) +@LargeTest +public class LoginInstrumentedTest { + + private String mButtonTextExpected; + + @Rule + public ActivityTestRule<LoginActivity> mActivityRule = new ActivityTestRule(LoginActivity.class); + + @Before + public void initValidString() { + // Specify a valid string. + mButtonTextExpected = "Login QR"; + } + + @Test + public void labelUsername_inLoginActivity() { + // Type text and then press the button. + onView(withId(R.id.login)).check(matches(withText(mButtonTextExpected))); + } + + @Test + public void clickLogin_showHomeActivity(){ + onView(withId(R.id.login)).perform(click()); + onView(withId(R.id.bottomBar)).check(matches(isDisplayed())); + } +} diff --git a/app/src/main/java/id/ac/ui/cs/myui/activity/DetailActivity.java b/app/src/main/java/id/ac/ui/cs/myui/activity/DetailActivity.java index 7257b894980bd33827faeddeeaa33a818653a169..f0030727c670888d22ce534ce6712a2303751adb 100644 --- a/app/src/main/java/id/ac/ui/cs/myui/activity/DetailActivity.java +++ b/app/src/main/java/id/ac/ui/cs/myui/activity/DetailActivity.java @@ -10,6 +10,10 @@ import id.ac.ui.cs.myui.R; * Created by faisalagustp on 7/13/17. */ +/** + * This class is used as a Response of pushing Academic Tracker button in HomeActivity + * You may change or delete this class in the future if it's necessary + */ public class DetailActivity extends AppCompatActivity { @Override @@ -19,7 +23,8 @@ public class DetailActivity extends AppCompatActivity { //Set title bar setTitle("My Academic Tracker"); - //Set back button + + //Set back button redirection getSupportActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/app/src/main/java/id/ac/ui/cs/myui/activity/HomeActivity.java b/app/src/main/java/id/ac/ui/cs/myui/activity/HomeActivity.java index b43f622bbdcd376372f02674397914a2cbb8703d..0b39cfad4fe562960bd06970a818afd40110df94 100644 --- a/app/src/main/java/id/ac/ui/cs/myui/activity/HomeActivity.java +++ b/app/src/main/java/id/ac/ui/cs/myui/activity/HomeActivity.java @@ -12,6 +12,11 @@ import id.ac.ui.cs.myui.R; import id.ac.ui.cs.myui.fragment.JadwalFragment; import id.ac.ui.cs.myui.fragment.MainFragment; + +/** + * This class is used for Homepage logic + * The footer tab logic also defined here + */ public class HomeActivity extends AppCompatActivity { @Override @@ -29,6 +34,8 @@ public class HomeActivity extends AppCompatActivity { bottomBar.setOnTabSelectListener(new OnTabSelectListener() { @Override public void onTabSelected(@IdRes int tabId) { + + //Checking tab Id that has been tapped if (tabId == R.id.tab_menu) { //Use MainFragment FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); diff --git a/app/src/main/java/id/ac/ui/cs/myui/activity/LoginActivity.java b/app/src/main/java/id/ac/ui/cs/myui/activity/LoginActivity.java index 1146b8a2ff4c0883406d859b71a660ad2b785ace..8b6b2ae1969840e53a4668d41e88a3d5f4b95407 100644 --- a/app/src/main/java/id/ac/ui/cs/myui/activity/LoginActivity.java +++ b/app/src/main/java/id/ac/ui/cs/myui/activity/LoginActivity.java @@ -12,6 +12,10 @@ import android.content.Intent; import id.ac.ui.cs.myui.R; +/** + * This class defining Login page Logic + * This class also need some fixing because Login logic isn't actually complete + */ public class LoginActivity extends AppCompatActivity { @Override diff --git a/app/src/main/java/id/ac/ui/cs/myui/fragment/JadwalFragment.java b/app/src/main/java/id/ac/ui/cs/myui/fragment/JadwalFragment.java index cee6fb6d29aacb785473d3609cdbcfe5bf6346ec..7113773a88df6f1d54a089b300c3216fd03ce752 100644 --- a/app/src/main/java/id/ac/ui/cs/myui/fragment/JadwalFragment.java +++ b/app/src/main/java/id/ac/ui/cs/myui/fragment/JadwalFragment.java @@ -12,6 +12,9 @@ import id.ac.ui.cs.myui.R; * Created by faisalagustp on 7/13/17. */ +/** + * This class defining Logic of JadwalPage when tab Jadwal is tapped + */ public class JadwalFragment extends Fragment { @Override diff --git a/app/src/main/java/id/ac/ui/cs/myui/fragment/MainFragment.java b/app/src/main/java/id/ac/ui/cs/myui/fragment/MainFragment.java index 123a12ba66d6532013d1bb53375dd6b116d820be..cd52e9b7d2384782907382a83215eac7b3a709ec 100644 --- a/app/src/main/java/id/ac/ui/cs/myui/fragment/MainFragment.java +++ b/app/src/main/java/id/ac/ui/cs/myui/fragment/MainFragment.java @@ -18,6 +18,10 @@ import id.ac.ui.cs.myui.activity.LoginActivity; * Created by faisalagustp on 7/13/17. */ +/** + * This class define Menu tab logic + * You may have to fix this class or maybe delete it if you think necessary + */ public class MainFragment extends Fragment { @Override diff --git a/app/src/main/res/layout/fragment_jadwal.xml b/app/src/main/res/layout/fragment_jadwal.xml index 096d34ddcb1ff4a6a1c4fb5a478c23d4209b21f0..658eb6199f4c0f717a5b527f3350af60997d7e75 100644 --- a/app/src/main/res/layout/fragment_jadwal.xml +++ b/app/src/main/res/layout/fragment_jadwal.xml @@ -4,6 +4,7 @@ tools:context="id.ac.ui.cs.myui.fragment.JadwalFragment"> <TextView + android:id="@+id/jadwaldisini" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Jadwal Disini" diff --git a/build.gradle b/build.gradle index c2eea8e27fd12cc1e274a0940f06f350e855e20f..c3f7e3d60c90d580012427d427947dff27d30e90 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' - + classpath 'com.github.ksoichiro:gradle-console-reporter:0.5.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -18,6 +18,14 @@ allprojects { } } +ext { + buildToolsVersion = "25.0.3" + supportLibVersion = "25.3.0" + runnerVersion = "0.5" + rulesVersion = "0.5" + espressoVersion = "2.2.2" +} + task clean(type: Delete) { delete rootProject.buildDir } diff --git a/jacoco.gradle b/jacoco.gradle new file mode 100644 index 0000000000000000000000000000000000000000..ee210566dc99e31d9000e08225ce2689fa7b4b72 --- /dev/null +++ b/jacoco.gradle @@ -0,0 +1,58 @@ +apply plugin: 'jacoco' + +jacoco { + toolVersion = "0.7.9" +} + +project.afterEvaluate { + // Grab all build types and product flavors + def buildTypes = android.buildTypes.collect { type -> type.name } + def productFlavors = android.productFlavors.collect { flavor -> flavor.name } + + // When no product flavors defined, use empty + if (!productFlavors) productFlavors.add('') + + productFlavors.each { productFlavorName -> + buildTypes.each { buildTypeName -> + def sourceName, sourcePath + if (!productFlavorName) { + sourceName = sourcePath = "${buildTypeName}" + } else { + sourceName = "${productFlavorName}${buildTypeName.capitalize()}" + sourcePath = "${productFlavorName}/${buildTypeName}" + } + def testTaskName = "test${sourceName.capitalize()}UnitTest" + + // Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest' + task "${testTaskName}Coverage" (type:JacocoReport, dependsOn: "$testTaskName") { + group = "Reporting" + description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build." + + classDirectories = fileTree( + dir: "${project.buildDir}/intermediates/classes/${sourcePath}", + excludes: ['**/R.class', + '**/R$*.class', + '**/*$ViewInjector*.*', + '**/*$ViewBinder*.*', + '**/BuildConfig.*', + '**/Manifest*.*' + ] + ) + + def coverageSourceDirs = [ + "src/main/java", + "src/$productFlavorName/java", + "src/$buildTypeName/java" + ] + additionalSourceDirs = files(coverageSourceDirs) + sourceDirectories = files(coverageSourceDirs) + executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec") + + reports { + xml.enabled = false + html.enabled = true + } + } + } + } +}