Eşzamansız İşlemler
Bu bölümde, Espresso Boşta Durma Kaynaklarını kullanarak eşzamansız işlemlerin nasıl test edileceğini öğreneceğiz.
Modern uygulamanın zorluklarından biri, sorunsuz bir kullanıcı deneyimi sağlamaktır. Sorunsuz bir kullanıcı deneyimi sağlamak, uygulama işleminin birkaç milisaniyeden uzun sürmemesini sağlamak için arka planda çok fazla çalışma gerektirir. Arka plan görevi basit olandan maliyetli ve karmaşık, uzak API / veritabanından veri getirme görevine kadar uzanır. Geçmişteki zorluklarla karşılaşmak için, bir geliştirici, maliyetli ve uzun süren bir görevi bir arka plan iş parçacığına yazıyor ve arka plan iş parçacığı tamamlandığında ana UIThread ile senkronize oluyordu .
Çok iş parçacıklı bir uygulama geliştirmek karmaşıksa, bunun için test senaryoları yazmak daha da karmaşıktır. Örneğin , gerekli veriler veritabanından yüklenmeden bir AdapterView'i test etmemeliyiz . Verilerin alınması ayrı bir iş parçacığında yapılırsa, testin iş parçacığı tamamlanana kadar beklemesi gerekir. Bu nedenle, test ortamı arka plan iş parçacığı ve UI iş parçacığı arasında senkronize edilmelidir. Espresso, çok iş parçacıklı uygulamayı test etmek için mükemmel bir destek sağlar. Bir uygulama iş parçacığını aşağıdaki şekillerde kullanır ve espresso her senaryoyu destekler.
Kullanıcı Arayüzü Diş Açma
Karmaşık UI öğeleriyle sorunsuz bir kullanıcı deneyimi sağlamak için dahili olarak android SDK tarafından kullanılır. Espresso bu senaryoyu şeffaf bir şekilde destekler ve herhangi bir konfigürasyona ve özel kodlamaya ihtiyaç duymaz.
Zaman uyumsuz görev
Modern programlama dilleri, iş parçacığı programlamanın karmaşıklığı olmadan hafif diş açma yapmak için eşzamansız programlamayı destekler. Eşzamansız görev ayrıca espresso çerçevesi tarafından şeffaf bir şekilde desteklenir.
Kullanıcı ileti dizisi
Bir geliştirici, veritabanından karmaşık veya büyük verileri almak için yeni bir iş parçacığı başlatabilir. Bu senaryoyu desteklemek için espresso, boşta çalışan kaynak konsepti sağlar.
Bu bölümde boşta çalışan kaynak kavramını ve nasıl yapılacağını öğrenelim.
Genel Bakış
Boşta kalan kaynak kavramı çok basit ve sezgiseldir. Temel fikir, uzun süre çalışan bir süreç ayrı bir iş parçacığında başlatıldığında, sürecin çalışıp çalışmadığını belirlemek ve bunu test ortamına kaydetmek için bir değişken (boole değeri) oluşturmaktır. Test sırasında, test çalıştırıcısı kayıtlı değişkeni, varsa, kontrol edecek ve ardından çalışma durumunu bulacaktır. Çalışma durumu doğruysa, test çalıştırıcısı durum yanlış olana kadar bekleyecektir.
Espresso, çalışma durumunu sürdürmek amacıyla bir arayüz, IdlingResources sağlar. Gerçekleştirilecek ana yöntem isIdleNow () 'dur. İsIdleNow () true döndürürse, espresso test sürecini sürdürür veya isIdleNow () false döndürene kadar bekler. IdlingResources'i uygulamalı ve türetilmiş sınıfı kullanmalıyız. Espresso, iş yükümüzü kolaylaştırmak için bazı yerleşik IdlingResources uygulamalarını da sağlar. Bunlar aşağıdaki gibidir,
CountingIdlingResource
Bu, çalışan görevin dahili bir sayacını korur. Increment () ve decment () yöntemlerini ortaya çıkarır . increment () , sayaca bir ekler ve decment () , sayaçtan bir tane çıkarır. isIdleNow () yalnızca hiçbir görev etkin olmadığında true değerini döndürür.
UriIdlingResource
Bu, CounintIdlingResource'a benzer, ancak ağ gecikmesini de almak için sayacın uzun süre sıfır olması gerekir.
IdlingThreadPoolExecutor
Bu, geçerli iş parçacığı havuzunda sayı etkin çalışan görevi korumak için özel bir ThreadPoolExecutor uygulamasıdır .
IdlingScheduledThreadHoolExecutor
Bu, IdlingThreadPoolExecutor'a benzer , ancak bir görevi ve ScheduledThreadPoolExecutor'un özel bir uygulamasını da zamanlar.
Uygulamada yukarıdaki IdlingResources uygulamalarından herhangi biri veya özel bir uygulama kullanılıyorsa, IdlingRegistry sınıfını kullanarak aşağıdaki gibi uygulamayı test etmeden önce onu test ortamına kaydetmemiz gerekir ,
IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
Ayrıca, aşağıdaki gibi test tamamlandıktan sonra çıkarılabilir -
IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
Espresso bu işlevi ayrı bir pakette sağlar ve paketin app.gradle'da aşağıdaki gibi yapılandırılması gerekir.
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Örnek Uygulama
Meyveleri bir web servisinden ayrı bir iş parçacığında alarak listelemek için basit bir uygulama oluşturalım ve ardından boşta çalışan kaynak konseptini kullanarak test edelim.
Android stüdyosunu başlatın.
Daha önce tartışıldığı gibi yeni proje oluşturun ve MyIdlingFruitApp olarak adlandırın
Geçiş kullanarak AndroidX çerçevesinde başvuruyu Elden Geçirme için → Geçirme AndroidX seçeneği menüde.
App / build.gradle içine espresso boşta kalan kaynak kitaplığını ekleyin (ve aşağıda belirtildiği gibi senkronize edin),
dependencies {
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Ana aktivitede varsayılan tasarımı kaldırın ve ListView ekleyin. Activity_main.xml dosyasının içeriği aşağıdaki gibidir,
<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
tools:context = ".MainActivity">
<ListView
android:id = "@+id/listView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content" />
</RelativeLayout>
Liste görünümünün öğe şablonunu belirtmek için yeni düzen kaynağı, item.xml ekleyin . İtem.xml dosyasının içeriği aşağıdaki gibidir,
<?xml version = "1.0" encoding = "utf-8"?>
<TextView xmlns:android = "http://schemas.android.com/apk/res/android"
android:id = "@+id/name"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
android:padding = "8dp"
/>
Yeni bir sınıf oluşturun - MyIdlingResource . MyIdlingResource , IdlingResource'umuzu tek bir yerde tutmak ve gerektiğinde almak için kullanılır. Örneğimizde CountingIdlingResource'u kullanacağız .
package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.test.espresso.IdlingResource;
import androidx.test.espresso.idling.CountingIdlingResource;
public class MyIdlingResource {
private static CountingIdlingResource mCountingIdlingResource =
new CountingIdlingResource("my_idling_resource");
public static void increment() {
mCountingIdlingResource.increment();
}
public static void decrement() {
mCountingIdlingResource.decrement();
}
public static IdlingResource getIdlingResource() {
return mCountingIdlingResource;
}
}
Küresel bir değişkeni bildirmek, mIdlingResource tipi CountingIdlingResource içinde MainActivity aşağıda sınıf,
@Nullable
private CountingIdlingResource mIdlingResource = null;
Aşağıdaki gibi web'den meyve listesi almak için özel bir yöntem yazın,
private ArrayList<String> getFruitList(String data) {
ArrayList<String> fruits = new ArrayList<String>();
try {
// Get url from async task and set it into a local variable
URL url = new URL(data);
Log.e("URL", url.toString());
// Create new HTTP connection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set HTTP connection method as "Get"
conn.setRequestMethod("GET");
// Do a http request and get the response code
int responseCode = conn.getResponseCode();
// check the response code and if success, get response content
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuffer response = new StringBuffer();
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
JSONArray jsonArray = new JSONArray(response.toString());
Log.e("HTTPResponse", response.toString());
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = String.valueOf(jsonObject.getString("name"));
fruits.add(name);
}
} else {
throw new IOException("Unable to fetch data from url");
}
conn.disconnect();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return fruits;
}
GetFruitList yöntemimizi kullanarak web'den verileri almak için onCreate () yönteminde yeni bir görev oluşturun ve ardından yeni bir bağdaştırıcı oluşturup onu liste görünümüne ayarlayın. Ayrıca, iş parçacığında çalışmamız tamamlandığında boşta kalan kaynağı azaltın. Kod aşağıdaki gibidir,
// Get data
class FruitTask implements Runnable {
ListView listView;
CountingIdlingResource idlingResource;
FruitTask(CountingIdlingResource idlingRes, ListView listView) {
this.listView = listView;
this.idlingResource = idlingRes;
}
public void run() {
//code to do the HTTP request
final ArrayList<String> fruitList = getFruitList("http://<your domain or IP>/fruits.json");
try {
synchronized (this){
runOnUiThread(new Runnable() {
@Override
public void run() {
// Create adapter and set it to list view
final ArrayAdapter adapter = new
ArrayAdapter(MainActivity.this, R.layout.item, fruitList);
ListView listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
MyIdlingResource.decrement(); // Set app as idle.
}
}
}
Burada meyve url'si http: // <alan adınız veya IP / meyveler.json olarak kabul edilir ve JSON olarak biçimlendirilir. İçerik aşağıdaki gibidir,
[
{
"name":"Apple"
},
{
"name":"Banana"
},
{
"name":"Cherry"
},
{
"name":"Dates"
},
{
"name":"Elderberry"
},
{
"name":"Fig"
},
{
"name":"Grapes"
},
{
"name":"Grapefruit"
},
{
"name":"Guava"
},
{
"name":"Jack fruit"
},
{
"name":"Lemon"
},
{
"name":"Mango"
},
{
"name":"Orange"
},
{
"name":"Papaya"
},
{
"name":"Pears"
},
{
"name":"Peaches"
},
{
"name":"Pineapple"
},
{
"name":"Plums"
},
{
"name":"Raspberry"
},
{
"name":"Strawberry"
},
{
"name":"Watermelon"
}
]
Note - Dosyayı yerel web sunucunuza yerleştirin ve kullanın.
Şimdi görünümü bulun, FruitTask'ı geçerek yeni bir iş parçacığı oluşturun , boşta kalan kaynağı artırın ve son olarak görevi başlatın.
// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
MainActivity'nin tam kodu aşağıdaki gibidir,
package com.tutorialspoint.espressosamples.myidlingfruitapp;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;
import androidx.test.espresso.idling.CountingIdlingResource;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
@Nullable
private CountingIdlingResource mIdlingResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Get data
class FruitTask implements Runnable {
ListView listView;
CountingIdlingResource idlingResource;
FruitTask(CountingIdlingResource idlingRes, ListView listView) {
this.listView = listView;
this.idlingResource = idlingRes;
}
public void run() {
//code to do the HTTP request
final ArrayList<String> fruitList = getFruitList(
"http://<yourdomain or IP>/fruits.json");
try {
synchronized (this){
runOnUiThread(new Runnable() {
@Override
public void run() {
// Create adapter and set it to list view
final ArrayAdapter adapter = new ArrayAdapter(
MainActivity.this, R.layout.item, fruitList);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
}
});
}
} catch (Exception e) {
e.printStackTrace();
}
if (!MyIdlingResource.getIdlingResource().isIdleNow()) {
MyIdlingResource.decrement(); // Set app as idle.
}
}
}
// Find list view
ListView listView = (ListView) findViewById(R.id.listView);
Thread fruitTask = new Thread(new FruitTask(this.mIdlingResource, listView));
MyIdlingResource.increment();
fruitTask.start();
}
private ArrayList<String> getFruitList(String data) {
ArrayList<String> fruits = new ArrayList<String>();
try {
// Get url from async task and set it into a local variable
URL url = new URL(data);
Log.e("URL", url.toString());
// Create new HTTP connection
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set HTTP connection method as "Get"
conn.setRequestMethod("GET");
// Do a http request and get the response code
int responseCode = conn.getResponseCode();
// check the response code and if success, get response content
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuffer response = new StringBuffer();
while ((line = in.readLine()) != null) {
response.append(line);
}
in.close();
JSONArray jsonArray = new JSONArray(response.toString());
Log.e("HTTPResponse", response.toString());
for(int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String name = String.valueOf(jsonObject.getString("name"));
fruits.add(name);
}
} else {
throw new IOException("Unable to fetch data from url");
}
conn.disconnect();
} catch (IOException | JSONException e) {
e.printStackTrace();
}
return fruits;
}
}
Şimdi, uygulama bildirim dosyası olan AndroidManifest.xml'de aşağıdaki yapılandırmayı ekleyin
<uses-permission android:name = "android.permission.INTERNET" />
Şimdi yukarıdaki kodu derleyin ve uygulamayı çalıştırın. Idling Fruit Uygulamamın ekran görüntüsü aşağıdaki gibidir,
Şimdi, ExampleInstrumentedTest.java dosyasını açın ve ActivityTestRule'u aşağıda belirtildiği gibi ekleyin,
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
Also, make sure the test configuration is done in app/build.gradle
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test:rules:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'androidx.test.espresso:espresso-idling-resource:3.1.1'
androidTestImplementation "androidx.test.espresso.idling:idlingconcurrent:3.1.1"
}
Liste görünümünü aşağıdaki gibi test etmek için yeni bir test senaryosu ekleyin,
@Before
public void registerIdlingResource() {
IdlingRegistry.getInstance().register(MyIdlingResource.getIdlingResource());
}
@Test
public void contentTest() {
// click a child item
onData(allOf())
.inAdapterView(withId(R.id.listView))
.atPosition(10)
.perform(click());
}
@After
public void unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(MyIdlingResource.getIdlingResource());
}
Son olarak, android stüdyosunun içerik menüsünü kullanarak test senaryosunu çalıştırın ve tüm test durumlarının başarılı olup olmadığını kontrol edin.