Autor Wątek: C++ Konwencje, oddzielenie modułów projektu  (Przeczytany 5963 razy)

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Maj 30, 2013, 13:32:45
Zapodaj kod kontrolki, dam Ci do niej testy.
Chyba interfejsu kontrolki...

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline siso

  • Użytkownik

# Maj 30, 2013, 14:02:32
Może być interfejsu. Ale testuje się nie interfejsy, a implementacje i o to mi dokładnie chodziło. Przykład testu dla konkretnej implementacji. Żeby nie było pustosłowia.

Wiem, wiem... Będziecie się tu teraz dupereli czepiać, żeby tylko pokazać, że unit testy są zUEm, bo w "gejmdewie się ich nie używa", "bo to za dużo zachodu", "a czasami się po prostu nie da", i w ogóle to wszystko do bani jest :)

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Maj 30, 2013, 14:31:11
Po prostu najpierw stoisz za podejściem test->kodowanie->zielono.

A teraz gdy chcesz pokazać plusy tego podejścia wymagasz pokazania implementacji.
Nonsens!

Jeżeli chcesz pokazać zalety powinieneś oprzeć się tylko na interfejsie kontrolki, bo jaki sens ma pisanie testów do implementacji, która ma powstawać tak by przejść testy?

To na podstawie wymagań/interfejsu powinieneś oprzeć testy. No bo jak inaczej?


Ja się nie czepiam że testy to zło, sam teraz rozgryzam temat testów (zlecenie gdzie błędy mogą boleć użytkowników i to dosłownie ).

Offline siso

  • Użytkownik

# Maj 30, 2013, 15:52:43
Hmmm, nie miałem zupełnie chęci zgłębiać samego tematu TDD-owania GUI teraz, i dlatego poprosiłem o implementację. Ale ok, skoro tak, to niech będzie przykład z obszaru luźno-kompilowalnej Javy:

public interface Window<T> {
    Handle getHandle();
    String getTitle();
    Point2D<T> getTop();
    Size2D<T> getSize();

    boolean isVisible();

}

public class MyFancyWindow implements Window<Integer> {

    public MyFancyWindow(Handle handle, Poin2D<integer> top, Size2D<Integer> size, String title) {...}

    public String getTitle() {...};
    public Point2D<T> getTop() {...};
    public Size2D<T> getSize() {...};

    public boolean isVisible() {...};
}

public class MyFancyWindowTest {

    @Test
    public void shouldCreateWindow() {
        // given
        Handle handle = new Handle();
        Point2D<Integer> top = new Point2D<Integer>(10, 20);
        Size2D>Integer> size = new Size2D<Integer>(200, 300);
        String title = "my very first unit-tested window";

        // when
        MyFancyWindow window = new MyFancyWindow(handle, top, size, title);

        // then
        assertThat(window.getHandle()).isEqualTo(handle);
        assertThat(window.getTitle()).isEqualTo(title);
        assertThat(window.getTop()).isEqualTo(top);
        assertThat(window.getSize()).isEqualTo(size);
        assertThat(window.getVisible()).isFalse();
    }

    @Test
    public void shouldMoveWindow() {
        // given
        Handle handle = new Handle();
        Point2D<Integer> top = new Point2D<Integer>(10, 20);
        Size2D>Integer> size = new Size2D<Integer>(200, 300);
        MyFancyWindow window = new MyFancyWindow(handle, top, size, "my very first unit-tested window");
        Point2D<Integer> newPosition = new Point2D<Integer>(10, 20);

        // when
        window.move(newPosition);

        // then
        assertThat(window.getTop()).isEqualTo(newPosition);
        assertThat(window.getSize()).isEqualTo(size); // size doesn't change when moving (object invariant)
    }

}

I tak dalej, pamiętając oczywiście o rozdzielaniu pojęć, a więc:
- okno samo się nie renderuje, robi to renderer dostając je jako argument wywołania;
- uchwyt okna to tylko dane (tak samo, jak okno w tym przykładzie), więc odpowiedzi na pytanie "is it valid?" udziela jakieś magiczne WindowRegistry czy cokolwiek w ten deseń.

Jeśli chcieli byśmy pozwolić oknu renderować samodzielnie, to koniecznie trzeba to zrobić jako delegację wywołania do dostarczonego z zewnątrz jako zależność klasy renderera. Wówczas można tegoż renderera sobie zamokować i upewnić się, że wywołanie renderujące okno wymusza rendering na rendererze. Renderer, rzecz jasna testujemy całkiem osobno.

Podobnych przykładów można by tu namnożyć, większość będzie zależna od sposobu, w jaki projektujemy GUI. Niezależne są tylko pojęcia leżące u podstaw designu.

Offline artur1

  • Użytkownik

# Czerwiec 12, 2013, 04:35:36
Bo skoro coś czegoś potrzebuje, to niech dostaje to po prostu w konstruktorze:
guiRender = new GUIRender(deviceContext, ...);To najprostsze i najlogiczniejsze rozwiązanie, tyle że przy większej liczbie zależności zaczyna się robić trochę niewygodne w użyciu. Niestety w C++ nie ma żadnego dobrego rozwiązania do Dependency Injection, więc trzeba z tym żyć.
Tylko dlaczego obiekt GUIRender musi być wskaźnikiem, a nie może być zwykłym obiektem klasy ? To są jakieś konwencje czy jak ? Do głowy przychodzi mi tylko jedno rozwiązanie, jeżeli ten obiekt jest utworzony to z dostępem do niego nie będzie problemu a jeżeli nie jest utworzony bo nie jest potrzeby to w takim wypadku przy jakimś niejawnym dostępnie coś odwoła się do zerowego wskaźnika i wykryty zostanie błąd.

A co do reszty, to po co te testy jednostkowe dla przycisku skoro to taki krótki i banalny kod ? Po najechaniu myszą zmienia się jego stan, po wciśnięciu tego przycisku zmienia się jego stan, to widać więc po co te testy ?

Offline aphity

  • Użytkownik

# Czerwiec 14, 2013, 12:51:26
Tylko dlaczego obiekt GUIRender musi być wskaźnikiem, a nie może być zwykłym obiektem klasy ? To są jakieś konwencje czy jak ? Do głowy przychodzi mi tylko jedno rozwiązanie, jeżeli ten obiekt jest utworzony to z dostępem do niego nie będzie problemu a jeżeli nie jest utworzony bo nie jest potrzeby to w takim wypadku przy jakimś niejawnym dostępnie coś odwoła się do zerowego wskaźnika i wykryty zostanie błąd.
Nie musi. Jezeli w danej aplikacji masz tylko jeden GUI renderer (zwykle tak bedzie), mozesz zrobic np. tak:

int main() {
    GUIRender render(new DeviceContext(), ...);
    render.mainLoop();
}

Renderer bedzie zmienna automatyczna, wiec bonusowo nie musisz sie martwic jego usuwaniem. Za to gdy zaczniesz pisac inna aplikacje, moze sie okazac ze nie potrzebujesz GUI przez caly czas - wtedy nieodzowny bedzie wskaznik.

A co do reszty, to po co te testy jednostkowe dla przycisku skoro to taki krótki i banalny kod ? Po najechaniu myszą zmienia się jego stan, po wciśnięciu tego przycisku zmienia się jego stan, to widać więc po co te testy ?
Tyle ze "krotki i banalny kod" ma taka dziwna wlasciwosc (zwana tez II zasada termodynamiki), ze w dosc krotkim czasie potrafi sie zrobic bardzo skomplikowany i zawily. Biblioteki nie sa niezmienne w czasie, bardzo czesto masz ochote (pragnienie czy wrecz koniecznosc) dodac nowe mozliwosci. Teraz, jezeli bedziesz rozwijal swoja biblioteke dodajac rozne rzeczy: zmiane wygladu przycisku, focus, wsparcie klawiatury, wiecej niz dwa stany... to uwierz, sprawdzanie za kazdym razem czy drobna zmiana kodu nie rozwalila ktorejs z funkcji zrobi sie naprawde bardzo meczace. A jesli masz testy, to sprowadza sie do ich czestego odpalenia (najlepiej po kazdym buildzie ;) )

Offline artur1

  • Użytkownik

# Czerwiec 27, 2013, 23:23:33
Dobra a jak w takiej bibliotece umieścić Loggera ? Obiekt globalny ? Czy może tutaj już użycie singletona jest uzasadnione ?

Offline rm-f

  • Użytkownik
    • Tu trolluje

# Czerwiec 27, 2013, 23:50:27
Dobra a jak w takiej bibliotece umieścić Loggera ? Obiekt globalny ? Czy może tutaj już użycie singletona jest uzasadnione ?
Nie masz większego problemu?

Offline artur1

  • Użytkownik

# Czerwiec 27, 2013, 23:55:36
Czyli to bez różnicy ale ładniejszy będzie singleton ?

Offline Xirdus

  • Redaktor

# Czerwiec 28, 2013, 01:18:13
Jeśli są dwa sposoby na zrobienie czegoś, które dają takie same możliwości, wybierz sposób prostszy. Przy czym nie chodzi o łatwiejszy dla ciebie, tylko taki, w którym mniej rzeczy może się popsuć / ma mniej zależności z innymi modułami.

obiekt globalny z funkcją inicjalizującą

Offline Xion

  • Redaktor
    • xion.log

  • +1
# Czerwiec 28, 2013, 02:31:36
Cytuj
mniej zależności
Cytuj
obiekt globalny

Offline Xirdus

  • Redaktor

# Czerwiec 28, 2013, 02:44:33
@up
Mnie się wydaje, że obiekt globalny to mniej zależności od singletona. Oczywiście, wskaźnik globalny byłby jeszcze lepszy w tym względzie, bo można łatwo podmienić obiekt jeśli jest potrzeba. Zależności w 100% się nigdy nie uniknie.

Offline Xion

  • Redaktor
    • xion.log

  • +1
# Czerwiec 28, 2013, 15:11:49
Prawie w ogóle nie rozumiem, co masz tutaj na myśli.

Każdy globalny stan prowadza niejawne zależności między wszystkimi modułami w których jest dostępny. Nie ma tutaj najmniejszego znaczenia jak ów stan nazwiesz lub zaimplentujesz. Czy to będzie full-wypas GPG-based template-powered singleton, czy obiekt globalny, czy wskaźnik globalny, czy pole statyczne z prywatnym konstruktorem, czy zmienna statyczna w funkcji, czy jeszcze inny sposób na osiągnięcie tego samego który nie przychodzi mi teraz do głowy.

Skutkiem może być zawsze "spooky action at a distance": kod w jednym miejscu zależy niejawnie i czasem w nieoczywisty sposób od kodu w zupełnie innym miejscu.